import { BadRequestException, ConflictException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { AuditService } from 'src/audit/audit.service';
import { formatDateResponse } from 'src/utils/utilidades';
import { PrismaService } from '../prisma/prisma.service';
import { CreateCotizacionDto } from './dto/create-cotizacion.dto';
import { CreateEmpresaMonedaDto } from './dto/create-empresa-moneda.dto';
import { CreateMonedaDto } from './dto/create-moneda.dto';
import { UpdateCotizacionDto } from './dto/update-cotizacion.dto';
import { UpdateEmpresaMonedaDto } from './dto/update-empresa-moneda.dto';
import { UpdateMonedaDto } from './dto/update-moneda.dto';

@Injectable()
export class MonedasService {
  private readonly logger = new Logger(MonedasService.name);
  constructor(
    private prisma: PrismaService,
    private readonly auditService: AuditService,
  ) {}

  // ==================== MONEDAS (Catálogo global) ====================

  async createMoneda(dto: CreateMonedaDto, user_id?: string) {
    const existing = await this.prisma.moneda.findFirst({
      where: { codigo: dto.codigo.toUpperCase() },
    });
    if (existing) throw new ConflictException('Ya existe una moneda con ese código');

    const moneda = await this.prisma.moneda.create({
      data: {
        codigo: dto.codigo.toUpperCase(),
        codigo_numerico: dto.codigo_numerico,
        descripcion: dto.descripcion,
        simbolo: dto.simbolo,
        decimales_estandar: dto.decimales_estandar ?? 2,
        posicion_simbolo: dto.posicion_simbolo ?? 'L',
        active: dto.active ?? true,
        created_at: new Date(),
        updated_at: new Date(),
      },
    });
    this.logger.log(`Moneda creada: ${moneda.codigo} (${moneda.id})`, 'MonedasService');
    await this.auditService.log({
      user_id,
      action: 'CREATE',
      entity_type: 'moneda',
      entity_id: moneda.id,
      descripcion: `Moneda creada: ${moneda.descripcion} | Código: ${moneda.codigo}`,
      new_value: { codigo: moneda.codigo, descripcion: moneda.descripcion, simbolo: moneda.simbolo },
    });
    return { message: 'Moneda creada exitosamente', data: moneda };
  }

  async findAllMonedas(includeInactive = false) {
    const where = includeInactive ? {} : { active: true };
    const monedas = await this.prisma.moneda.findMany({
      where,
      orderBy: { codigo: 'asc' },
    });
    return monedas;
  }

  async findOneMoneda(id: string) {
    const moneda = await this.prisma.moneda.findUnique({ where: { id } });
    if (!moneda) throw new NotFoundException('Moneda no encontrada');
    return moneda;
  }

  async updateMoneda(id: string, dto: UpdateMonedaDto, user_id?: string) {
    const existing = await this.prisma.moneda.findUnique({ where: { id } });
    if (!existing) throw new NotFoundException('Moneda no encontrada');

    if (dto.codigo && dto.codigo.toUpperCase() !== existing.codigo) {
      const duplicate = await this.prisma.moneda.findFirst({
        where: { codigo: dto.codigo.toUpperCase(), id: { not: id } },
      });
      if (duplicate) throw new ConflictException('Ya existe una moneda con ese código');
    }

    const moneda = await this.prisma.moneda.update({
      where: { id },
      data: {
        codigo: dto.codigo?.toUpperCase(),
        codigo_numerico: dto.codigo_numerico,
        descripcion: dto.descripcion,
        simbolo: dto.simbolo,
        decimales_estandar: dto.decimales_estandar,
        posicion_simbolo: dto.posicion_simbolo,
        active: dto.active,
        updated_at: new Date(),
      },
    });
    this.logger.log(`Moneda actualizada: ${moneda.codigo} (${id})`, 'MonedasService');
    await this.auditService.log({
      user_id,
      action: 'UPDATE',
      entity_type: 'moneda',
      entity_id: id,
      descripcion: `Moneda actualizada: ${moneda.descripcion}`,
      old_value: { codigo: existing.codigo, descripcion: existing.descripcion },
      new_value: { codigo: moneda.codigo, descripcion: moneda.descripcion },
    });
    return { message: 'Moneda actualizada exitosamente', data: moneda };
  }

  async toggleMonedaStatus(id: string, user_id?: string) {
    const existing = await this.prisma.moneda.findUnique({ where: { id } });
    if (!existing) throw new NotFoundException('Moneda no encontrada');

    const moneda = await this.prisma.moneda.update({
      where: { id },
      data: { active: !existing.active, updated_at: new Date() },
    });
    const action = moneda.active ? 'ACTIVATE' : 'DEACTIVATE';
    this.logger.log(`Moneda (${id}) ${moneda.active ? 'activada' : 'desactivada'}`, 'MonedasService');
    await this.auditService.log({
      user_id,
      action,
      entity_type: 'moneda',
      entity_id: id,
      descripcion: `Moneda ${moneda.active ? 'activada' : 'desactivada'}: ${existing.codigo}`,
      old_value: { active: existing.active },
      new_value: { active: moneda.active },
    });
    return { message: `Moneda ${moneda.active ? 'activada' : 'desactivada'}`, data: moneda };
  }

  // ==================== EMPRESA MONEDAS ====================

  async findEmpresaMonedas(empresa_id: string) {
    const monedas = await this.prisma.empresa_monedas.findMany({
      where: { empresa_id },
      include: { moneda: true },
      orderBy: [{ es_principal: 'desc' }, { moneda: { codigo: 'asc' } }],
    });
    return monedas;
  }

  async addEmpresaMoneda(empresa_id: string, dto: CreateEmpresaMonedaDto, user_id?: string) {
    // Verificar que la moneda existe
    const moneda = await this.prisma.moneda.findUnique({ where: { id: dto.moneda_id } });
    if (!moneda) throw new NotFoundException('Moneda no encontrada');

    // Verificar que no esté ya asignada
    const existing = await this.prisma.empresa_monedas.findUnique({
      where: { empresa_id_moneda_id: { empresa_id, moneda_id: dto.moneda_id } },
    });
    if (existing) throw new ConflictException('Esta moneda ya está asignada a la empresa');

    // Si es principal, quitar principal de las demás
    if (dto.es_principal) {
      await this.prisma.empresa_monedas.updateMany({
        where: { empresa_id },
        data: { es_principal: false },
      });
    }

    const empresaMoneda = await this.prisma.empresa_monedas.create({
      data: {
        empresa_id,
        moneda_id: dto.moneda_id,
        es_principal: dto.es_principal ?? false,
        decimales_visualizar: dto.decimales_visualizar ?? 0,
        active: dto.active ?? true,
        created_at: new Date(),
        updated_at: new Date(),
      },
      include: { moneda: true },
    });
    this.logger.log(`Moneda ${dto.moneda_id} asignada a empresa ${empresa_id}`, 'MonedasService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'CREATE',
      entity_type: 'empresa_moneda',
      entity_id: empresaMoneda.id,
      descripcion: `Moneda asignada a empresa: ${empresaMoneda.moneda?.codigo ?? dto.moneda_id} | Principal: ${empresaMoneda.es_principal}`,
      new_value: { moneda_id: dto.moneda_id, es_principal: empresaMoneda.es_principal },
    });
    return { message: 'Moneda asignada a la empresa', data: empresaMoneda };
  }

  async updateEmpresaMoneda(empresa_id: string, id: string, dto: UpdateEmpresaMonedaDto, user_id?: string) {
    const existing = await this.prisma.empresa_monedas.findFirst({
      where: { id, empresa_id },
    });
    if (!existing) throw new NotFoundException('Configuración de moneda no encontrada');

    // Si se marca como principal, quitar principal de las demás
    if (dto.es_principal) {
      await this.prisma.empresa_monedas.updateMany({
        where: { empresa_id, id: { not: id } },
        data: { es_principal: false },
      });
    }

    const empresaMoneda = await this.prisma.empresa_monedas.update({
      where: { id },
      data: {
        es_principal: dto.es_principal,
        decimales_visualizar: dto.decimales_visualizar,
        active: dto.active,
        updated_at: new Date(),
      },
      include: { moneda: true },
    });
    this.logger.log(`Empresa-moneda actualizada (${id}) para empresa ${empresa_id}`, 'MonedasService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'empresa_moneda',
      entity_id: id,
      descripcion: `Configuración de moneda actualizada: ${empresaMoneda.moneda?.codigo}`,
      old_value: {
        es_principal: existing.es_principal,
        decimales_visualizar: existing.decimales_visualizar,
        active: existing.active,
      },
      new_value: {
        es_principal: empresaMoneda.es_principal,
        decimales_visualizar: empresaMoneda.decimales_visualizar,
        active: empresaMoneda.active,
      },
    });
    return { message: 'Configuración actualizada', data: empresaMoneda };
  }

  async removeEmpresaMoneda(empresa_id: string, id: string, user_id?: string) {
    const existing = await this.prisma.empresa_monedas.findFirst({
      where: { id, empresa_id },
    });
    if (!existing) throw new NotFoundException('Configuración de moneda no encontrada');

    if (existing.es_principal) {
      throw new BadRequestException(
        'No se puede eliminar la moneda principal. Asigne otra moneda como principal primero.',
      );
    }

    await this.prisma.empresa_monedas.delete({ where: { id } });
    this.logger.log(`Empresa-moneda eliminada (${id}) de empresa ${empresa_id}`, 'MonedasService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'DELETE',
      entity_type: 'empresa_moneda',
      entity_id: id,
      descripcion: `Moneda removida de la empresa`,
      old_value: { moneda_id: existing.moneda_id, es_principal: existing.es_principal },
    });
    return { message: 'Moneda removida de la empresa' };
  }

  // ==================== COTIZACIONES ====================

  async findCotizaciones(empresa_id: string, moneda_id?: string, limit = 30) {
    const where: any = { empresa_id };
    if (moneda_id) where.moneda_id = moneda_id;

    const cotizaciones = await this.prisma.cotizacion_moneda.findMany({
      where,
      include: { moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } } },
      orderBy: { fecha: 'desc' },
      take: limit,
    });
    return cotizaciones;
  }

  async findCotizacionByFecha(empresa_id: string, moneda_id: string, fecha: string) {
    const cotizacion = await this.prisma.cotizacion_moneda.findUnique({
      where: { empresa_id_moneda_id_fecha: { empresa_id, moneda_id, fecha: new Date(fecha) } },
      include: { moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } } },
    });
    return cotizacion;
  }

  /**
   * FASE 3 — Cotización vigente unificada (single source of truth).
   * Ver docs/plan-acreedores-proveedores-exterior.md.
   *
   * Cascada:
   *   1. Si la empresa tiene módulo CONTABILIDAD activo → `cont_tipo_cambio`
   *      (última tasa con fecha ≤ solicitada). Fuente autoritativa.
   *   2. Si no → `cotizacion_moneda` (última con fecha ≤ solicitada). Fuente Facturación.
   *   3. Si tampoco existe → `{ tasa: 1, fuente: "fallback" }`.
   *
   * Uso: todo form multi-moneda (Compras, OP, Gastos, Recibos, Importaciones)
   * consume este endpoint al seleccionar moneda o cambiar fecha.
   */
  async getCotizacionVigente(
    empresa_id: string,
    codigo: string,
    fecha?: Date,
  ): Promise<{
    tasa: number;
    fecha_efectiva: string | null;
    fuente: 'contabilidad' | 'facturacion' | 'fallback';
    moneda_codigo: string;
    warning?: string;
  }> {
    const codigoUp = String(codigo || '').toUpperCase().trim();
    const fechaRef = fecha ?? new Date();

    if (codigoUp === 'PYG') {
      return { tasa: 1, fecha_efectiva: null, fuente: 'fallback', moneda_codigo: 'PYG' };
    }

    // 1. Preferir contabilidad si el módulo está activo
    const tieneContab = await this.prisma.suscripcion_modulos.findFirst({
      where: {
        activo: true,
        modulos: { codigo: 'CONTABILIDAD' },
        suscripciones: { empresa_id },
      },
      select: { id: true },
    });

    if (tieneContab) {
      const tc = await this.prisma.cont_tipo_cambio.findFirst({
        where: { empresa_id, moneda: codigoUp, fecha: { lte: fechaRef } },
        orderBy: { fecha: 'desc' },
      });
      if (tc) {
        return {
          tasa: Number(tc.tasa),
          fecha_efectiva: formatDateResponse(tc.fecha),
          fuente: 'contabilidad',
          moneda_codigo: codigoUp,
        };
      }
    }

    // 2. Fallback a cotizacion_moneda (por moneda_id vía codigo)
    const monedaRef = await this.prisma.moneda.findFirst({
      where: { codigo: codigoUp },
      select: { id: true },
    });
    if (monedaRef) {
      const cot = await this.prisma.cotizacion_moneda.findFirst({
        where: {
          empresa_id,
          moneda_id: monedaRef.id,
          fecha: { lte: fechaRef },
        },
        orderBy: { fecha: 'desc' },
      });
      if (cot) {
        // Preferimos cotización de venta (usualmente la que aplica al comprar moneda extranjera).
        const tasa = Number(cot.cotizacion_venta) || Number(cot.cotizacion_compra) || 1;
        return {
          tasa,
          fecha_efectiva: formatDateResponse(cot.fecha),
          fuente: 'facturacion',
          moneda_codigo: codigoUp,
        };
      }
    }

    // 3. Fallback duro
    return {
      tasa: 1,
      fecha_efectiva: null,
      fuente: 'fallback',
      moneda_codigo: codigoUp,
      warning: `Sin cotización registrada para ${codigoUp}. Cargá una en ${tieneContab ? 'Contabilidad → Tipo de Cambio' : 'Facturación → Monedas → Cotizaciones'}.`,
    };
  }

  async getUltimaCotizacion(empresa_id: string, moneda_id: string) {
    const cotizacion = await this.prisma.cotizacion_moneda.findFirst({
      where: { empresa_id, moneda_id },
      orderBy: { fecha: 'desc' },
      include: { moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } } },
    });
    if (!cotizacion) throw new NotFoundException('Cotización no encontrada');

    return {
      ...cotizacion,
      fecha: formatDateResponse(cotizacion.fecha),
    };
  }

  async createCotizacion(empresa_id: string, dto: CreateCotizacionDto, usuario_id?: string) {
    // Verificar que la moneda existe y está asignada a la empresa
    const empresaMoneda = await this.prisma.empresa_monedas.findUnique({
      where: { empresa_id_moneda_id: { empresa_id, moneda_id: dto.moneda_id } },
    });
    if (!empresaMoneda) {
      throw new BadRequestException('Esta moneda no está configurada para la empresa');
    }

    // Verificar si ya existe cotización para esa fecha
    const existing = await this.prisma.cotizacion_moneda.findUnique({
      where: { empresa_id_moneda_id_fecha: { empresa_id, moneda_id: dto.moneda_id, fecha: new Date(dto.fecha) } },
    });

    if (existing) {
      // Actualizar cotización existente
      const cotizacion = await this.prisma.cotizacion_moneda.update({
        where: { id: existing.id },
        data: {
          cotizacion_compra: dto.cotizacion_compra,
          cotizacion_venta: dto.cotizacion_venta,
          usuario_id,
        },
        include: { moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } } },
      });
      this.logger.log(
        `Cotización actualizada (fecha duplicada) (${existing.id}) para empresa ${empresa_id}`,
        'MonedasService',
      );
      await this.auditService.log({
        empresa_id,
        user_id: usuario_id,
        action: 'UPDATE',
        entity_type: 'cotizacion_moneda',
        entity_id: existing.id,
        descripcion: `Cotización actualizada por fecha existente: ${dto.fecha} | Moneda: ${cotizacion.moneda?.codigo ?? dto.moneda_id}`,
        old_value: {
          cotizacion_compra: Number(existing.cotizacion_compra),
          cotizacion_venta: Number(existing.cotizacion_venta),
        },
        new_value: { cotizacion_compra: dto.cotizacion_compra, cotizacion_venta: dto.cotizacion_venta },
      });
      return { message: 'Cotización actualizada', data: cotizacion };
    }

    const cotizacion = await this.prisma.cotizacion_moneda.create({
      data: {
        empresa_id,
        moneda_id: dto.moneda_id,
        fecha: new Date(dto.fecha),
        cotizacion_compra: dto.cotizacion_compra,
        cotizacion_venta: dto.cotizacion_venta,
        usuario_id,
        created_at: new Date(),
      },
      include: { moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } } },
    });
    this.logger.log(`Cotización registrada (${cotizacion.id}) para empresa ${empresa_id}`, 'MonedasService');
    await this.auditService.log({
      empresa_id,
      user_id: usuario_id,
      action: 'CREATE',
      entity_type: 'cotizacion_moneda',
      entity_id: cotizacion.id,
      descripcion: `Cotización registrada: ${dto.fecha} | Moneda: ${cotizacion.moneda?.codigo ?? dto.moneda_id}`,
      new_value: {
        moneda_id: dto.moneda_id,
        fecha: dto.fecha,
        cotizacion_compra: dto.cotizacion_compra,
        cotizacion_venta: dto.cotizacion_venta,
      },
    });
    return { message: 'Cotización registrada', data: cotizacion };
  }

  async updateCotizacion(empresa_id: string, id: string, dto: UpdateCotizacionDto, user_id?: string) {
    const existing = await this.prisma.cotizacion_moneda.findFirst({
      where: { id, empresa_id },
    });
    if (!existing) throw new NotFoundException('Cotización no encontrada');

    const cotizacion = await this.prisma.cotizacion_moneda.update({
      where: { id },
      data: {
        cotizacion_compra: dto.cotizacion_compra,
        cotizacion_venta: dto.cotizacion_venta,
      },
      include: { moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } } },
    });
    this.logger.log(`Cotización actualizada (${id}) para empresa ${empresa_id}`, 'MonedasService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'cotizacion_moneda',
      entity_id: id,
      descripcion: `Cotización actualizada`,
      old_value: {
        cotizacion_compra: Number(existing.cotizacion_compra),
        cotizacion_venta: Number(existing.cotizacion_venta),
      },
      new_value: { cotizacion_compra: dto.cotizacion_compra, cotizacion_venta: dto.cotizacion_venta },
    });
    return { message: 'Cotización actualizada', data: cotizacion };
  }

  async deleteCotizacion(empresa_id: string, id: string, user_id?: string) {
    const existing = await this.prisma.cotizacion_moneda.findFirst({
      where: { id, empresa_id },
    });
    if (!existing) throw new NotFoundException('Cotización no encontrada');

    await this.prisma.cotizacion_moneda.delete({ where: { id } });
    this.logger.log(`Cotización eliminada (${id}) de empresa ${empresa_id}`, 'MonedasService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'DELETE',
      entity_type: 'cotizacion_moneda',
      entity_id: id,
      descripcion: `Cotización eliminada`,
      old_value: {
        moneda_id: existing.moneda_id,
        cotizacion_compra: Number(existing.cotizacion_compra),
        cotizacion_venta: Number(existing.cotizacion_venta),
      },
    });
    return { message: 'Cotización eliminada' };
  }
}
