import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../database/prisma.service';
import { LoggerService } from './logger.service';

export interface AuditEvent {
  action: string;
  resource: string;
  resourceId?: string;
  userId?: string;
  userEmail?: string;
  userRole?: string;
  ip?: string;
  userAgent?: string;
  requestId?: string;
  oldValues?: Record<string, any>;
  newValues?: Record<string, any>;
  metadata?: Record<string, any>;
  severity?: 'low' | 'medium' | 'high' | 'critical';
  category?: 'authentication' | 'authorization' | 'data_access' | 'data_modification' | 'system' | 'security';
}

export interface AuditQuery {
  userId?: string;
  action?: string;
  resource?: string;
  category?: string;
  severity?: string;
  startDate?: Date;
  endDate?: Date;
  ip?: string;
  page?: number;
  limit?: number;
}

@Injectable()
export class AuditService {
  private readonly logger = new Logger(AuditService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly loggerService: LoggerService
  ) {}

  /**
   * Registra un evento de auditoría
   */
  async logAuditEvent(event: AuditEvent): Promise<void> {
    try {
      // Guardar en base de datos
      await this.prisma.auditLog.create({
        data: {
          action: event.action,
          resource: event.resource,
          resourceId: event.resourceId,
          userId: event.userId,
          userEmail: event.userEmail,
          userRole: event.userRole,
          ip: event.ip,
          userAgent: event.userAgent,
          requestId: event.requestId,
          oldValues: event.oldValues ? JSON.stringify(event.oldValues) : null,
          newValues: event.newValues ? JSON.stringify(event.newValues) : null,
          metadata: event.metadata ? JSON.stringify(event.metadata) : null,
          severity: event.severity || 'medium',
          category: event.category || 'system',
          timestamp: new Date(),
        },
      });

      // Log estructurado para análisis
      this.loggerService.logAudit(event.action, {
        resource: event.resource,
        resourceId: event.resourceId,
        userId: event.userId,
        userEmail: event.userEmail,
        userRole: event.userRole,
        ip: event.ip,
        requestId: event.requestId,
        severity: event.severity,
        category: event.category,
        hasOldValues: !!event.oldValues,
        hasNewValues: !!event.newValues,
        metadata: event.metadata,
      });

      // Log de seguridad para eventos críticos
      if (event.severity === 'critical' || event.severity === 'high') {
        this.loggerService.logSecurity(
          event.action,
          event.severity,
          {
            resource: event.resource,
            resourceId: event.resourceId,
            userId: event.userId,
            ip: event.ip,
            category: event.category,
            metadata: event.metadata,
          }
        );
      }

    } catch (error) {
      this.logger.error('Error al registrar evento de auditoría', {
        error: error.message,
        stack: error.stack,
        event,
      });
      
      // Fallback: al menos loguear el evento
      this.loggerService.logAudit(`audit_error_${event.action}`, {
        originalEvent: event,
        error: error.message,
      });
    }
  }

  /**
   * Busca eventos de auditoría con filtros
   */
  async searchAuditEvents(query: AuditQuery) {
    const {
      userId,
      action,
      resource,
      category,
      severity,
      startDate,
      endDate,
      ip,
      page = 1,
      limit = 50,
    } = query;

    const where: any = {};

    if (userId) where.userId = userId;
    if (action) where.action = { contains: action, mode: 'insensitive' };
    if (resource) where.resource = { contains: resource, mode: 'insensitive' };
    if (category) where.category = category;
    if (severity) where.severity = severity;
    if (ip) where.ip = ip;
    
    if (startDate || endDate) {
      where.timestamp = {};
      if (startDate) where.timestamp.gte = startDate;
      if (endDate) where.timestamp.lte = endDate;
    }

    const [events, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where,
        orderBy: { timestamp: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.auditLog.count({ where }),
    ]);

    return {
      events: events.map(event => ({
        ...event,
        oldValues: event.oldValues ? JSON.parse(event.oldValues) : null,
        newValues: event.newValues ? JSON.parse(event.newValues) : null,
        metadata: event.metadata ? JSON.parse(event.metadata) : null,
      })),
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Obtiene estadísticas de auditoría
   */
  async getAuditStatistics(startDate?: Date, endDate?: Date) {
    const where: any = {};
    
    if (startDate || endDate) {
      where.timestamp = {};
      if (startDate) where.timestamp.gte = startDate;
      if (endDate) where.timestamp.lte = endDate;
    }

    const [totalEvents, eventsByCategory, eventsBySeverity, eventsByAction] = await Promise.all([
      this.prisma.auditLog.count({ where }),
      this.prisma.auditLog.groupBy({
        by: ['category'],
        where,
        _count: { category: true },
      }),
      this.prisma.auditLog.groupBy({
        by: ['severity'],
        where,
        _count: { severity: true },
      }),
      this.prisma.auditLog.groupBy({
        by: ['action'],
        where,
        _count: { action: true },
        orderBy: { _count: { action: 'desc' } },
        take: 10,
      }),
    ]);

    return {
      totalEvents,
      byCategory: eventsByCategory.reduce((acc, item) => {
        acc[item.category] = item._count.category;
        return acc;
      }, {}),
      bySeverity: eventsBySeverity.reduce((acc, item) => {
        acc[item.severity] = item._count.severity;
        return acc;
      }, {}),
      topActions: eventsByAction.map(item => ({
        action: item.action,
        count: item._count.action,
      })),
    };
  }

  /**
   * Métodos de conveniencia para eventos comunes
   */

  async logUserLogin(userId: string, userEmail: string, ip: string, userAgent: string, requestId?: string) {
    await this.logAuditEvent({
      action: 'user_login',
      resource: 'authentication',
      userId,
      userEmail,
      ip,
      userAgent,
      requestId,
      category: 'authentication',
      severity: 'low',
    });
  }

  async logUserLogout(userId: string, userEmail: string, ip: string, requestId?: string) {
    await this.logAuditEvent({
      action: 'user_logout',
      resource: 'authentication',
      userId,
      userEmail,
      ip,
      requestId,
      category: 'authentication',
      severity: 'low',
    });
  }

  async logFailedLogin(email: string, ip: string, userAgent: string, reason: string, requestId?: string) {
    await this.logAuditEvent({
      action: 'failed_login',
      resource: 'authentication',
      userEmail: email,
      ip,
      userAgent,
      requestId,
      category: 'authentication',
      severity: 'medium',
      metadata: { reason },
    });
  }

  async logDataAccess(resource: string, resourceId: string, userId: string, action: string, requestId?: string) {
    await this.logAuditEvent({
      action: `data_access_${action}`,
      resource,
      resourceId,
      userId,
      requestId,
      category: 'data_access',
      severity: 'low',
    });
  }

  async logDataModification(
    resource: string,
    resourceId: string,
    userId: string,
    action: 'create' | 'update' | 'delete',
    oldValues?: Record<string, any>,
    newValues?: Record<string, any>,
    requestId?: string
  ) {
    await this.logAuditEvent({
      action: `data_${action}`,
      resource,
      resourceId,
      userId,
      oldValues,
      newValues,
      requestId,
      category: 'data_modification',
      severity: action === 'delete' ? 'high' : 'medium',
    });
  }

  async logSecurityEvent(
    action: string,
    severity: 'low' | 'medium' | 'high' | 'critical',
    userId?: string,
    ip?: string,
    metadata?: Record<string, any>,
    requestId?: string
  ) {
    await this.logAuditEvent({
      action,
      resource: 'security',
      userId,
      ip,
      metadata,
      requestId,
      category: 'security',
      severity,
    });
  }

  async logSystemEvent(
    action: string,
    metadata?: Record<string, any>,
    severity: 'low' | 'medium' | 'high' | 'critical' = 'low'
  ) {
    await this.logAuditEvent({
      action,
      resource: 'system',
      metadata,
      category: 'system',
      severity,
    });
  }

  /**
   * Limpia eventos de auditoría antiguos
   */
  async cleanupOldAuditEvents(daysToKeep: number = 90): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

    const result = await this.prisma.auditLog.deleteMany({
      where: {
        timestamp: {
          lt: cutoffDate,
        },
      },
    });

    this.logger.log(`Limpieza de auditoría: ${result.count} eventos eliminados (anteriores a ${cutoffDate.toISOString()})`);
    
    await this.logSystemEvent('audit_cleanup', {
      deletedCount: result.count,
      cutoffDate: cutoffDate.toISOString(),
      daysToKeep,
    });

    return result.count;
  }

  /**
   * Exporta eventos de auditoría para análisis externo
   */
  async exportAuditEvents(query: AuditQuery, format: 'json' | 'csv' = 'json') {
    const { events } = await this.searchAuditEvents({ ...query, limit: 10000 });
    
    if (format === 'csv') {
      // Implementar conversión a CSV si es necesario
      const headers = [
        'timestamp', 'action', 'resource', 'resourceId', 'userId', 
        'userEmail', 'ip', 'category', 'severity'
      ];
      
      const csvData = events.map(event => 
        headers.map(header => event[header] || '').join(',')
      );
      
      return [headers.join(','), ...csvData].join('\n');
    }
    
    return events;
  }
}