/**
 * Biometric Credential Service
 * ERP Construccion - Modulo Biometrics
 *
 * Logica de negocio para gestion de credenciales biometricas (huella, Face ID, etc).
 */

import { Repository, DataSource, IsNull, LessThan } from 'typeorm';
import { BiometricCredential } from '../entities/biometric-credential.entity';
import { BiometricType } from '../entities/device.entity';

// DTOs
export interface RegisterCredentialDto {
  deviceId: string;
  userId: string;
  biometricType: BiometricType;
  credentialId: string;
  publicKey: string;
  algorithm?: string;
  credentialName?: string;
  isPrimary?: boolean;
}

export interface UpdateCredentialDto {
  credentialName?: string;
  isPrimary?: boolean;
  isActive?: boolean;
}

export interface VerifyCredentialDto {
  credentialId: string;
  signature: string;
  challenge: string;
}

export interface PaginationOptions {
  page: number;
  limit: number;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

// Security constants
const MAX_FAILED_ATTEMPTS = 5;
const LOCKOUT_DURATION_MINUTES = 15;

export class BiometricCredentialService {
  private credentialRepository: Repository<BiometricCredential>;

  constructor(dataSource: DataSource) {
    this.credentialRepository = dataSource.getRepository(BiometricCredential);
  }

  // ============================================
  // CREDENTIAL MANAGEMENT
  // ============================================

  /**
   * Register a new biometric credential
   */
  async register(dto: RegisterCredentialDto): Promise<BiometricCredential> {
    // Check if credential already exists
    const existing = await this.credentialRepository.findOne({
      where: { deviceId: dto.deviceId, credentialId: dto.credentialId },
    });

    if (existing) {
      throw new Error('Credential already registered on this device');
    }

    // If this is primary, unset other primary credentials for this user/device
    if (dto.isPrimary) {
      await this.credentialRepository.update(
        { deviceId: dto.deviceId, userId: dto.userId, isPrimary: true },
        { isPrimary: false }
      );
    }

    const credential = this.credentialRepository.create({
      deviceId: dto.deviceId,
      userId: dto.userId,
      biometricType: dto.biometricType,
      credentialId: dto.credentialId,
      publicKey: dto.publicKey,
      algorithm: dto.algorithm || 'ES256',
      credentialName: dto.credentialName,
      isPrimary: dto.isPrimary || false,
      isActive: true,
      useCount: 0,
      failedAttempts: 0,
    });

    return this.credentialRepository.save(credential);
  }

  /**
   * Find credential by ID
   */
  async findById(id: string): Promise<BiometricCredential | null> {
    return this.credentialRepository.findOne({
      where: { id },
      relations: ['device'],
    });
  }

  /**
   * Find credential by credential ID (public key identifier)
   */
  async findByCredentialId(credentialId: string): Promise<BiometricCredential | null> {
    return this.credentialRepository.findOne({
      where: { credentialId, isActive: true, deletedAt: IsNull() },
      relations: ['device'],
    });
  }

  /**
   * Get all credentials for a device
   */
  async findByDevice(deviceId: string): Promise<BiometricCredential[]> {
    return this.credentialRepository.find({
      where: { deviceId, isActive: true, deletedAt: IsNull() },
      order: { isPrimary: 'DESC', createdAt: 'DESC' },
    });
  }

  /**
   * Get all credentials for a user
   */
  async findByUser(
    userId: string,
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<BiometricCredential>> {
    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await this.credentialRepository.findAndCount({
      where: { userId, deletedAt: IsNull() },
      relations: ['device'],
      order: { isPrimary: 'DESC', lastUsedAt: 'DESC' },
      skip,
      take: pagination.limit,
    });

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Get primary credential for device
   */
  async getPrimaryCredential(deviceId: string): Promise<BiometricCredential | null> {
    return this.credentialRepository.findOne({
      where: { deviceId, isPrimary: true, isActive: true, deletedAt: IsNull() },
    });
  }

  /**
   * Update credential
   */
  async update(id: string, dto: UpdateCredentialDto): Promise<BiometricCredential | null> {
    const credential = await this.findById(id);
    if (!credential) return null;

    // If setting as primary, unset other primary credentials
    if (dto.isPrimary === true) {
      await this.credentialRepository.update(
        { deviceId: credential.deviceId, userId: credential.userId, isPrimary: true },
        { isPrimary: false }
      );
    }

    Object.assign(credential, dto);
    return this.credentialRepository.save(credential);
  }

  /**
   * Soft delete credential
   */
  async delete(id: string): Promise<boolean> {
    const result = await this.credentialRepository.update(
      { id },
      { deletedAt: new Date(), isActive: false }
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Delete all credentials for device
   */
  async deleteByDevice(deviceId: string): Promise<number> {
    const result = await this.credentialRepository.update(
      { deviceId },
      { deletedAt: new Date(), isActive: false }
    );
    return result.affected ?? 0;
  }

  // ============================================
  // AUTHENTICATION
  // ============================================

  /**
   * Check if credential is locked
   */
  async isLocked(credentialId: string): Promise<boolean> {
    const credential = await this.findByCredentialId(credentialId);
    if (!credential) return false;

    if (credential.lockedUntil && credential.lockedUntil > new Date()) {
      return true;
    }

    return false;
  }

  /**
   * Record successful authentication
   */
  async recordSuccess(credentialId: string): Promise<BiometricCredential | null> {
    const credential = await this.findByCredentialId(credentialId);
    if (!credential) return null;

    credential.useCount += 1;
    credential.lastUsedAt = new Date();
    credential.failedAttempts = 0;
    credential.lockedUntil = undefined as any;

    return this.credentialRepository.save(credential);
  }

  /**
   * Record failed authentication attempt
   */
  async recordFailure(credentialId: string): Promise<{
    credential: BiometricCredential | null;
    isLocked: boolean;
    remainingAttempts: number;
    lockoutUntil?: Date;
  }> {
    const credential = await this.findByCredentialId(credentialId);
    if (!credential) {
      return { credential: null, isLocked: false, remainingAttempts: 0 };
    }

    credential.failedAttempts += 1;

    let isLocked = false;
    let lockoutUntil: Date | undefined;
    let remainingAttempts = MAX_FAILED_ATTEMPTS - credential.failedAttempts;

    if (credential.failedAttempts >= MAX_FAILED_ATTEMPTS) {
      isLocked = true;
      lockoutUntil = new Date(Date.now() + LOCKOUT_DURATION_MINUTES * 60 * 1000);
      credential.lockedUntil = lockoutUntil;
      remainingAttempts = 0;
    }

    await this.credentialRepository.save(credential);

    return {
      credential,
      isLocked,
      remainingAttempts,
      lockoutUntil,
    };
  }

  /**
   * Reset failed attempts (admin function)
   */
  async resetFailedAttempts(id: string): Promise<boolean> {
    const result = await this.credentialRepository.update(
      { id },
      { failedAttempts: 0, lockedUntil: undefined as any }
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Unlock all locked credentials (for scheduled cleanup)
   */
  async unlockExpiredLockouts(): Promise<number> {
    const result = await this.credentialRepository.update(
      { lockedUntil: LessThan(new Date()) },
      { lockedUntil: undefined as any, failedAttempts: 0 }
    );
    return result.affected ?? 0;
  }

  // ============================================
  // STATISTICS
  // ============================================

  /**
   * Get credential statistics for user
   */
  async getUserStats(userId: string): Promise<{
    totalCredentials: number;
    activeCredentials: number;
    byType: Record<string, number>;
    totalUseCount: number;
    lockedCredentials: number;
  }> {
    const [
      totalCredentials,
      activeCredentials,
      byTypeRaw,
      useCountResult,
      lockedCredentials,
    ] = await Promise.all([
      this.credentialRepository.count({ where: { userId, deletedAt: IsNull() } }),
      this.credentialRepository.count({ where: { userId, isActive: true, deletedAt: IsNull() } }),

      this.credentialRepository.createQueryBuilder('cred')
        .select('cred.biometric_type', 'type')
        .addSelect('COUNT(*)', 'count')
        .where('cred.user_id = :userId', { userId })
        .andWhere('cred.deleted_at IS NULL')
        .groupBy('cred.biometric_type')
        .getRawMany(),

      this.credentialRepository.createQueryBuilder('cred')
        .select('SUM(cred.use_count)', 'total')
        .where('cred.user_id = :userId', { userId })
        .andWhere('cred.deleted_at IS NULL')
        .getRawOne(),

      this.credentialRepository.createQueryBuilder('cred')
        .where('cred.user_id = :userId', { userId })
        .andWhere('cred.deleted_at IS NULL')
        .andWhere('cred.locked_until > :now', { now: new Date() })
        .getCount(),
    ]);

    const byType: Record<string, number> = {};
    byTypeRaw.forEach((row: any) => {
      byType[row.type] = parseInt(row.count, 10);
    });

    return {
      totalCredentials,
      activeCredentials,
      byType,
      totalUseCount: parseInt(useCountResult?.total) || 0,
      lockedCredentials,
    };
  }
}
