/**
 * ERPIntegrationService - Servicio de Integración con ERPs
 *
 * Exportación de datos a SAP, CONTPAQi y otros sistemas.
 *
 * @module Finance
 */

import { DataSource, Repository, IsNull, Between } from 'typeorm';
import {
  ChartOfAccounts,
  AccountType,
  AccountingEntry,
  AccountingEntryLine,
} from '../entities';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

interface SAPExportEntry {
  BUKRS: string; // Sociedad
  BELNR: string; // Número de documento
  GJAHR: number; // Ejercicio
  BLART: string; // Clase de documento
  BLDAT: string; // Fecha de documento
  BUDAT: string; // Fecha de contabilización
  MONAT: number; // Periodo
  WAERS: string; // Moneda
  KURSF: number; // Tipo de cambio
  BKTXT: string; // Texto de cabecera
  lines: SAPExportLine[];
}

interface SAPExportLine {
  BUZEI: number; // Posición
  BSCHL: string; // Clave de contabilización
  HKONT: string; // Cuenta
  WRBTR: number; // Importe en moneda del documento
  DMBTR: number; // Importe en moneda local
  SGTXT: string; // Texto
  ZUONR?: string; // Asignación
  KOSTL?: string; // Centro de costo
  PROJK?: string; // Elemento PEP
}

interface CONTPAQiPoliza {
  Tipo: number;
  Folio: number;
  Fecha: string;
  Concepto: string;
  Diario: number;
  Movimientos: CONTPAQiMovimiento[];
}

interface CONTPAQiMovimiento {
  NumMovto: number;
  Cuenta: string;
  Concepto: string;
  Cargo: number;
  Abono: number;
  Referencia?: string;
  Diario?: number;
}

interface ExportResult {
  success: boolean;
  format: string;
  recordCount: number;
  data: string | object;
  filename: string;
  errors?: string[];
}

export class ERPIntegrationService {
  private accountRepository: Repository<ChartOfAccounts>;
  private entryRepository: Repository<AccountingEntry>;
  private lineRepository: Repository<AccountingEntryLine>;
  dataSource: DataSource;

  constructor(dataSource: DataSource) {
    this.dataSource = dataSource;
    this.accountRepository = dataSource.getRepository(ChartOfAccounts);
    this.entryRepository = dataSource.getRepository(AccountingEntry);
    this.lineRepository = dataSource.getRepository(AccountingEntryLine);
  }

  // ==================== EXPORTACIÓN SAP ====================

  async exportToSAP(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date,
    options: {
      companyCode?: string;
      documentType?: string;
      journalNumber?: number;
    } = {}
  ): Promise<ExportResult> {
    const { companyCode = '1000' } = options;

    const entries = await this.entryRepository.find({
      where: {
        tenantId: ctx.tenantId,
        status: 'posted',
        entryDate: Between(periodStart, periodEnd),
        deletedAt: IsNull(),
      },
      relations: ['lines'],
      order: { entryDate: 'ASC', entryNumber: 'ASC' },
    });

    const sapEntries: SAPExportEntry[] = entries.map((entry) => ({
      BUKRS: companyCode,
      BELNR: entry.entryNumber.replace(/[^0-9]/g, '').slice(-10).padStart(10, '0'),
      GJAHR: entry.fiscalYear,
      BLART: this.mapEntryTypeToSAP(entry.entryType),
      BLDAT: this.formatDateSAP(entry.entryDate),
      BUDAT: this.formatDateSAP(entry.entryDate),
      MONAT: entry.fiscalPeriod,
      WAERS: entry.currency,
      KURSF: entry.exchangeRate,
      BKTXT: entry.description.slice(0, 25),
      lines: (entry.lines || []).map((line, idx) => ({
        BUZEI: idx + 1,
        BSCHL: line.debit > 0 ? '40' : '50', // 40=Debe, 50=Haber
        HKONT: line.accountCode.replace(/\./g, '').padStart(10, '0'),
        WRBTR: Math.abs(line.debit || line.credit),
        DMBTR: Math.abs(line.debit || line.credit) * entry.exchangeRate,
        SGTXT: (line.description || entry.description).slice(0, 50),
        ZUONR: line.description?.slice(0, 18),
        KOSTL: line.costCenterId?.slice(0, 10),
        PROJK: line.projectId?.slice(0, 24),
      })),
    }));

    // Generar archivo texto para LSMW o BAPI
    const lines: string[] = [];

    for (const entry of sapEntries) {
      // Cabecera
      lines.push(
        `H|${entry.BUKRS}|${entry.BELNR}|${entry.GJAHR}|${entry.BLART}|${entry.BLDAT}|${entry.BUDAT}|${entry.MONAT}|${entry.WAERS}|${entry.KURSF}|${entry.BKTXT}`
      );

      // Posiciones
      for (const line of entry.lines) {
        lines.push(
          `L|${line.BUZEI}|${line.BSCHL}|${line.HKONT}|${line.WRBTR}|${line.DMBTR}|${line.SGTXT}|${line.ZUONR || ''}|${line.KOSTL || ''}|${line.PROJK || ''}`
        );
      }
    }

    const filename = `SAP_POLIZAS_${periodStart.toISOString().split('T')[0]}_${periodEnd.toISOString().split('T')[0]}.txt`;

    return {
      success: true,
      format: 'SAP-LSMW',
      recordCount: entries.length,
      data: lines.join('\n'),
      filename,
    };
  }

  private mapEntryTypeToSAP(entryType: string): string {
    const mapping: Record<string, string> = {
      purchase: 'KR', // Factura de acreedor
      sale: 'DR', // Factura de deudor
      payment: 'KZ', // Pago a acreedor
      collection: 'DZ', // Cobro de deudor
      payroll: 'PR', // Nómina
      adjustment: 'SA', // Documento contable
      depreciation: 'AF', // Amortización
      transfer: 'SA', // Traspaso
      opening: 'AB', // Apertura
      closing: 'SB', // Cierre
    };
    return mapping[entryType] || 'SA';
  }

  private formatDateSAP(date: Date): string {
    const d = new Date(date);
    const year = d.getFullYear();
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    return `${year}${month}${day}`;
  }

  // ==================== EXPORTACIÓN CONTPAQi ====================

  async exportToCONTPAQi(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date,
    options: {
      polizaTipo?: number;
      diario?: number;
    } = {}
  ): Promise<ExportResult> {
    const { diario = 1 } = options; // 1=Diario

    const entries = await this.entryRepository.find({
      where: {
        tenantId: ctx.tenantId,
        status: 'posted',
        entryDate: Between(periodStart, periodEnd),
        deletedAt: IsNull(),
      },
      relations: ['lines'],
      order: { entryDate: 'ASC', entryNumber: 'ASC' },
    });

    const polizas: CONTPAQiPoliza[] = entries.map((entry, idx) => ({
      Tipo: this.mapEntryTypeToCONTPAQi(entry.entryType),
      Folio: idx + 1,
      Fecha: this.formatDateCONTPAQi(entry.entryDate),
      Concepto: entry.description.slice(0, 200),
      Diario: diario,
      Movimientos: (entry.lines || []).map((line, lineIdx) => ({
        NumMovto: lineIdx + 1,
        Cuenta: line.accountCode,
        Concepto: (line.description || entry.description).slice(0, 200),
        Cargo: line.debit,
        Abono: line.credit,
        Referencia: line.description?.slice(0, 20),
        Diario: diario,
      })),
    }));

    // Formato texto para importación CONTPAQi
    const lines: string[] = [];

    for (const poliza of polizas) {
      // Cabecera de póliza
      lines.push(`P,${poliza.Tipo},${poliza.Folio},${poliza.Fecha},"${poliza.Concepto}",${poliza.Diario}`);

      // Movimientos
      for (const mov of poliza.Movimientos) {
        lines.push(
          `M,${mov.NumMovto},"${mov.Cuenta}","${mov.Concepto}",${mov.Cargo.toFixed(2)},${mov.Abono.toFixed(2)},"${mov.Referencia || ''}"`
        );
      }
    }

    const filename = `CONTPAQI_POLIZAS_${periodStart.toISOString().split('T')[0]}_${periodEnd.toISOString().split('T')[0]}.txt`;

    return {
      success: true,
      format: 'CONTPAQi-TXT',
      recordCount: entries.length,
      data: lines.join('\n'),
      filename,
    };
  }

  private mapEntryTypeToCONTPAQi(entryType: string): number {
    const mapping: Record<string, number> = {
      purchase: 2, // Egresos
      sale: 1, // Ingresos
      payment: 2, // Egresos
      collection: 1, // Ingresos
      payroll: 3, // Nómina
      adjustment: 4, // Diario
      depreciation: 4, // Diario
      transfer: 4, // Diario
      opening: 4, // Diario
      closing: 4, // Diario
    };
    return mapping[entryType] || 4;
  }

  private formatDateCONTPAQi(date: Date): string {
    const d = new Date(date);
    const day = String(d.getDate()).padStart(2, '0');
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const year = d.getFullYear();
    return `${day}/${month}/${year}`;
  }

  // ==================== EXPORTACIÓN XML CFDI ====================

  async exportCFDIPolizas(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date,
    options: {
      tipoSolicitud?: string;
      numOrden?: string;
      numTramite?: string;
    } = {}
  ): Promise<ExportResult> {
    const { tipoSolicitud = 'AF', numOrden, numTramite } = options;

    const entries = await this.entryRepository.find({
      where: {
        tenantId: ctx.tenantId,
        status: 'posted',
        entryDate: Between(periodStart, periodEnd),
        deletedAt: IsNull(),
      },
      relations: ['lines'],
      order: { entryDate: 'ASC' },
    });

    // Generar XML según Anexo 24 del SAT
    const xml = `<?xml version="1.0" encoding="UTF-8"?>
<PLZ:Polizas
  xmlns:PLZ="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo/PolizasPeriodo_1_3.xsd"
  Version="1.3"
  TipoSolicitud="${tipoSolicitud}"
  ${numOrden ? `NumOrden="${numOrden}"` : ''}
  ${numTramite ? `NumTramite="${numTramite}"` : ''}
  Anio="${periodStart.getFullYear()}"
  Mes="${String(periodStart.getMonth() + 1).padStart(2, '0')}"
>
${entries.map((entry) => this.generatePolizaXML(entry)).join('\n')}
</PLZ:Polizas>`;

    const filename = `CFDI_POLIZAS_${periodStart.getFullYear()}${String(periodStart.getMonth() + 1).padStart(2, '0')}.xml`;

    return {
      success: true,
      format: 'CFDI-XML',
      recordCount: entries.length,
      data: xml,
      filename,
    };
  }

  private generatePolizaXML(entry: AccountingEntry): string {
    const fecha = new Date(entry.entryDate).toISOString().split('T')[0];
    // Map entry type for CFDI (reserved for future use)
    this.mapEntryTypeToCFDI(entry.entryType);

    const transacciones = (entry.lines || [])
      .map((line) => {
        return `    <PLZ:Transaccion
      NumCta="${this.escapeXML(line.accountCode)}"
      DesCta="${this.escapeXML(line.description || '')}"
      Concepto="${this.escapeXML(entry.description)}"
      Debe="${line.debit.toFixed(2)}"
      Haber="${line.credit.toFixed(2)}"
    />`;
      })
      .join('\n');

    return `  <PLZ:Poliza
    NumUnIdenPol="${this.escapeXML(entry.entryNumber)}"
    Fecha="${fecha}"
    Concepto="${this.escapeXML(entry.description)}"
  >
${transacciones}
  </PLZ:Poliza>`;
  }

  private mapEntryTypeToCFDI(entryType: string): string {
    const mapping: Record<string, string> = {
      purchase: 'Eg',
      sale: 'In',
      payment: 'Eg',
      collection: 'In',
      payroll: 'No',
      adjustment: 'Di',
      depreciation: 'Di',
      transfer: 'Di',
      opening: 'Di',
      closing: 'Di',
    };
    return mapping[entryType] || 'Di';
  }

  private escapeXML(str: string): string {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&apos;');
  }

  // ==================== EXPORTACIÓN CATÁLOGO DE CUENTAS ====================

  async exportChartOfAccounts(
    ctx: ServiceContext,
    format: 'csv' | 'xml' | 'json' = 'csv'
  ): Promise<ExportResult> {
    const accounts = await this.accountRepository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
      order: { code: 'ASC' },
    });

    let data: string | object;
    let filename: string;

    switch (format) {
      case 'xml':
        data = this.generateChartXML(accounts);
        filename = `CATALOGO_CUENTAS.xml`;
        break;

      case 'json':
        data = accounts.map((acc) => ({
          code: acc.code,
          name: acc.name,
          type: acc.accountType,
          nature: acc.nature,
          level: acc.level,
          parentCode: acc.parentId,
          satCode: acc.sapCode,
          status: acc.status,
        }));
        filename = `CATALOGO_CUENTAS.json`;
        break;

      default:
        const rows = [
          'Código,Nombre,Tipo,Naturaleza,Nivel,Código SAT,Estado',
          ...accounts.map(
            (acc) =>
              `"${acc.code}","${acc.name}","${acc.accountType}","${acc.nature}",${acc.level},"${acc.sapCode || ''}","${acc.status}"`
          ),
        ];
        data = rows.join('\n');
        filename = `CATALOGO_CUENTAS.csv`;
    }

    return {
      success: true,
      format,
      recordCount: accounts.length,
      data,
      filename,
    };
  }

  private generateChartXML(accounts: ChartOfAccounts[]): string {
    const cuentas = accounts
      .map(
        (acc) => `  <Cuenta
    CodAgrup="${acc.sapCode || ''}"
    NumCta="${acc.code}"
    Desc="${this.escapeXML(acc.name)}"
    Nivel="${acc.level}"
    Natur="${acc.nature === 'debit' ? 'D' : 'A'}"
  />`
      )
      .join('\n');

    return `<?xml version="1.0" encoding="UTF-8"?>
<BCE:Catalogo
  xmlns:BCE="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/CatalogoCuentas"
  Version="1.3"
  Anio="${new Date().getFullYear()}"
  Mes="${String(new Date().getMonth() + 1).padStart(2, '0')}"
>
${cuentas}
</BCE:Catalogo>`;
  }

  // ==================== EXPORTACIÓN BALANZA ====================

  async exportTrialBalance(
    ctx: ServiceContext,
    periodStart: Date,
    periodEnd: Date,
    format: 'csv' | 'xml' | 'json' = 'csv'
  ): Promise<ExportResult> {
    // Obtener balanza
    const accounts = await this.accountRepository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
        allowsDirectPosting: true,
      },
      order: { code: 'ASC' },
    });

    // Obtener saldos
    const balances = await this.lineRepository
      .createQueryBuilder('line')
      .innerJoin('line.entry', 'entry')
      .select([
        'line.accountCode as "accountCode"',
        'SUM(line.debit) as "debit"',
        'SUM(line.credit) as "credit"',
      ])
      .where('entry.tenantId = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('entry.status = :status', { status: 'posted' })
      .andWhere('entry.entryDate BETWEEN :periodStart AND :periodEnd', {
        periodStart,
        periodEnd,
      })
      .groupBy('line.accountCode')
      .getRawMany();

    const balanceMap = new Map(
      balances.map((b) => [
        b.accountCode,
        {
          debit: parseFloat(b.debit) || 0,
          credit: parseFloat(b.credit) || 0,
        },
      ])
    );

    const rows = accounts
      .map((acc) => {
        const bal = balanceMap.get(acc.code) || { debit: 0, credit: 0 };
        return {
          code: acc.code,
          name: acc.name,
          initialDebit: 0,
          initialCredit: 0,
          periodDebit: bal.debit,
          periodCredit: bal.credit,
          finalDebit: bal.debit,
          finalCredit: bal.credit,
        };
      })
      .filter((r) => r.periodDebit > 0 || r.periodCredit > 0);

    let data: string | object;
    let filename: string;

    switch (format) {
      case 'xml':
        data = this.generateBalanzaXML(rows, periodStart);
        filename = `BALANZA_${periodStart.getFullYear()}${String(periodStart.getMonth() + 1).padStart(2, '0')}.xml`;
        break;

      case 'json':
        data = rows;
        filename = `BALANZA_${periodStart.getFullYear()}${String(periodStart.getMonth() + 1).padStart(2, '0')}.json`;
        break;

      default:
        const csvRows = [
          'Cuenta,Nombre,Saldo Inicial Debe,Saldo Inicial Haber,Debe,Haber,Saldo Final Debe,Saldo Final Haber',
          ...rows.map(
            (r) =>
              `"${r.code}","${r.name}",${r.initialDebit.toFixed(2)},${r.initialCredit.toFixed(2)},${r.periodDebit.toFixed(2)},${r.periodCredit.toFixed(2)},${r.finalDebit.toFixed(2)},${r.finalCredit.toFixed(2)}`
          ),
        ];
        data = csvRows.join('\n');
        filename = `BALANZA_${periodStart.getFullYear()}${String(periodStart.getMonth() + 1).padStart(2, '0')}.csv`;
    }

    return {
      success: true,
      format,
      recordCount: rows.length,
      data,
      filename,
    };
  }

  private generateBalanzaXML(
    rows: {
      code: string;
      name: string;
      initialDebit: number;
      initialCredit: number;
      periodDebit: number;
      periodCredit: number;
      finalDebit: number;
      finalCredit: number;
    }[],
    periodStart: Date
  ): string {
    const cuentas = rows
      .map(
        (r) => `  <BCE:Ctas
    NumCta="${r.code}"
    SaldoIni="${(r.initialDebit - r.initialCredit).toFixed(2)}"
    Debe="${r.periodDebit.toFixed(2)}"
    Haber="${r.periodCredit.toFixed(2)}"
    SaldoFin="${(r.finalDebit - r.finalCredit).toFixed(2)}"
  />`
      )
      .join('\n');

    return `<?xml version="1.0" encoding="UTF-8"?>
<BCE:Balanza
  xmlns:BCE="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/BalanzaComprobacion"
  Version="1.3"
  Anio="${periodStart.getFullYear()}"
  Mes="${String(periodStart.getMonth() + 1).padStart(2, '0')}"
  TipoEnvio="N"
>
${cuentas}
</BCE:Balanza>`;
  }

  // ==================== IMPORTACIÓN ====================

  async importChartOfAccounts(
    ctx: ServiceContext,
    data: string,
    format: 'csv' | 'json'
  ): Promise<{ imported: number; errors: string[] }> {
    const errors: string[] = [];
    let imported = 0;

    let accounts: {
      code: string;
      name: string;
      type: AccountType;
      nature: 'debit' | 'credit';
      level: number;
      parentCode?: string;
      sapCode?: string;
    }[] = [];

    if (format === 'json') {
      accounts = JSON.parse(data);
    } else {
      const lines = data.split('\n').slice(1); // Skip header
      accounts = lines
        .filter((line) => line.trim())
        .map((line) => {
          const parts = line.split(',').map((p) => p.replace(/"/g, '').trim());
          return {
            code: parts[0],
            name: parts[1],
            type: parts[2] as AccountType,
            nature: parts[3] as 'debit' | 'credit',
            level: parseInt(parts[4]) || 1,
            sapCode: parts[5],
          };
        });
    }

    for (const acc of accounts) {
      try {
        const existing = await this.accountRepository.findOne({
          where: {
            tenantId: ctx.tenantId,
            code: acc.code,
            deletedAt: IsNull(),
          },
        });

        if (existing) {
          // Actualizar
          existing.name = acc.name;
          existing.accountType = acc.type;
          existing.nature = acc.nature;
          existing.level = acc.level;
          existing.sapCode = acc.sapCode;
          existing.updatedBy = ctx.userId;
          await this.accountRepository.save(existing);
        } else {
          // Crear
          const newAccount = this.accountRepository.create({
            tenantId: ctx.tenantId,
            code: acc.code,
            name: acc.name,
            accountType: acc.type,
            nature: acc.nature,
            level: acc.level,
            sapCode: acc.sapCode,
            allowsDirectPosting: true,
            status: 'active',
            initialBalance: 0,
            currentBalance: 0,
            createdBy: ctx.userId,
          });
          await this.accountRepository.save(newAccount);
        }

        imported++;
      } catch (error) {
        errors.push(`Error en cuenta ${acc.code}: ${(error as Error).message}`);
      }
    }

    return { imported, errors };
  }
}
