import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { EstadoSuscripcion } from 'src/suscripciones/dto/enum-sucripcion';
import { MAPEO_DEFAULT, MAPEO_DEFAULT_MODULOS, PLAN_CUENTAS_PARAGUAY } from '../seeds/plan-cuentas-paraguay.seed';

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

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Carga el plan de cuentas Paraguay para una empresa específica.
   * Si empresa_id es null, carga como plan maestro del sistema.
   * Es idempotente: usa upsert por código.
   */
  async seedPlanCuentas(empresaId: string | null = null): Promise<{ insertadas: number; omitidas: number }> {
    let insertadas = 0;
    let omitidas = 0;
    const modulosActivos = await this.obtenerModulosActivos(empresaId);

    // Construir mapa de código → id para resolver referencias de padre
    const mapaIds = new Map<string, string>();

    // Procesar nivel a nivel para que el padre exista antes que los hijos
    const niveles = [1, 2, 3, 4];

    for (const nivel of niveles) {
      const cuentasNivel = PLAN_CUENTAS_PARAGUAY.filter(
        (c) => c.nivel === nivel && this.moduloHabilitado(c.modulo, modulosActivos),
      );

      for (const cuenta of cuentasNivel) {
        // Resolver cuenta padre
        let padreId: string | null = null;
        if (cuenta.nivel > 1) {
          const codigoPadre = this.codigoPadre(cuenta.codigo);
          padreId = mapaIds.get(codigoPadre) ?? null;
        }

        const existente = await this.prisma.cont_plan_cuentas.findFirst({
          where: { empresa_id: empresaId ?? null, codigo: cuenta.codigo },
          select: { id: true },
        });

        if (existente) {
          mapaIds.set(cuenta.codigo, existente.id);
          omitidas++;
          continue;
        }

        const creada = await this.prisma.cont_plan_cuentas.create({
          data: {
            empresa_id: empresaId,
            codigo: cuenta.codigo,
            descripcion: cuenta.descripcion,
            tipo: cuenta.tipo,
            naturaleza: cuenta.naturaleza,
            nivel: cuenta.nivel,
            cuenta_padre_id: padreId,
            acepta_movimientos: cuenta.acepta_movimientos,
            acepta_cc: cuenta.acepta_cc ?? false,
            moneda_fija: cuenta.moneda_fija ?? null,
            is_sistema: true,
          },
          select: { id: true },
        });

        mapaIds.set(cuenta.codigo, creada.id);
        insertadas++;
      }
    }

    this.logger.log(
      `Plan de cuentas seed: ${insertadas} insertadas, ${omitidas} omitidas (empresa: ${empresaId ?? 'sistema'})`,
    );
    return { insertadas, omitidas };
  }

  /**
   * Carga el mapeo de conceptos del sistema a cuentas contables.
   * Requiere que el plan de cuentas ya esté cargado para la empresa.
   */
  async seedMapeoDefault(empresaId: string): Promise<{ mapeados: number; errores: string[] }> {
    let mapeados = 0;
    const errores: string[] = [];
    const modulosActivos = await this.obtenerModulosActivos(empresaId);

    for (const [concepto, codigo] of Object.entries(MAPEO_DEFAULT)) {
      if (!this.moduloHabilitado(MAPEO_DEFAULT_MODULOS[concepto], modulosActivos)) continue;

      const cuenta = await this.prisma.cont_plan_cuentas.findFirst({
        where: { empresa_id: empresaId, codigo },
        select: { id: true },
      });

      if (!cuenta) {
        // Intentar con cuenta maestra del sistema (empresa_id = null)
        const cuentaSistema = await this.prisma.cont_plan_cuentas.findFirst({
          where: { empresa_id: null, codigo },
          select: { id: true },
        });

        if (!cuentaSistema) {
          errores.push(`${concepto}: cuenta ${codigo} no encontrada`);
          continue;
        }
      }

      const cuentaId = (
        await this.prisma.cont_plan_cuentas.findFirst({
          where: {
            OR: [
              { empresa_id: empresaId, codigo },
              { empresa_id: null, codigo },
            ],
          },
          orderBy: { empresa_id: 'desc' }, // empresa específica tiene prioridad
          select: { id: true },
        })
      )?.id;

      if (!cuentaId) continue;

      await this.prisma.cont_mapeo_cuentas.upsert({
        where: { empresa_id_concepto: { empresa_id: empresaId, concepto } },
        update: { cuenta_id: cuentaId },
        create: { empresa_id: empresaId, concepto, cuenta_id: cuentaId },
      });

      mapeados++;
    }

    this.logger.log(`Mapeo default seed: ${mapeados} mapeados, ${errores.length} errores (empresa: ${empresaId})`);
    return { mapeados, errores };
  }

  /**
   * Siembra solo los conceptos faltantes para una empresa existente.
   * No sobrescribe mapeos ya asignados manualmente por el contador.
   */
  async seedMapeoFaltantes(
    empresaId: string,
  ): Promise<{ agregados: number; conceptos: string[]; errores: string[]; cuentas_creadas: number }> {
    const conceptos: string[] = [];
    const errores: string[] = [];
    const modulosActivos = await this.obtenerModulosActivos(empresaId);

    // Primero asegurar que el plan de cuentas tenga todas las cuentas del seed
    // (idempotente: omite las que ya existen, crea las nuevas).
    const planResult = await this.seedPlanCuentas(empresaId);

    const existentes = await this.prisma.cont_mapeo_cuentas.findMany({
      where: { empresa_id: empresaId },
      select: { concepto: true },
    });
    const existentesSet = new Set(existentes.map((e) => e.concepto));

    for (const [concepto, codigo] of Object.entries(MAPEO_DEFAULT)) {
      if (!this.moduloHabilitado(MAPEO_DEFAULT_MODULOS[concepto], modulosActivos)) continue;
      if (existentesSet.has(concepto)) continue;

      const cuenta = await this.prisma.cont_plan_cuentas.findFirst({
        where: {
          OR: [
            { empresa_id: empresaId, codigo },
            { empresa_id: null, codigo },
          ],
        },
        orderBy: { empresa_id: 'desc' },
        select: { id: true },
      });
      if (!cuenta) {
        errores.push(`${concepto}: cuenta ${codigo} no existe en el plan`);
        continue;
      }

      await this.prisma.cont_mapeo_cuentas.create({
        data: { empresa_id: empresaId, concepto, cuenta_id: cuenta.id },
      });
      conceptos.push(concepto);
    }

    this.logger.log(
      `Mapeo faltantes seed: ${conceptos.length} agregados, ${planResult.insertadas} cuentas nuevas (empresa: ${empresaId})`,
    );
    return { agregados: conceptos.length, conceptos, errores, cuentas_creadas: planResult.insertadas };
  }

  private codigoPadre(codigo: string): string {
    const partes = codigo.split('.');
    return partes.slice(0, -1).join('.');
  }

  /**
   * null significa "sin filtro": se usa para el plan maestro del sistema y
   * para empresas sin suscripción activa, igual que ModulosService.
   */
  /**
   * Códigos del plan contra los que se decide si un concepto se siembra.
   *
   * Incluye módulos Y submódulos: algunas funciones no tienen módulo propio en
   * el plan. Las comisiones, por ejemplo, viven bajo Cobranzas y se habilitan
   * con el submódulo COB_COMISIONES; gatearlas por un módulo "COMISIONES" que
   * el plan no trae dejaba sus conceptos sin sembrar en empresas que liquidan
   * comisiones todos los meses.
   */
  private async obtenerModulosActivos(empresaId: string | null): Promise<Set<string> | null> {
    if (!empresaId) return null;

    const suscripcion = await this.prisma.suscripciones.findFirst({
      where: {
        empresa_id: empresaId,
        estado: {
          in: [EstadoSuscripcion.Activo, EstadoSuscripcion.EnGracia, EstadoSuscripcion.Pausado],
        },
      },
      orderBy: { created_at: 'desc' },
      select: { id: true },
    });

    if (!suscripcion) return null;

    const modulos = await this.prisma.suscripcion_modulos.findMany({
      where: {
        suscripcion_id: suscripcion.id,
        activo: true,
        modulos: { active: true },
      },
      select: { modulos: { select: { codigo: true } } },
    });

    const submodulos = await this.prisma.suscripcion_submodulos.findMany({
      where: {
        suscripcion_id: suscripcion.id,
        active: true,
        submodulos: { active: true },
      },
      select: { submodulos: { select: { codigo: true } } },
    });

    return new Set(
      [
        ...modulos.map((m) => m.modulos?.codigo),
        ...submodulos.map((s) => s.submodulos?.codigo),
      ].filter((codigo): codigo is string => Boolean(codigo)),
    );
  }

  private moduloHabilitado(modulo: string | undefined, modulosActivos: Set<string> | null): boolean {
    if (!modulo || modulosActivos === null) return true;
    return modulosActivos.has(modulo);
  }
}
