/**
 * Service responsible for login validation and failed login handling
 */
import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import * as argon2 from "argon2";
import type { Request } from "express";
import type { User as PrismaUser } from "@prisma/client";
import { UsersService } from "@bff/modules/users/application/users.service.js";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";

export interface ValidatedUser {
  id: string;
  email: string;
  role: string;
}

@Injectable()
export class AuthLoginService {
  private readonly MAX_LOGIN_ATTEMPTS = 5;
  private readonly LOCKOUT_DURATION_MINUTES = 15;

  constructor(
    private readonly usersService: UsersService,
    private readonly auditService: AuditService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Validate user credentials
   */
  async validateUser(
    email: string,
    password: string,
    request?: Request
  ): Promise<ValidatedUser | null> {
    const user = await this.usersService.findByEmailInternal(email);

    if (!user) {
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        undefined,
        { reason: "User not found" },
        request,
        false,
        "User not found"
      );
      return null;
    }

    // Check if account is locked
    if (user.lockedUntil && user.lockedUntil > new Date()) {
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        user.id,
        { reason: "Account locked" },
        request,
        false,
        "Account is locked"
      );
      return null;
    }

    if (!user.passwordHash) {
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        user.id,
        { reason: "No password set" },
        request,
        false,
        "No password set"
      );
      return null;
    }

    try {
      const isPasswordValid = await argon2.verify(user.passwordHash, password);

      if (isPasswordValid) {
        return {
          id: user.id,
          email: user.email,
          role: user.role,
        };
      } else {
        await this.handleFailedLogin(user, request);
        return null;
      }
    } catch (error) {
      this.logger.error("Password validation error", {
        userId: user.id,
        error: extractErrorMessage(error),
      });
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        user.id,
        { error: extractErrorMessage(error) },
        request,
        false,
        extractErrorMessage(error)
      );
      return null;
    }
  }

  /**
   * Handle failed login attempt - increment counter and lock if needed
   */
  async handleFailedLogin(user: PrismaUser, request?: Request): Promise<void> {
    const newFailedAttempts = (user.failedLoginAttempts || 0) + 1;
    let lockedUntil = null;
    let isAccountLocked = false;

    if (newFailedAttempts >= this.MAX_LOGIN_ATTEMPTS) {
      lockedUntil = new Date();
      lockedUntil.setMinutes(lockedUntil.getMinutes() + this.LOCKOUT_DURATION_MINUTES);
      isAccountLocked = true;
    }

    await this.usersService.update(user.id, {
      failedLoginAttempts: newFailedAttempts,
      lockedUntil,
    });

    await this.auditService.logAuthEvent(
      AuditAction.LOGIN_FAILED,
      user.id,
      {
        failedAttempts: newFailedAttempts,
        lockedUntil: lockedUntil?.toISOString(),
      },
      request,
      false,
      "Invalid password"
    );

    if (isAccountLocked) {
      await this.auditService.logAuthEvent(
        AuditAction.ACCOUNT_LOCKED,
        user.id,
        {
          lockDuration: this.LOCKOUT_DURATION_MINUTES,
          lockedUntil: lockedUntil?.toISOString(),
        },
        request,
        false,
        `Account locked for ${this.LOCKOUT_DURATION_MINUTES} minutes`
      );
    }
  }
}
