/**
 * AuditLogService - Gestión de Logs de Auditoría
 *
 * Registra y consulta eventos de auditoría para trazabilidad.
 *
 * @module Admin
 */

import { Repository } from 'typeorm';
import { AuditLog, AuditCategory, AuditAction, AuditSeverity } from '../entities/audit-log.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

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

export interface CreateAuditLogDto {
  userId?: string;
  category: AuditCategory;
  action: AuditAction;
  severity?: AuditSeverity;
  entityType?: string;
  entityId?: string;
  entityName?: string;
  description: string;
  oldValues?: Record<string, any>;
  newValues?: Record<string, any>;
  changedFields?: string[];
  ipAddress?: string;
  userAgent?: string;
  requestId?: string;
  sessionId?: string;
  module?: string;
  endpoint?: string;
  httpMethod?: string;
  responseStatus?: number;
  durationMs?: number;
  isSuccess?: boolean;
  errorMessage?: string;
  metadata?: Record<string, any>;
}

export interface AuditLogFilters {
  userId?: string;
  category?: AuditCategory;
  action?: AuditAction;
  severity?: AuditSeverity;
  entityType?: string;
  entityId?: string;
  module?: string;
  isSuccess?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
  ipAddress?: string;
  search?: string;
}

export class AuditLogService {
  constructor(private readonly repository: Repository<AuditLog>) {}

  /**
   * Crear registro de auditoría
   */
  async log(ctx: ServiceContext, data: CreateAuditLogDto): Promise<AuditLog> {
    // Determinar retención basada en severidad
    let retentionDays = 90; // Default for operational logs
    if (data.severity === 'critical' || data.category === 'critical_operation') {
      retentionDays = 1825; // 5 years for critical
    } else if (data.severity === 'high') {
      retentionDays = 365; // 1 year for high severity
    }

    const log = this.repository.create({
      tenantId: ctx.tenantId,
      ...data,
      severity: data.severity || 'medium',
      isSuccess: data.isSuccess ?? true,
      retentionDays,
    });

    return this.repository.save(log);
  }

  /**
   * Buscar logs con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: AuditLogFilters,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<AuditLog>> {
    const qb = this.repository
      .createQueryBuilder('al')
      .leftJoinAndSelect('al.user', 'u')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('al.user_id = :userId', { userId: filters.userId });
    }
    if (filters.category) {
      qb.andWhere('al.category = :category', { category: filters.category });
    }
    if (filters.action) {
      qb.andWhere('al.action = :action', { action: filters.action });
    }
    if (filters.severity) {
      qb.andWhere('al.severity = :severity', { severity: filters.severity });
    }
    if (filters.entityType) {
      qb.andWhere('al.entity_type = :entityType', { entityType: filters.entityType });
    }
    if (filters.entityId) {
      qb.andWhere('al.entity_id = :entityId', { entityId: filters.entityId });
    }
    if (filters.module) {
      qb.andWhere('al.module = :module', { module: filters.module });
    }
    if (filters.isSuccess !== undefined) {
      qb.andWhere('al.is_success = :isSuccess', { isSuccess: filters.isSuccess });
    }
    if (filters.dateFrom) {
      qb.andWhere('al.created_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('al.created_at <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.ipAddress) {
      qb.andWhere('al.ip_address = :ipAddress', { ipAddress: filters.ipAddress });
    }
    if (filters.search) {
      qb.andWhere(
        '(al.description ILIKE :search OR al.entity_name ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (page - 1) * limit;
    qb.orderBy('al.created_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

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

  /**
   * Obtener logs por entidad
   */
  async findByEntity(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<AuditLog>> {
    return this.findWithFilters(ctx, { entityType, entityId }, page, limit);
  }

  /**
   * Obtener logs por usuario
   */
  async findByUser(
    ctx: ServiceContext,
    userId: string,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<AuditLog>> {
    return this.findWithFilters(ctx, { userId }, page, limit);
  }

  /**
   * Obtener logs críticos recientes
   */
  async getCriticalLogs(
    ctx: ServiceContext,
    days = 7,
    limit = 100
  ): Promise<AuditLog[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    return this.repository
      .createQueryBuilder('al')
      .leftJoinAndSelect('al.user', 'u')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.severity IN (:...severities)', { severities: ['high', 'critical'] })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .orderBy('al.created_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Obtener logs fallidos
   */
  async getFailedLogs(
    ctx: ServiceContext,
    hours = 24,
    limit = 100
  ): Promise<AuditLog[]> {
    const dateFrom = new Date();
    dateFrom.setHours(dateFrom.getHours() - hours);

    return this.repository
      .createQueryBuilder('al')
      .leftJoinAndSelect('al.user', 'u')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.is_success = false')
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .orderBy('al.created_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Limpiar logs expirados
   */
  async cleanupExpiredLogs(ctx: ServiceContext): Promise<number> {
    const result = await this.repository
      .createQueryBuilder()
      .delete()
      .from(AuditLog)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere("created_at < NOW() - (retention_days || ' days')::interval")
      .execute();

    return result.affected || 0;
  }

  /**
   * Obtener estadísticas
   */
  async getStats(ctx: ServiceContext, days = 30): Promise<AuditLogStats> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const qb = this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom });

    // Total count
    const total = await qb.getCount();

    // By category
    const byCategory = await this.repository
      .createQueryBuilder('al')
      .select('al.category', 'category')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.category')
      .getRawMany();

    // By action
    const byAction = await this.repository
      .createQueryBuilder('al')
      .select('al.action', 'action')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.action')
      .getRawMany();

    // Success/failure
    const successCount = await this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .andWhere('al.is_success = true')
      .getCount();

    // By severity
    const bySeverity = await this.repository
      .createQueryBuilder('al')
      .select('al.severity', 'severity')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.severity')
      .getRawMany();

    return {
      period: { days, from: dateFrom, to: new Date() },
      total,
      successCount,
      failureCount: total - successCount,
      successRate: total > 0 ? (successCount / total) * 100 : 0,
      byCategory: byCategory.map((r) => ({ category: r.category, count: parseInt(r.count) })),
      byAction: byAction.map((r) => ({ action: r.action, count: parseInt(r.count) })),
      bySeverity: bySeverity.map((r) => ({ severity: r.severity, count: parseInt(r.count) })),
    };
  }
}

export interface AuditLogStats {
  period: { days: number; from: Date; to: Date };
  total: number;
  successCount: number;
  failureCount: number;
  successRate: number;
  byCategory: { category: AuditCategory; count: number }[];
  byAction: { action: AuditAction; count: number }[];
  bySeverity: { severity: AuditSeverity; count: number }[];
}
