import { create, fragment } from 'xmlbuilder2';
import { CFDI, CFDIType } from '../entities/cfdi.entity';
import { CFDIConfig } from '../entities/cfdi-config.entity';

export interface CFDIConcepto {
  claveProdServ: string;
  claveUnidad: string;
  cantidad: number;
  unidad: string;
  descripcion: string;
  valorUnitario: number;
  importe: number;
  descuento?: number;
  objetoImp: string;
  impuestos?: {
    traslados?: {
      base: number;
      impuesto: string;
      tipoFactor: string;
      tasaOCuota: number;
      importe: number;
    }[];
    retenciones?: {
      base: number;
      impuesto: string;
      tipoFactor: string;
      tasaOCuota: number;
      importe: number;
    }[];
  };
}

export interface CFDIImpuestos {
  traslados?: {
    impuesto: string;
    tipoFactor: string;
    tasaOCuota: number;
    importe: number;
    base: number;
  }[];
  retenciones?: {
    impuesto: string;
    importe: number;
  }[];
}

export interface CFDIData {
  version: string;
  serie: string;
  folio: number;
  fecha: string;
  formaPago: string;
  metodoPago: string;
  tipoDeComprobante: string;
  exportacion: string;
  lugarExpedicion: string;
  moneda: string;
  tipoCambio?: number;
  subTotal: number;
  descuento?: number;
  total: number;
  condicionesDePago?: string;
  emisor: {
    rfc: string;
    nombre: string;
    regimenFiscal: string;
  };
  receptor: {
    rfc: string;
    nombre: string;
    usoCFDI: string;
    domicilioFiscalReceptor?: string;
    regimenFiscalReceptor?: string;
  };
  conceptos: CFDIConcepto[];
  impuestos?: CFDIImpuestos;
  cfdiRelacionados?: {
    tipoRelacion: string;
    uuids: string[];
  };
}

export interface TimbreData {
  version: string;
  uuid: string;
  fechaTimbrado: string;
  rfcProvCertif: string;
  selloCFD: string;
  noCertificadoSAT: string;
  selloSAT: string;
}

const CFDI_NAMESPACE = 'http://www.sat.gob.mx/cfd/4';
const TIMBRE_NAMESPACE = 'http://www.sat.gob.mx/TimbreFiscalDigital';
const XSI_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-instance';

export class XMLService {
  /**
   * Build CFDI XML without stamps (for signing)
   */
  buildCFDIXML(data: CFDIData, certificateNumber: string): string {
    const schemaLocation = `${CFDI_NAMESPACE} http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd`;

    const doc = create({ version: '1.0', encoding: 'UTF-8' })
      .ele(CFDI_NAMESPACE, 'cfdi:Comprobante')
      .att('xmlns:cfdi', CFDI_NAMESPACE)
      .att('xmlns:xsi', XSI_NAMESPACE)
      .att('xsi:schemaLocation', schemaLocation)
      .att('Version', data.version)
      .att('Serie', data.serie)
      .att('Folio', data.folio.toString())
      .att('Fecha', data.fecha)
      .att('FormaPago', data.formaPago)
      .att('SubTotal', this.formatMoney(data.subTotal))
      .att('Moneda', data.moneda)
      .att('Total', this.formatMoney(data.total))
      .att('TipoDeComprobante', data.tipoDeComprobante)
      .att('Exportacion', data.exportacion)
      .att('MetodoPago', data.metodoPago)
      .att('LugarExpedicion', data.lugarExpedicion)
      .att('NoCertificado', certificateNumber);

    if (data.tipoCambio && data.tipoCambio !== 1) {
      doc.att('TipoCambio', data.tipoCambio.toString());
    }

    if (data.descuento && data.descuento > 0) {
      doc.att('Descuento', this.formatMoney(data.descuento));
    }

    if (data.condicionesDePago) {
      doc.att('CondicionesDePago', data.condicionesDePago);
    }

    // CFDI Relacionados
    if (data.cfdiRelacionados && data.cfdiRelacionados.uuids.length > 0) {
      const relacionados = doc.ele('cfdi:CfdiRelacionados')
        .att('TipoRelacion', data.cfdiRelacionados.tipoRelacion);

      for (const uuid of data.cfdiRelacionados.uuids) {
        relacionados.ele('cfdi:CfdiRelacionado').att('UUID', uuid);
      }
    }

    // Emisor
    doc.ele('cfdi:Emisor')
      .att('Rfc', data.emisor.rfc)
      .att('Nombre', data.emisor.nombre)
      .att('RegimenFiscal', data.emisor.regimenFiscal);

    // Receptor
    const receptor = doc.ele('cfdi:Receptor')
      .att('Rfc', data.receptor.rfc)
      .att('Nombre', data.receptor.nombre)
      .att('UsoCFDI', data.receptor.usoCFDI);

    if (data.receptor.domicilioFiscalReceptor) {
      receptor.att('DomicilioFiscalReceptor', data.receptor.domicilioFiscalReceptor);
    }
    if (data.receptor.regimenFiscalReceptor) {
      receptor.att('RegimenFiscalReceptor', data.receptor.regimenFiscalReceptor);
    }

    // Conceptos
    const conceptos = doc.ele('cfdi:Conceptos');
    for (const concepto of data.conceptos) {
      const conceptoNode = conceptos.ele('cfdi:Concepto')
        .att('ClaveProdServ', concepto.claveProdServ)
        .att('Cantidad', concepto.cantidad.toString())
        .att('ClaveUnidad', concepto.claveUnidad)
        .att('Unidad', concepto.unidad)
        .att('Descripcion', concepto.descripcion)
        .att('ValorUnitario', this.formatMoney(concepto.valorUnitario))
        .att('Importe', this.formatMoney(concepto.importe))
        .att('ObjetoImp', concepto.objetoImp);

      if (concepto.descuento && concepto.descuento > 0) {
        conceptoNode.att('Descuento', this.formatMoney(concepto.descuento));
      }

      // Impuestos del concepto
      if (concepto.impuestos) {
        const impuestosNode = conceptoNode.ele('cfdi:Impuestos');

        if (concepto.impuestos.traslados && concepto.impuestos.traslados.length > 0) {
          const trasladosNode = impuestosNode.ele('cfdi:Traslados');
          for (const traslado of concepto.impuestos.traslados) {
            trasladosNode.ele('cfdi:Traslado')
              .att('Base', this.formatMoney(traslado.base))
              .att('Impuesto', traslado.impuesto)
              .att('TipoFactor', traslado.tipoFactor)
              .att('TasaOCuota', this.formatRate(traslado.tasaOCuota))
              .att('Importe', this.formatMoney(traslado.importe));
          }
        }

        if (concepto.impuestos.retenciones && concepto.impuestos.retenciones.length > 0) {
          const retencionesNode = impuestosNode.ele('cfdi:Retenciones');
          for (const retencion of concepto.impuestos.retenciones) {
            retencionesNode.ele('cfdi:Retencion')
              .att('Base', this.formatMoney(retencion.base))
              .att('Impuesto', retencion.impuesto)
              .att('TipoFactor', retencion.tipoFactor)
              .att('TasaOCuota', this.formatRate(retencion.tasaOCuota))
              .att('Importe', this.formatMoney(retencion.importe));
          }
        }
      }
    }

    // Impuestos generales
    if (data.impuestos) {
      const impuestosNode = doc.ele('cfdi:Impuestos');

      if (data.impuestos.retenciones && data.impuestos.retenciones.length > 0) {
        const totalRetenido = data.impuestos.retenciones.reduce((sum, r) => sum + r.importe, 0);
        impuestosNode.att('TotalImpuestosRetenidos', this.formatMoney(totalRetenido));

        const retencionesNode = impuestosNode.ele('cfdi:Retenciones');
        for (const retencion of data.impuestos.retenciones) {
          retencionesNode.ele('cfdi:Retencion')
            .att('Impuesto', retencion.impuesto)
            .att('Importe', this.formatMoney(retencion.importe));
        }
      }

      if (data.impuestos.traslados && data.impuestos.traslados.length > 0) {
        const totalTrasladado = data.impuestos.traslados.reduce((sum, t) => sum + t.importe, 0);
        impuestosNode.att('TotalImpuestosTrasladados', this.formatMoney(totalTrasladado));

        const trasladosNode = impuestosNode.ele('cfdi:Traslados');
        for (const traslado of data.impuestos.traslados) {
          trasladosNode.ele('cfdi:Traslado')
            .att('Base', this.formatMoney(traslado.base))
            .att('Impuesto', traslado.impuesto)
            .att('TipoFactor', traslado.tipoFactor)
            .att('TasaOCuota', this.formatRate(traslado.tasaOCuota))
            .att('Importe', this.formatMoney(traslado.importe));
        }
      }
    }

    return doc.end({ prettyPrint: false });
  }

  /**
   * Add seal (sello) to XML
   */
  addSealToXML(xml: string, sello: string): string {
    // Insert Sello attribute after NoCertificado
    return xml.replace(
      /NoCertificado="([^"]+)"/,
      `NoCertificado="$1" Sello="${sello}"`
    );
  }

  /**
   * Add certificate to XML
   */
  addCertificateToXML(xml: string, certificateBase64: string): string {
    // Insert Certificado attribute after Sello
    return xml.replace(
      /Sello="([^"]+)"/,
      `Sello="$1" Certificado="${certificateBase64}"`
    );
  }

  /**
   * Add TimbreFiscalDigital complement to stamped XML
   */
  addTimbreToXML(xml: string, timbre: TimbreData): string {
    const timbreFragment = fragment()
      .ele(TIMBRE_NAMESPACE, 'tfd:TimbreFiscalDigital')
      .att('xmlns:tfd', TIMBRE_NAMESPACE)
      .att('xmlns:xsi', XSI_NAMESPACE)
      .att('xsi:schemaLocation', `${TIMBRE_NAMESPACE} http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd`)
      .att('Version', timbre.version)
      .att('UUID', timbre.uuid)
      .att('FechaTimbrado', timbre.fechaTimbrado)
      .att('RfcProvCertif', timbre.rfcProvCertif)
      .att('SelloCFD', timbre.selloCFD)
      .att('NoCertificadoSAT', timbre.noCertificadoSAT)
      .att('SelloSAT', timbre.selloSAT)
      .end();

    // Find closing tag and insert Complemento before it
    const insertPoint = xml.indexOf('</cfdi:Comprobante>');
    if (insertPoint === -1) {
      throw new Error('Invalid CFDI XML: missing closing tag');
    }

    return xml.slice(0, insertPoint) +
           '<cfdi:Complemento>' + timbreFragment + '</cfdi:Complemento>' +
           xml.slice(insertPoint);
  }

  /**
   * Build original chain for signing (cadena original)
   */
  buildCadenaOriginal(data: CFDIData, certificateNumber: string): string {
    const parts: string[] = [];

    // Version and main attributes
    parts.push(data.version);
    parts.push(data.serie);
    parts.push(data.folio.toString());
    parts.push(data.fecha);
    parts.push(data.formaPago);
    parts.push(certificateNumber);
    if (data.condicionesDePago) parts.push(data.condicionesDePago);
    parts.push(this.formatMoney(data.subTotal));
    if (data.descuento && data.descuento > 0) {
      parts.push(this.formatMoney(data.descuento));
    }
    parts.push(data.moneda);
    if (data.tipoCambio && data.tipoCambio !== 1) {
      parts.push(data.tipoCambio.toString());
    }
    parts.push(this.formatMoney(data.total));
    parts.push(data.tipoDeComprobante);
    parts.push(data.exportacion);
    parts.push(data.metodoPago);
    parts.push(data.lugarExpedicion);

    // CFDI Relacionados
    if (data.cfdiRelacionados && data.cfdiRelacionados.uuids.length > 0) {
      parts.push(data.cfdiRelacionados.tipoRelacion);
      for (const uuid of data.cfdiRelacionados.uuids) {
        parts.push(uuid);
      }
    }

    // Emisor
    parts.push(data.emisor.rfc);
    parts.push(data.emisor.nombre);
    parts.push(data.emisor.regimenFiscal);

    // Receptor
    parts.push(data.receptor.rfc);
    parts.push(data.receptor.nombre);
    if (data.receptor.domicilioFiscalReceptor) {
      parts.push(data.receptor.domicilioFiscalReceptor);
    }
    if (data.receptor.regimenFiscalReceptor) {
      parts.push(data.receptor.regimenFiscalReceptor);
    }
    parts.push(data.receptor.usoCFDI);

    // Conceptos
    for (const concepto of data.conceptos) {
      parts.push(concepto.claveProdServ);
      parts.push(concepto.cantidad.toString());
      parts.push(concepto.claveUnidad);
      parts.push(concepto.unidad);
      parts.push(concepto.descripcion);
      parts.push(this.formatMoney(concepto.valorUnitario));
      parts.push(this.formatMoney(concepto.importe));
      if (concepto.descuento && concepto.descuento > 0) {
        parts.push(this.formatMoney(concepto.descuento));
      }
      parts.push(concepto.objetoImp);

      // Impuestos del concepto
      if (concepto.impuestos) {
        if (concepto.impuestos.traslados) {
          for (const traslado of concepto.impuestos.traslados) {
            parts.push(this.formatMoney(traslado.base));
            parts.push(traslado.impuesto);
            parts.push(traslado.tipoFactor);
            parts.push(this.formatRate(traslado.tasaOCuota));
            parts.push(this.formatMoney(traslado.importe));
          }
        }
        if (concepto.impuestos.retenciones) {
          for (const retencion of concepto.impuestos.retenciones) {
            parts.push(this.formatMoney(retencion.base));
            parts.push(retencion.impuesto);
            parts.push(retencion.tipoFactor);
            parts.push(this.formatRate(retencion.tasaOCuota));
            parts.push(this.formatMoney(retencion.importe));
          }
        }
      }
    }

    // Impuestos generales
    if (data.impuestos) {
      if (data.impuestos.retenciones) {
        const totalRetenido = data.impuestos.retenciones.reduce((sum, r) => sum + r.importe, 0);
        parts.push(this.formatMoney(totalRetenido));
        for (const retencion of data.impuestos.retenciones) {
          parts.push(retencion.impuesto);
          parts.push(this.formatMoney(retencion.importe));
        }
      }
      if (data.impuestos.traslados) {
        const totalTrasladado = data.impuestos.traslados.reduce((sum, t) => sum + t.importe, 0);
        parts.push(this.formatMoney(totalTrasladado));
        for (const traslado of data.impuestos.traslados) {
          parts.push(this.formatMoney(traslado.base));
          parts.push(traslado.impuesto);
          parts.push(traslado.tipoFactor);
          parts.push(this.formatRate(traslado.tasaOCuota));
          parts.push(this.formatMoney(traslado.importe));
        }
      }
    }

    return '||' + parts.join('|') + '||';
  }

  /**
   * Parse stamped XML response from PAC
   */
  parseStampedXML(xml: string): {
    uuid: string;
    fechaTimbrado: string;
    selloCFD: string;
    noCertificadoSAT: string;
    selloSAT: string;
    rfcProvCertif: string;
    cadenaOriginalSAT: string;
  } {
    // Extract TimbreFiscalDigital attributes using regex (faster than full XML parse)
    const uuidMatch = xml.match(/UUID="([^"]+)"/);
    const fechaMatch = xml.match(/FechaTimbrado="([^"]+)"/);
    const selloCFDMatch = xml.match(/SelloCFD="([^"]+)"/);
    const noCertMatch = xml.match(/NoCertificadoSAT="([^"]+)"/);
    const selloSATMatch = xml.match(/SelloSAT="([^"]+)"/);
    const rfcProvMatch = xml.match(/RfcProvCertif="([^"]+)"/);

    if (!uuidMatch || !fechaMatch || !selloSATMatch || !noCertMatch) {
      throw new Error('Invalid stamped XML: missing TimbreFiscalDigital attributes');
    }

    // Build SAT cadena original from timbre
    const cadenaOriginalSAT = this.buildCadenaOriginalSAT({
      version: '1.1',
      uuid: uuidMatch[1],
      fechaTimbrado: fechaMatch[1],
      rfcProvCertif: rfcProvMatch?.[1] || '',
      selloCFD: selloCFDMatch?.[1] || '',
      noCertificadoSAT: noCertMatch[1],
      selloSAT: selloSATMatch[1],
    });

    return {
      uuid: uuidMatch[1],
      fechaTimbrado: fechaMatch[1],
      selloCFD: selloCFDMatch?.[1] || '',
      noCertificadoSAT: noCertMatch[1],
      selloSAT: selloSATMatch[1],
      rfcProvCertif: rfcProvMatch?.[1] || '',
      cadenaOriginalSAT,
    };
  }

  /**
   * Build SAT cadena original from timbre
   */
  private buildCadenaOriginalSAT(timbre: TimbreData): string {
    const parts: string[] = [];
    parts.push(timbre.version);
    parts.push(timbre.uuid);
    parts.push(timbre.fechaTimbrado);
    parts.push(timbre.rfcProvCertif);
    parts.push(timbre.selloCFD);
    parts.push(timbre.noCertificadoSAT);
    return '||' + parts.join('|') + '||';
  }

  /**
   * Generate QR code data string
   */
  generateQRData(cfdi: CFDI): string {
    // Format: https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=uuid&re=rfcEmisor&rr=rfcReceptor&tt=total&fe=ultimos8SelloCFD
    const total = cfdi.total.toFixed(2).padStart(17, '0');
    const selloLast8 = cfdi.selloCFDI?.slice(-8) || '00000000';

    return `https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=${cfdi.uuid}&re=${cfdi.emisorRfc}&rr=${cfdi.receptorRfc}&tt=${total}&fe=${selloLast8}`;
  }

  /**
   * Format money value for CFDI (2 decimal places)
   */
  private formatMoney(value: number): string {
    return value.toFixed(2);
  }

  /**
   * Format rate/percentage for CFDI (6 decimal places)
   */
  private formatRate(value: number): string {
    return value.toFixed(6);
  }

  /**
   * Format date for CFDI (ISO 8601 without timezone)
   */
  formatCFDIDate(date: Date): string {
    return date.toISOString().replace('Z', '').slice(0, 19);
  }

  /**
   * Validate XML structure
   */
  validateXMLStructure(xml: string): { valid: boolean; errors: string[] } {
    const errors: string[] = [];

    // Check required elements
    if (!xml.includes('cfdi:Comprobante')) {
      errors.push('Missing Comprobante element');
    }
    if (!xml.includes('cfdi:Emisor')) {
      errors.push('Missing Emisor element');
    }
    if (!xml.includes('cfdi:Receptor')) {
      errors.push('Missing Receptor element');
    }
    if (!xml.includes('cfdi:Conceptos')) {
      errors.push('Missing Conceptos element');
    }

    // Check version
    if (!xml.includes('Version="4.0"')) {
      errors.push('Invalid or missing CFDI version (expected 4.0)');
    }

    return {
      valid: errors.length === 0,
      errors,
    };
  }
}
