import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CrearDescuentoAdminDto, CreateAutorizacionDescuentoDto, QueryAutorizacionesDto, ResolverAutorizacionDto } from './dto/cobranzas.dto';

const TTL_MINUTOS = 30;

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

  constructor(private readonly prisma: PrismaService) {}

  /** Cobrador solicita un descuento — crea la autorización con TTL */
  async solicitar(dto: CreateAutorizacionDescuentoDto, empresa_id: string, user_id: string) {
    if (!dto.descuento_monto && !dto.descuento_porcentaje) {
      throw new BadRequestException('Debe indicar descuento_monto o descuento_porcentaje');
    }
    const tipo = dto.tipo ?? 'QUITA_CAPITAL';
    if (tipo === 'EXONERACION_INTERES' && !dto.factura_cuota_id) {
      throw new BadRequestException(
        'tipo=EXONERACION_INTERES requiere factura_cuota_id',
      );
    }

    const expiresAt = new Date(Date.now() + TTL_MINUTOS * 60 * 1000);

    const autorización = await this.prisma.autorizaciones_descuento.create({
      data: {
        empresa_id,
        factura_cab_id: dto.factura_cab_id ?? null,
        factura_cuota_id: dto.factura_cuota_id ?? null,
        tipo,
        cliente_id: dto.cliente_id,
        cobrador_id: dto.cobrador_id,
        solicitado_por: user_id,
        descuento_monto: dto.descuento_monto ?? null,
        descuento_porcentaje: dto.descuento_porcentaje ?? null,
        motivo: dto.motivo ?? null,
        estado: 'pendiente',
        expires_at: expiresAt,
      },
      include: {
        cliente: { select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true } } } },
        cobrador: { select: { id: true, nombre: true, apellido: true } },
      },
    });

    this.logger.log(`Autorización de descuento solicitada: ${autorización.id} por cobrador ${dto.cobrador_id}`);
    return autorización;
  }

  /** Admin crea descuento directamente aprobado (no requiere solicitud del cobrador) */
  async crearDescuentoAdmin(dto: CrearDescuentoAdminDto, empresa_id: string, admin_id: string) {
    if (!dto.descuento_monto && !dto.descuento_porcentaje) {
      throw new BadRequestException('Debe indicar descuento_monto o descuento_porcentaje');
    }
    const tipo = dto.tipo ?? 'QUITA_CAPITAL';
    if (tipo === 'EXONERACION_INTERES') {
      if (!dto.factura_cuota_id) {
        throw new BadRequestException(
          'tipo=EXONERACION_INTERES requiere factura_cuota_id',
        );
      }
      // Coherencia: la cuota debe pertenecer al cliente (y a la factura si se envía)
      const cuota = await this.prisma.factura_cuotas.findFirst({
        where: { id: dto.factura_cuota_id },
        select: { factura_cab_id: true, factura_cab: { select: { cliente_id: true } } },
      });
      if (!cuota) {
        throw new BadRequestException('La cuota indicada no existe');
      }
      if (cuota.factura_cab.cliente_id !== dto.cliente_id) {
        throw new BadRequestException(
          'La cuota no pertenece al cliente indicado',
        );
      }
      if (dto.factura_cab_id && dto.factura_cab_id !== cuota.factura_cab_id) {
        throw new BadRequestException(
          'factura_cab_id no coincide con la factura de la cuota',
        );
      }
    }

    const TTL_DIAS_DEFAULT = 30;
    const expiresAt = dto.expires_at
      ? new Date(dto.expires_at)
      : new Date(Date.now() + TTL_DIAS_DEFAULT * 24 * 60 * 60 * 1000);

    const descuento = await this.prisma.autorizaciones_descuento.create({
      data: {
        empresa_id,
        factura_cab_id: dto.factura_cab_id ?? null,
        factura_cuota_id: dto.factura_cuota_id ?? null,
        tipo,
        cliente_id: dto.cliente_id,
        cobrador_id: dto.cobrador_id ?? null,
        solicitado_por: admin_id,
        aprobado_por: admin_id,
        descuento_monto: dto.descuento_monto ?? null,
        descuento_porcentaje: dto.descuento_porcentaje ?? null,
        motivo: dto.motivo ?? null,
        estado: 'aprobado',
        expires_at: expiresAt,
      },
      include: {
        cliente: { select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true } } } },
        cobrador: { select: { id: true, nombre: true, apellido: true } },
        factura: { select: { id: true, dnumdoc: true, dest: true, dpunexp: true, moneda: { select: { codigo: true } } } },
        factura_cuota: { select: { id: true, nro_cuota: true, dvenccuo: true, dmoncuota: true } },
      },
    });

    this.logger.log(`Autorización ${tipo} creada por admin ${admin_id} cliente=${dto.cliente_id} cuota=${dto.factura_cuota_id ?? '-'} → ${descuento.id}`);
    return descuento;
  }

  /** Supervisor aprueba o rechaza */
  async resolver(id: string, dto: ResolverAutorizacionDto, empresa_id: string, user_id: string) {
    const auth = await this.prisma.autorizaciones_descuento.findFirst({ where: { id, empresa_id } });
    if (!auth) throw new NotFoundException(`Autorización ${id} no encontrada`);

    if (auth.estado !== 'pendiente') {
      throw new BadRequestException(`La autorización ya fue ${auth.estado}`);
    }

    // Verificar que no esté expirada
    if (auth.expires_at && auth.expires_at < new Date()) {
      await this.prisma.autorizaciones_descuento.update({
        where: { id },
        data: { estado: 'expirado', updated_at: new Date() },
      });
      throw new BadRequestException('La autorización ya expiró. El cobrador debe solicitar una nueva.');
    }

    const actualizada = await this.prisma.autorizaciones_descuento.update({
      where: { id },
      data: {
        estado: dto.decision,
        aprobado_por: user_id,
        notas_supervisor: dto.notas_supervisor ?? null,
        updated_at: new Date(),
      },
      include: {
        cobrador: { select: { id: true, nombre: true, apellido: true } },
        cliente: { select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true } } } },
      },
    });

    this.logger.log(`Autorización ${id} ${dto.decision} por usuario ${user_id}`);
    return actualizada;
  }

  /**
   * Busca la autorización de descuento activa para una factura.
   * Usado desde el módulo de cobros. Cubre descuentos nivel-factura y nivel-cliente.
   * Maneja ambos estados ('aprobado'/'aprobada') para compatibilidad con datos existentes.
   */
  async getActivaParaUsuario(factura_cab_id: string, usuario_id: string, empresa_id: string) {
    const vigencia = [{ expires_at: null }, { expires_at: { gt: new Date() } }];
    const estadosAprobados = ['aprobado', 'aprobada'];

    // Solo descuentos de capital: las exoneraciones de interés viajan por otro canal
    // (getExoneracionesActivasPorCuotas) porque se aplican por cuota concreta.
    // `tipo` es NOT NULL con default 'QUITA_CAPITAL' — comparar contra el valor literal.
    const tipoCapital = { tipo: 'QUITA_CAPITAL' };

    // 1. Descuento vinculado a esta factura específica
    const porFactura = await this.prisma.autorizaciones_descuento.findFirst({
      where: { factura_cab_id, empresa_id, estado: { in: estadosAprobados }, OR: vigencia, ...tipoCapital },
      orderBy: { created_at: 'desc' },
    });
    if (porFactura) return porFactura;

    // 2. Fallback: descuento nivel-cliente (sin factura específica), buscando el cliente de la factura
    const factura = await this.prisma.factura_cab.findFirst({
      where: { id: factura_cab_id },
      select: { cliente_id: true },
    });
    if (!factura) return null;

    return this.prisma.autorizaciones_descuento.findFirst({
      where: { factura_cab_id: null, cliente_id: factura.cliente_id, empresa_id, estado: { in: estadosAprobados }, OR: vigencia, ...tipoCapital },
      orderBy: { created_at: 'desc' },
    });
  }

  /**
   * Fase A.3.7: devuelve las autorizaciones de tipo=EXONERACION_INTERES,
   * aprobadas y vigentes, para las cuotas indicadas.
   * Una por cuota: si hay múltiples, prevalece la más reciente.
   */
  async getExoneracionesActivasPorCuotas(
    empresa_id: string,
    factura_cuota_ids: string[],
  ) {
    if (factura_cuota_ids.length === 0) return [];
    const vigencia = [{ expires_at: null }, { expires_at: { gt: new Date() } }];
    const filas = await this.prisma.autorizaciones_descuento.findMany({
      where: {
        empresa_id,
        tipo: 'EXONERACION_INTERES',
        estado: { in: ['aprobado', 'aprobada'] },
        factura_cuota_id: { in: factura_cuota_ids },
        OR: vigencia,
      },
      orderBy: { created_at: 'desc' },
    });
    // Dedup por cuota — la más reciente gana
    const porCuota = new Map<string, (typeof filas)[number]>();
    for (const fila of filas) {
      if (fila.factura_cuota_id && !porCuota.has(fila.factura_cuota_id)) {
        porCuota.set(fila.factura_cuota_id, fila);
      }
    }
    return Array.from(porCuota.values());
  }

  /** Obtener autorización aprobada y vigente para una factura+cobrador (usado al cobrar) */
  async getAprobadaParaFactura(factura_cab_id: string, cobrador_id: string, empresa_id: string) {
    return this.prisma.autorizaciones_descuento.findFirst({
      where: {
        factura_cab_id,
        cobrador_id,
        empresa_id,
        estado: { in: ['aprobado', 'aprobada'] },
        OR: [
          { expires_at: null },
          { expires_at: { gt: new Date() } },
        ],
      },
      orderBy: { created_at: 'desc' },
    });
  }

  /**
   * Cancelar una autorización aprobada/pendiente que aún no se usó.
   * Útil cuando el admin se equivocó al crear o el cliente desistió.
   * No se puede cancelar una autorización ya usada (atada a un recibo) — para ese caso
   * el camino correcto es anular el recibo, lo que libera la autorización.
   */
  async cancelar(id: string, empresa_id: string, user_id: string, motivo?: string) {
    const auth = await this.prisma.autorizaciones_descuento.findFirst({
      where: { id, empresa_id },
    });
    if (!auth) throw new NotFoundException(`Autorización ${id} no encontrada`);
    if (auth.estado === 'usado') {
      throw new BadRequestException(
        'No se puede cancelar una autorización ya usada en un recibo. Anulá el recibo para liberarla.',
      );
    }
    if (['cancelado', 'rechazado', 'expirado'].includes(auth.estado)) {
      throw new BadRequestException(`La autorización ya está ${auth.estado}`);
    }
    const actualizada = await this.prisma.autorizaciones_descuento.update({
      where: { id },
      data: {
        estado: 'cancelado',
        notas_supervisor: motivo
          ? `${auth.notas_supervisor ? `${auth.notas_supervisor} | ` : ''}Cancelada: ${motivo}`
          : auth.notas_supervisor,
        updated_at: new Date(),
      },
    });
    this.logger.log(`Autorización ${id} cancelada por usuario ${user_id}${motivo ? ` motivo="${motivo}"` : ''}`);
    return actualizada;
  }

  /** Marcar autorización como usada al crear el recibo */
  async marcarUsada(id: string, recibo_cobro_id: string) {
    return this.prisma.autorizaciones_descuento.update({
      where: { id },
      data: { estado: 'usado', recibo_cobro_id, updated_at: new Date() },
    });
  }

  async findAll(empresa_id: string, query: QueryAutorizacionesDto) {
    const where: any = { empresa_id };
    if (query.estado && query.estado !== 'todos') where.estado = query.estado;
    if (query.cobrador_id) where.cobrador_id = query.cobrador_id;

    return this.prisma.autorizaciones_descuento.findMany({
      where,
      include: {
        cobrador: { select: { id: true, nombre: true, apellido: true } },
        cliente: { select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true } } } },
        factura: { select: { id: true, dnumdoc: true, dest: true, dpunexp: true, moneda: { select: { codigo: true } } } },
        factura_cuota: { select: { id: true, nro_cuota: true, dvenccuo: true, dmoncuota: true } },
        solicitante: { select: { id: true, nombres: true } },
        aprobador: { select: { id: true, nombres: true } },
      },
      orderBy: { created_at: 'desc' },
    });
  }

  async countPendientes(empresa_id: string): Promise<number> {
    return this.prisma.autorizaciones_descuento.count({
      where: {
        empresa_id,
        estado: 'pendiente',
        expires_at: { gt: new Date() },
      },
    });
  }

  /** Job: Expirar autorizaciones pendientes cuyo TTL venció */
  async expirarVencidas(): Promise<number> {
    const result = await this.prisma.autorizaciones_descuento.updateMany({
      where: { estado: 'pendiente', expires_at: { lt: new Date() } },
      data: { estado: 'expirado', updated_at: new Date() },
    });
    if (result.count > 0) {
      this.logger.log(`${result.count} autorizaciones de descuento expiradas (job automático)`);
    }
    return result.count;
  }
}
