import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable, tap } from 'rxjs';
import { AuditService } from './audit.service';
import { AUDIT_ENTITY_KEY } from './auditable.decorator';

@Injectable()
export class AuditInterceptor implements NestInterceptor {
  constructor(
    private readonly auditService: AuditService,
    private readonly reflector: Reflector,
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const entityType = this.reflector.get<string>(AUDIT_ENTITY_KEY, context.getHandler());
    if (!entityType) return next.handle();

    const req = context.switchToHttp().getRequest();
    const method = req.method as string;

    // Solo auditar mutaciones
    const mutationMethods = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
    if (!mutationMethods.has(method)) return next.handle();

    const action = this.resolveAction(
      method,
      String(req.route?.path || req.originalUrl || req.url || ''),
    );
    const user = req.user;
    const ip = (req.ip as string) || (req.headers?.['x-forwarded-for'] as string) || '';
    const userAgent = (req.headers?.['user-agent'] as string) || '';
    const requestEntityId = (req.params?.id as string) || (req.params?.suscripcionId as string) || undefined;
    const terminalId = (req.headers?.['x-terminal-id'] as string) || undefined;
    const cajaId = (req.headers?.['x-caja-id'] as string) || undefined;
    const sesionCajaId = (req.headers?.['x-sesion-caja-id'] as string) || undefined;

    return next.handle().pipe(
      tap((response) => {
        // Fire-and-forget: no bloqueamos la respuesta
        const resData = response && typeof response === 'object' ? response : undefined;
        const entityId = this.resolveEntityId(requestEntityId, response);
        const numero = this.resolveNumeroLegible(response);
        void this.auditService.log({
          empresa_id: user?.empresa_id,
          user_id: user?.id,
          action,
          entity_type: entityType,
          entity_id: entityId,
          new_value: resData as Record<string, unknown> | undefined,
          ip_address: ip,
          user_agent: userAgent,
          // No exponer el id interno en la descripción (la ve el usuario); entity_id ya lo guarda.
          descripcion: `${action} ${entityType}${numero ? ` ${numero}` : ''}`,
          terminal_id: terminalId,
          caja_id: cajaId,
          sesion_caja_id: sesionCajaId,
        });
      }),
    );
  }

  private resolveEntityId(requestEntityId: unknown, response: unknown): string | undefined {
    const normalizeId = (value: unknown): string | undefined => {
      if (typeof value !== 'string' && typeof value !== 'number') return undefined;
      const normalized = String(value).trim();
      return normalized || undefined;
    };

    const fromRequest = normalizeId(requestEntityId);
    if (fromRequest) return fromRequest;
    if (!response || typeof response !== 'object' || Array.isArray(response)) return undefined;

    const record = response as Record<string, unknown>;
    const nestedData =
      record.data && typeof record.data === 'object' && !Array.isArray(record.data)
        ? (record.data as Record<string, unknown>)
        : undefined;
    const candidates = [
      record.id,
      record.entity_id,
      record.compra_id,
      record.gasto_id,
      record.orden_compra_id,
      record.recepcion_id,
      nestedData?.id,
      nestedData?.entity_id,
    ];

    for (const candidate of candidates) {
      const resolved = normalizeId(candidate);
      if (resolved) return resolved;
    }
    return undefined;
  }

  /** Número legible para la descripción (nunca el id interno). */
  private resolveNumeroLegible(response: unknown): string | undefined {
    if (!response || typeof response !== 'object' || Array.isArray(response)) return undefined;
    const record = response as Record<string, unknown>;
    const nestedData =
      record.data && typeof record.data === 'object' && !Array.isArray(record.data)
        ? (record.data as Record<string, unknown>)
        : undefined;
    const keys = ['numero_factura', 'numero_pedido', 'numero_orden', 'numero', 'codigo'];
    for (const source of [record, nestedData]) {
      if (!source) continue;
      for (const key of keys) {
        const value = source[key];
        if (typeof value === 'string' || typeof value === 'number') {
          const normalized = String(value).trim();
          if (normalized) return normalized;
        }
      }
    }
    return undefined;
  }

  private resolveAction(method: string, route = ''): string {
    switch (method) {
      case 'POST':
        return 'CREATE';
      case 'PUT':
        return 'UPDATE';
      case 'PATCH':
        return /(?:anular|aprobar|cancelar|activar|desactivar|estado|status)/i.test(route)
          ? 'CHANGE_STATUS'
          : 'UPDATE';
      case 'DELETE':
        return 'DELETE';
      default:
        return method;
    }
  }
}
