import {
  ConflictException,
  ForbiddenException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { argon2id, hash, verify } from 'argon2';
import { eq } from 'drizzle-orm';
import { DatabaseService } from '../../common/database/database.service';
import { users } from '../../database/schema';
import { AuthSessionsService, type SessionContext } from './auth-sessions.service';
import type { LoginDto } from './dto/login.dto';
import type { RegisterDto } from './dto/register.dto';

@Injectable()
export class AuthService {
  constructor(
    private readonly database: DatabaseService,
    private readonly sessions: AuthSessionsService,
    private readonly config: ConfigService,
  ) {}

  async register(input: RegisterDto, context: SessionContext) {
    if (!this.config.get<boolean>('AUTH_ALLOW_REGISTRATION', false)) {
      throw new ForbiddenException('Public registration is disabled.');
    }

    const email = input.email.trim().toLowerCase();
    const [existing] = await this.database.db
      .select({ id: users.id })
      .from(users)
      .where(eq(users.email, email))
      .limit(1);
    if (existing) {
      throw new ConflictException('An account with this email already exists.');
    }

    const passwordHash = await hash(input.password, {
      type: argon2id,
      memoryCost: 19_456,
      timeCost: 3,
      parallelism: 1,
    });

    try {
      const [user] = await this.database.db
        .insert(users)
        .values({
          email,
          passwordHash,
          displayName: input.displayName?.trim() || null,
        })
        .returning({
          id: users.id,
          email: users.email,
          displayName: users.displayName,
          role: users.role,
        });

      const session = await this.sessions.create(user.id, context);
      return { user, session };
    } catch (error: unknown) {
      if (this.isUniqueViolation(error)) {
        throw new ConflictException('An account with this email already exists.');
      }
      throw error;
    }
  }

  async login(input: LoginDto, context: SessionContext) {
    const [user] = await this.database.db
      .select()
      .from(users)
      .where(eq(users.email, input.email.trim().toLowerCase()))
      .limit(1);

    if (!user || !user.isActive || !(await verify(user.passwordHash, input.password))) {
      throw new UnauthorizedException('Email or password is incorrect.');
    }

    const session = await this.sessions.create(user.id, context);
    return {
      user: {
        id: user.id,
        email: user.email,
        displayName: user.displayName,
        role: user.role,
      },
      session,
    };
  }

  private isUniqueViolation(error: unknown): boolean {
    return typeof error === 'object' && error !== null && 'code' in error && error.code === '23505';
  }
}
