import { Injectable, LoggerService } from '@nestjs/common';
import { createLogger, Logger, format, transports } from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
import { PrismaService } from '../database/prisma.service';
{{#if hasSentry}}
import * as Sentry from '@sentry/node';
{{/if}}

export interface LogContext {
  userId?: string;
  requestId?: string;
  action?: string;
  resource?: string;
  resourceId?: string;
  ipAddress?: string;
  userAgent?: string;
  metadata?: any;
}

export interface AuditLogData {
  userId?: string;
  action: string;
  resource: string;
  resourceId?: string;
  oldValues?: any;
  newValues?: any;
  ipAddress?: string;
  userAgent?: string;
  metadata?: any;
}

@Injectable()
export class LoggerService implements LoggerService {
  private logger: Logger;
  private auditLogger: Logger;

  constructor(
    {{#if enableDatabaseLog}}
    private readonly prisma: PrismaService,
    {{/if}}
  ) {
    this.initializeLoggers();
  }

  private initializeLoggers() {
    // Configuración del logger principal
    this.logger = createLogger({
      level: '{{config.logLevel}}',
      format: format.combine(
        format.timestamp(),
        format.errors({ stack: true }),
        format.json()
      ),
      defaultMeta: { service: 'backend-api' },
      transports: this.createTransports()
    });

    // Logger específico para auditoría
    this.auditLogger = createLogger({
      level: 'info',
      format: format.combine(
        format.timestamp(),
        format.json()
      ),
      transports: [
        {{#if config.enableFileLog}}
        new DailyRotateFile({
          filename: '{{config.logFilePath}}/audit-%DATE%.log',
          datePattern: '{{config.logDatePattern}}',
          maxSize: '{{config.logMaxSize}}',
          maxFiles: '{{config.logMaxFiles}}',
          format: format.combine(
            format.timestamp(),
            format.json()
          )
        }),
        {{/if}}
        {{#if config.enableConsoleLog}}
        new transports.Console({
          format: format.combine(
            format.colorize(),
            format.simple()
          )
        })
        {{/if}}
      ]
    });
  }

  private createTransports(): any[] {
    const transportsList = [];

    {{#if config.enableConsoleLog}}
    // Console transport
    transportsList.push(
      new transports.Console({
        format: format.combine(
          format.colorize(),
          format.simple()
        )
      })
    );
    {{/if}}

    {{#if config.enableFileLog}}
    // File transport con rotación diaria
    transportsList.push(
      new DailyRotateFile({
        filename: '{{config.logFilePath}}/application-%DATE%.log',
        datePattern: '{{config.logDatePattern}}',
        maxSize: '{{config.logMaxSize}}',
        maxFiles: '{{config.logMaxFiles}}'
      })
    );

    // Error file transport
    transportsList.push(
      new DailyRotateFile({
        filename: '{{config.logFilePath}}/error-%DATE%.log',
        datePattern: '{{config.logDatePattern}}',
        level: 'error',
        maxSize: '{{config.logMaxSize}}',
        maxFiles: '{{config.logMaxFiles}}'
      })
    );
    {{/if}}

    return transportsList;
  }

  // Métodos del LoggerService de NestJS
  log(message: any, context?: string) {
    this.info(message, { context });
  }

  error(message: any, trace?: string, context?: string) {
    this.logError(message, { trace, context });
  }

  warn(message: any, context?: string) {
    this.warning(message, { context });
  }

  debug(message: any, context?: string) {
    this.logDebug(message, { context });
  }

  verbose(message: any, context?: string) {
    this.logger.verbose(message, { context });
  }

  // Métodos personalizados
  info(message: string, context?: LogContext) {
    this.logger.info(message, context);
    {{#if enableDatabaseLog}}
    this.saveToDatabase('info', message, context);
    {{/if}}
  }

  warning(message: string, context?: LogContext) {
    this.logger.warn(message, context);
    {{#if enableDatabaseLog}}
    this.saveToDatabase('warn', message, context);
    {{/if}}
  }

  logError(message: string, context?: LogContext & { trace?: string }) {
    this.logger.error(message, context);
    
    {{#if hasSentry}}
    // Enviar a Sentry
    Sentry.captureException(new Error(message), {
      tags: {
        userId: context?.userId,
        action: context?.action,
        resource: context?.resource
      },
      extra: context
    });
    {{/if}}

    {{#if enableDatabaseLog}}
    this.saveErrorToDatabase(message, context);
    {{/if}}
  }

  logDebug(message: string, context?: LogContext) {
    this.logger.debug(message, context);
  }

  // Logging de auditoría
  async audit(data: AuditLogData) {
    const auditEntry = {
      ...data,
      timestamp: new Date().toISOString()
    };

    this.auditLogger.info('Audit Log', auditEntry);

    {{#if enableDatabaseLog}}
    try {
      await this.prisma.auditLog.create({
        data: {
          userId: data.userId,
          action: data.action,
          resource: data.resource,
          resourceId: data.resourceId,
          oldValues: data.oldValues,
          newValues: data.newValues,
          ipAddress: data.ipAddress,
          userAgent: data.userAgent,
          metadata: data.metadata
        }
      });
    } catch (error) {
      this.logger.error('Failed to save audit log to database', { error: error.message });
    }
    {{/if}}
  }

  // Logging de performance
  async logPerformance(data: {
    endpoint: string;
    method: string;
    duration: number;
    statusCode: number;
    userId?: string;
    requestId?: string;
    metadata?: any;
  }) {
    this.logger.info('Performance Log', data);

    {{#if enableDatabaseLog}}
    try {
      await this.prisma.performanceLog.create({
        data: {
          endpoint: data.endpoint,
          method: data.method,
          duration: data.duration,
          statusCode: data.statusCode,
          userId: data.userId,
          requestId: data.requestId,
          metadata: data.metadata
        }
      });
    } catch (error) {
      this.logger.error('Failed to save performance log to database', { error: error.message });
    }
    {{/if}}
  }

  // Logging de requests
  async logRequest(data: {
    method: string;
    url: string;
    statusCode: number;
    duration: number;
    userId?: string;
    ipAddress?: string;
    userAgent?: string;
    requestId?: string;
    requestBody?: any;
    responseBody?: any;
  }) {
    this.logger.http('Request Log', data);

    {{#if enableDatabaseLog}}
    try {
      await this.prisma.requestLog.create({
        data: {
          method: data.method,
          url: data.url,
          statusCode: data.statusCode,
          duration: data.duration,
          userId: data.userId,
          ipAddress: data.ipAddress,
          userAgent: data.userAgent,
          requestId: data.requestId,
          requestBody: data.requestBody,
          responseBody: data.responseBody
        }
      });
    } catch (error) {
      this.logger.error('Failed to save request log to database', { error: error.message });
    }
    {{/if}}
  }

  {{#if enableDatabaseLog}}
  private async saveToDatabase(level: string, message: string, context?: LogContext) {
    try {
      // Solo guardar logs de nivel error y warn en la base de datos por defecto
      if (level === 'error' || level === 'warn') {
        await this.prisma.errorLog.create({
          data: {
            level,
            message,
            context: context as any,
            userId: context?.userId,
            requestId: context?.requestId
          }
        });
      }
    } catch (error) {
      // No logear errores de logging para evitar loops infinitos
      console.error('Failed to save log to database:', error.message);
    }
  }

  private async saveErrorToDatabase(message: string, context?: LogContext & { trace?: string }) {
    try {
      await this.prisma.errorLog.create({
        data: {
          level: 'error',
          message,
          stack: context?.trace,
          context: context as any,
          userId: context?.userId,
          requestId: context?.requestId
        }
      });
    } catch (error) {
      console.error('Failed to save error log to database:', error.message);
    }
  }
  {{/if}}

  // Métodos de utilidad
  createChildLogger(defaultContext: Partial<LogContext>) {
    return {
      info: (message: string, context?: LogContext) => 
        this.info(message, { ...defaultContext, ...context }),
      warn: (message: string, context?: LogContext) => 
        this.warning(message, { ...defaultContext, ...context }),
      error: (message: string, context?: LogContext & { trace?: string }) => 
        this.logError(message, { ...defaultContext, ...context }),
      debug: (message: string, context?: LogContext) => 
        this.logDebug(message, { ...defaultContext, ...context })
    };
  }

  // Obtener estadísticas de logs
  {{#if enableDatabaseLog}}
  async getLogStats(startDate?: Date, endDate?: Date) {
    const where = {
      timestamp: {
        gte: startDate,
        lte: endDate
      }
    };

    const [errorCount, auditCount, performanceCount, requestCount] = await Promise.all([
      this.prisma.errorLog.count({ where }),
      this.prisma.auditLog.count({ where }),
      this.prisma.performanceLog.count({ where }),
      this.prisma.requestLog.count({ where })
    ]);

    return {
      errors: errorCount,
      audits: auditCount,
      performance: performanceCount,
      requests: requestCount,
      total: errorCount + auditCount + performanceCount + requestCount
    };
  }

  async getRecentErrors(limit = 10) {
    return this.prisma.errorLog.findMany({
      take: limit,
      orderBy: { timestamp: 'desc' },
      where: { resolved: false }
    });
  }

  async markErrorAsResolved(errorId: string) {
    return this.prisma.errorLog.update({
      where: { id: errorId },
      data: { resolved: true }
    });
  }
  {{/if}}

  // Cleanup de logs antiguos
  {{#if enableDatabaseLog}}
  async cleanupOldLogs() {
    const thirtyDaysAgo = new Date();
    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

    const sevenDaysAgo = new Date();
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

    const ninetyDaysAgo = new Date();
    ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);

    const sevenYearsAgo = new Date();
    sevenYearsAgo.setFullYear(sevenYearsAgo.getFullYear() - 7);

    await Promise.all([
      // Limpiar logs de performance (30 días)
      this.prisma.performanceLog.deleteMany({
        where: { timestamp: { lt: thirtyDaysAgo } }
      }),
      // Limpiar logs de request (30 días)
      this.prisma.requestLog.deleteMany({
        where: { timestamp: { lt: thirtyDaysAgo } }
      }),
      // Limpiar logs de error resueltos (90 días)
      this.prisma.errorLog.deleteMany({
        where: { 
          timestamp: { lt: ninetyDaysAgo },
          resolved: true
        }
      }),
      // Limpiar logs de auditoría muy antiguos (7 años)
      this.prisma.auditLog.deleteMany({
        where: { timestamp: { lt: sevenYearsAgo } }
      })
    ]);
  }
  {{/if}}
}