import crypto from 'crypto';

import { logger } from '@goodgo/logger';
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import { Request } from 'express';

import { getPrismaClient } from '../../config/database.config';
import { auditService } from '../../core/events/audit.service';
import { AuthResponse, RefreshTokenResponse, UserResponse } from '../../types/auth.types';
import { mfaService } from '../mfa/mfa.service';
import { rbacService } from '../rbac/rbac.service';
import { sessionService } from '../session/session.service';
import { jwtService } from '../token/jwt.service';

import { accountLockoutService } from './account-lockout.service';


/**
 * EN: Auth Service for authentication operations
 * VI: Service Auth cho các thao tác xác thực
 */
export class AuthService {
  private prisma: PrismaClient;

  constructor() {
    this.prisma = getPrismaClient();
  }

  /**
   * EN: Hash refresh token for secure storage
   * VI: Hash refresh token để lưu trữ an toàn
   */
  private hashToken(token: string): string {
    return crypto.createHash('sha256').update(token).digest('hex');
  }

  /**
   * EN: Register new user
   * VI: Đăng ký người dùng mới
   */
  async register(data: {
    email: string;
    password: string;
    username?: string;
  }, req: Request): Promise<AuthResponse> {
    // Check if user exists
    const existing = await this.prisma.user.findUnique({
      where: { email: data.email },
    });

    if (existing) {
      throw new Error('User already exists');
    }

    // Hash password
    const passwordHash = await bcrypt.hash(data.password, 12);

    // Create user
    const user = await this.prisma.user.create({
      data: {
        email: data.email,
        username: data.username || data.email.split('@')[0],
        passwordHash,
        isActive: true,
        emailVerified: false,
      },
    });

    // Assign default role (USER)
    const defaultRole = await this.prisma.role.findFirst({
      where: { name: 'USER' },
    });

    if (defaultRole) {
      await rbacService.assignRole(user.id, defaultRole.id);
    }

    // Generate tokens
    const roles = await rbacService.getUserRoles(user.id);
    const permissions = await rbacService.getUserPermissions(user.id);

    const tokens = await jwtService.generateTokenSet(
      {
        id: user.id,
        email: user.email,
        emailVerified: user.emailVerified,
        username: user.username || undefined,
        updatedAt: user.updatedAt,
      },
      roles,
      permissions
    );

    // Store refresh token in database
    const decodedRefreshToken = jwtService.verifyRefreshToken(tokens.refreshToken);
    const deviceId = 'test-device-id'; // cookieService.getDeviceFingerprint(req);
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days

    await this.prisma.refreshToken.create({
      data: {
        userId: user.id,
        token: this.hashToken(tokens.refreshToken),
        family: decodedRefreshToken.tokenId,
        deviceId,
        ipAddress: req.ip || req.socket.remoteAddress || null,
        userAgent: req.headers['user-agent'] || null,
        expiresAt,
      },
    });

    // Create session
    await sessionService.createSession(user.id, req);

    // Audit log
    await auditService.logFromRequest(req, 'USER_REGISTERED', {
      userId: user.id,
      success: true,
    });

    logger.info('User registered', { userId: user.id, email: user.email });

    return {
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        emailVerified: user.emailVerified,
        isActive: user.isActive,
        mfaEnabled: user.mfaEnabled,
      },
      tokens,
    };
  }

  /**
   * EN: Login user
   * VI: Đăng nhập người dùng
   */
  async login(data: { email: string; password: string }, req: Request): Promise<AuthResponse | { user: UserResponse; tokens: { accessToken: string; refreshToken: string } }> {
    // Find user
    const user = await this.prisma.user.findUnique({
      where: { email: data.email },
    });

    if (!user || !user.isActive) {
      await auditService.logFromRequest(req, 'LOGIN_FAILED', {
        success: false,
        errorMessage: 'Invalid credentials',
      });
      throw new Error('Invalid credentials');
    }

    // Check if account is locked
    const isLocked = await accountLockoutService.isAccountLocked(user.id);
    if (isLocked) {
      const lockoutStatus = await accountLockoutService.getLockoutStatus(user.id);
      await auditService.logFromRequest(req, 'LOGIN_FAILED', {
        userId: user.id,
        success: false,
        errorMessage: 'Account locked due to too many failed attempts',
        remainingMinutes: lockoutStatus.remainingMinutes,
      });
      throw new Error(`Account is locked. Try again in ${lockoutStatus.remainingMinutes} minutes.`);
    }

    // Verify password
    if (!user.passwordHash) {
      await auditService.logFromRequest(req, 'LOGIN_FAILED', {
        userId: user.id,
        success: false,
        errorMessage: 'Password not set',
      });
      throw new Error('Invalid credentials');
    }

    const isValid = await bcrypt.compare(data.password, user.passwordHash);
    if (!isValid) {
      // Record failed attempt
      await accountLockoutService.recordFailedAttempt(user.id);

      await auditService.logFromRequest(req, 'LOGIN_FAILED', {
        userId: user.id,
        success: false,
        errorMessage: 'Invalid password',
      });
      throw new Error('Invalid credentials');
    }

    // Check MFA
    if (user.mfaEnabled) {
      // MFA required - return special response
      return {
        user: {
          id: user.id,
          email: user.email,
          username: user.username,
          emailVerified: user.emailVerified,
          isActive: user.isActive,
          mfaEnabled: user.mfaEnabled,
          mfaRequired: true,
        },
        tokens: { accessToken: '', refreshToken: '' },
      };
    }

    // Update login info
    await this.prisma.user.update({
      where: { id: user.id },
      data: {
        lastLoginAt: new Date(),
        loginCount: { increment: 1 },
      },
    });

    // Clear failed login attempts on successful login
    await accountLockoutService.clearFailedAttempts(user.id);

    // Generate tokens
    const roles = await rbacService.getUserRoles(user.id);
    const permissions = await rbacService.getUserPermissions(user.id);

    const tokens = await jwtService.generateTokenSet(
      {
        id: user.id,
        email: user.email,
        emailVerified: user.emailVerified,
        username: user.username || undefined,
        updatedAt: user.updatedAt,
      },
      roles,
      permissions
    );

    // Store refresh token in database
    const decodedRefreshToken = jwtService.verifyRefreshToken(tokens.refreshToken);
    const deviceId = 'test-device-id'; // cookieService.getDeviceFingerprint(req);
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days

    await this.prisma.refreshToken.create({
      data: {
        userId: user.id,
        token: this.hashToken(tokens.refreshToken),
        family: decodedRefreshToken.tokenId,
        deviceId,
        ipAddress: req.ip || req.socket.remoteAddress || null,
        userAgent: req.headers['user-agent'] || null,
        expiresAt,
      },
    });

    // Create session
    await sessionService.createSession(user.id, req);

    // Audit log
    await auditService.logFromRequest(req, 'LOGIN_SUCCESS', {
      userId: user.id,
      success: true,
    });

    logger.info('User logged in', { userId: user.id, email: user.email });

    return {
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        emailVerified: user.emailVerified,
        isActive: user.isActive,
        mfaEnabled: user.mfaEnabled,
      },
      tokens,
    };
  }

  /**
   * EN: Logout user
   * VI: Đăng xuất người dùng
   */
  async logout(userId: string, sessionId?: string, req?: Request): Promise<void> {
    if (sessionId) {
      await sessionService.revokeSession(sessionId);
    } else {
      await sessionService.revokeAllUserSessions(userId);
    }

    if (req) {
      await auditService.logFromRequest(req, 'LOGOUT', {
        userId,
        success: true,
      });
    }

    logger.info('User logged out', { userId });
  }

  /**
   * EN: Refresh access token
   * VI: Làm mới access token
   */
  async refreshToken(refreshToken: string, req: Request): Promise<RefreshTokenResponse> {
    // Verify refresh token
    const decoded = jwtService.verifyRefreshToken(refreshToken);

    // Check if refresh token exists in DB (compare hashes for security)
    const tokenRecord = await this.prisma.refreshToken.findFirst({
      where: {
        token: this.hashToken(refreshToken),
        userId: decoded.sub,
        expiresAt: { gt: new Date() },
        revokedAt: null,
      },
      include: { user: true },
    });

    if (!tokenRecord) {
      throw new Error('Invalid refresh token');
    }

    // Verify tokenId matches family (token rotation security)
    if (tokenRecord.family && decoded.tokenId !== tokenRecord.family) {
      // Token family mismatch - possible token reuse attack
      // Revoke all tokens in this family
      await this.prisma.refreshToken.updateMany({
        where: {
          userId: decoded.sub,
          family: tokenRecord.family,
        },
        data: {
          revokedAt: new Date(),
        },
      });
      throw new Error('Invalid refresh token - token family mismatch');
    }

    // Generate new access token
    const roles = await rbacService.getUserRoles(decoded.sub);
    const permissions = await rbacService.getUserPermissions(decoded.sub);

    const accessToken = jwtService.generateAccessToken({
      sub: decoded.sub,
      email: tokenRecord.user.email,
      roles,
      permissions,
    });

    // Rotate refresh token for security
    const newRefreshToken = jwtService.generateRefreshToken(decoded.sub);
    const newRefreshTokenDecoded = jwtService.verifyRefreshToken(newRefreshToken);
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days

    // Revoke old token and create new one
    await this.prisma.$transaction([
      this.prisma.refreshToken.update({
        where: { id: tokenRecord.id },
        data: {
          revokedAt: new Date(),
        },
      }),
      this.prisma.refreshToken.create({
        data: {
          userId: decoded.sub,
          token: this.hashToken(newRefreshToken),
          family: tokenRecord.family || newRefreshTokenDecoded.tokenId, // Keep same family
          deviceId: tokenRecord.deviceId,
          ipAddress: req.ip || req.socket.remoteAddress || null,
          userAgent: req.headers['user-agent'] || null,
          expiresAt,
        },
      }),
    ]);

    // Audit log
    await auditService.logFromRequest(req, 'TOKEN_REFRESHED', {
      userId: decoded.sub,
      success: true,
    });

    return { accessToken, refreshToken: newRefreshToken };
  }

  /**
   * EN: Verify MFA token (TOTP or backup code)
   * VI: Xác thực token MFA (TOTP hoặc mã backup)
   */
  async verifyMFA(userId: string, token: string, req: Request): Promise<AuthResponse> {
    // Try TOTP first
    const totpValid = await mfaService.verifyTOTP(userId, token);

    if (totpValid) {
      await auditService.logFromRequest(req, 'MFA_VERIFIED', {
        userId,
        method: 'TOTP',
        success: true,
      });

      // Complete login flow
      return this.completeLoginAfterMFA(userId, req);
    }

    // Try backup code
    const backupCodeValid = await mfaService.validateBackupCode(userId, token);

    if (backupCodeValid) {
      await auditService.logFromRequest(req, 'MFA_VERIFIED', {
        userId,
        method: 'BACKUP_CODE',
        success: true,
      });

      // Complete login flow
      return this.completeLoginAfterMFA(userId, req);
    }

    // Neither TOTP nor backup code worked
    await auditService.logFromRequest(req, 'MFA_FAILED', {
      userId,
      success: false,
      errorMessage: 'Invalid MFA token',
    });

    throw new Error('Invalid MFA token');
  }

  /**
   * EN: Complete login after MFA verification
   * VI: Hoàn thành đăng nhập sau khi xác thực MFA
   */
  private async completeLoginAfterMFA(userId: string, req: Request): Promise<AuthResponse> {
    // Get user
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
    });

    if (!user) {
      throw new Error('User not found');
    }

    // Update login info
    await this.prisma.user.update({
      where: { id: userId },
      data: {
        lastLoginAt: new Date(),
        loginCount: { increment: 1 },
      },
    });

    // Generate tokens
    const roles = await rbacService.getUserRoles(userId);
    const permissions = await rbacService.getUserPermissions(userId);

    const tokens = await jwtService.generateTokenSet(
      {
        id: user.id,
        email: user.email,
        emailVerified: user.emailVerified,
        username: user.username || undefined,
        updatedAt: user.updatedAt,
      },
      roles,
      permissions
    );

    // Store refresh token in database (hashed for security)
    const decodedRefreshToken = jwtService.verifyRefreshToken(tokens.refreshToken);
    const deviceId = 'test-device-id'; // cookieService.getDeviceFingerprint(req);
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days

    await this.prisma.refreshToken.create({
      data: {
        userId: user.id,
        token: this.hashToken(tokens.refreshToken),
        family: decodedRefreshToken.tokenId,
        deviceId,
        ipAddress: req.ip || req.socket.remoteAddress || null,
        userAgent: req.headers['user-agent'] || null,
        expiresAt,
      },
    });

    // Create session
    await sessionService.createSession(userId, req);

    // Clear failed login attempts on successful login
    await accountLockoutService.clearFailedAttempts(userId);

    // Audit log
    await auditService.logFromRequest(req, 'LOGIN_SUCCESS', {
      userId,
      success: true,
    });

    logger.info('User logged in with MFA', { userId, email: user.email });

    return {
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        emailVerified: user.emailVerified,
        isActive: user.isActive,
        mfaEnabled: user.mfaEnabled,
      },
      tokens,
    };
  }
}

export const authService = new AuthService();
