import {
  BadRequestException,
  Injectable,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { createHash, randomBytes } from 'crypto';
import { PrismaService } from '../../prisma/prisma.service';

const KEY_PREFIX = 'sk_carc_';

export type ScopeApi = 'sync' | 'cobros' | 'gestiones';

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

  constructor(private readonly prisma: PrismaService) {}

  async crear(
    empresa_id: string,
    usuario_id: string,
    dto: { nombre: string; scopes?: ScopeApi[]; expira_en_dias?: number },
  ): Promise<{ id: string; nombre: string; key_plaintext: string; key_prefix: string }> {
    const plaintext = KEY_PREFIX + randomBytes(24).toString('hex');
    const hash = this.hash(plaintext);
    const prefix = plaintext.slice(0, 12);

    const expira = dto.expira_en_dias
      ? new Date(Date.now() + dto.expira_en_dias * 86_400_000)
      : null;

    const row = await this.prisma.cartera_api_keys.create({
      data: {
        empresa_id,
        nombre: dto.nombre,
        key_hash: hash,
        key_prefix: prefix,
        scopes: (dto.scopes ?? ['sync', 'cobros', 'gestiones']) as unknown as object,
        expira_en: expira,
        creada_por: usuario_id,
      },
    });

    this.logger.log(`API key creada empresa=${empresa_id} id=${row.id} scopes=${JSON.stringify(row.scopes)}`);

    return {
      id: row.id,
      nombre: row.nombre,
      key_plaintext: plaintext,
      key_prefix: prefix,
    };
  }

  async listar(empresa_id: string) {
    const rows = await this.prisma.cartera_api_keys.findMany({
      where: { empresa_id },
      orderBy: { created_at: 'desc' },
      select: {
        id: true,
        nombre: true,
        key_prefix: true,
        scopes: true,
        ultimo_uso_at: true,
        expira_en: true,
        revocada_en: true,
        created_at: true,
      },
    });
    return rows.map((r) => ({
      ...r,
      estado: this.estadoKey(r as any),
    }));
  }

  async revocar(empresa_id: string, id: string) {
    const row = await this.prisma.cartera_api_keys.findFirst({
      where: { id, empresa_id },
    });
    if (!row) throw new NotFoundException('API key no encontrada');
    if (row.revocada_en) throw new BadRequestException('La key ya está revocada');
    return this.prisma.cartera_api_keys.update({
      where: { id },
      data: { revocada_en: new Date() },
    });
  }

  /**
   * Valida una key plaintext recibida en X-Api-Key. Devuelve la fila completa
   * si es válida (key activa, no expirada, no revocada) y el empresa_id.
   */
  async validar(plaintext: string): Promise<{ empresa_id: string; api_key_id: string; scopes: ScopeApi[] } | null> {
    if (!plaintext || !plaintext.startsWith(KEY_PREFIX)) return null;
    const hash = this.hash(plaintext);
    const row = await this.prisma.cartera_api_keys.findUnique({
      where: { key_hash: hash },
    });
    if (!row) return null;
    if (row.revocada_en) return null;
    if (row.expira_en && row.expira_en < new Date()) return null;

    // Update last-used asíncronamente para no bloquear el request.
    this.prisma.cartera_api_keys
      .update({ where: { id: row.id }, data: { ultimo_uso_at: new Date() } })
      .catch(() => {});

    return {
      empresa_id: row.empresa_id,
      api_key_id: row.id,
      scopes: row.scopes as unknown as ScopeApi[],
    };
  }

  async auditar(params: {
    empresa_id?: string | null;
    api_key_id?: string | null;
    endpoint: string;
    method: string;
    status_code: number;
    ip?: string;
    user_agent?: string;
    request_size?: number;
    latency_ms?: number;
    error?: string;
  }): Promise<void> {
    try {
      await this.prisma.cartera_api_audit.create({
        data: {
          empresa_id: params.empresa_id ?? null,
          api_key_id: params.api_key_id ?? null,
          endpoint: params.endpoint.slice(0, 80),
          method: params.method,
          status_code: params.status_code,
          ip: params.ip ?? null,
          user_agent: params.user_agent?.slice(0, 500) ?? null,
          request_size: params.request_size ?? null,
          latency_ms: params.latency_ms ?? null,
          error: params.error?.slice(0, 1000) ?? null,
        },
      });
    } catch (err: any) {
      this.logger.warn(`Audit log falló: ${err?.message ?? err}`);
    }
  }

  async auditRecientes(empresa_id: string, limit = 50) {
    return this.prisma.cartera_api_audit.findMany({
      where: { empresa_id },
      orderBy: { created_at: 'desc' },
      take: Math.min(limit, 200),
      select: {
        id: true,
        endpoint: true,
        method: true,
        status_code: true,
        ip: true,
        latency_ms: true,
        error: true,
        created_at: true,
        api_key: { select: { nombre: true, key_prefix: true } },
      },
    });
  }

  private hash(plaintext: string): string {
    return createHash('sha256').update(plaintext).digest('hex');
  }

  private estadoKey(r: { revocada_en: Date | null; expira_en: Date | null }): string {
    if (r.revocada_en) return 'revocada';
    if (r.expira_en && r.expira_en < new Date()) return 'expirada';
    return 'activa';
  }
}
