import { XMLParser } from 'fast-xml-parser';
import { createHash, randomUUID } from 'crypto';
import type { Credential } from '@nodecfdi/credentials/node';
import type {
  SatDownloadRequestResponse,
  SatVerifyResponse,
  SatPackageResponse,
  CfdiSyncType
} from '@horux/shared';

const SAT_SOLICITUD_URL = 'https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/SolicitaDescargaService.svc';
const SAT_VERIFICA_URL = 'https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/VerificaSolicitudDescargaService.svc';
const SAT_DESCARGA_URL = 'https://cfdidescargamasaborrar.clouda.sat.gob.mx/DescargaMasivaService.svc';

type TipoSolicitud = 'CFDI' | 'Metadata';

interface RequestDownloadParams {
  credential: Credential;
  token: string;
  rfc: string;
  fechaInicio: Date;
  fechaFin: Date;
  tipoSolicitud: TipoSolicitud;
  tipoCfdi: CfdiSyncType;
}

/**
 * Formatea fecha a formato SAT (YYYY-MM-DDTHH:MM:SS)
 */
function formatSatDate(date: Date): string {
  return date.toISOString().slice(0, 19);
}

/**
 * Construye el XML de solicitud de descarga
 */
function buildDownloadRequest(params: RequestDownloadParams): string {
  const { credential, token, rfc, fechaInicio, fechaFin, tipoSolicitud, tipoCfdi } = params;
  const uuid = randomUUID();

  const certificate = credential.certificate();
  const cerB64 = Buffer.from(certificate.pem().replace(/-----.*-----/g, '').replace(/\n/g, '')).toString('base64');

  // Construir el elemento de solicitud
  const rfcEmisor = tipoCfdi === 'emitidos' ? rfc : undefined;
  const rfcReceptor = tipoCfdi === 'recibidos' ? rfc : undefined;

  const solicitudContent = `<des:RfcSolicitante>${rfc}</des:RfcSolicitante>` +
    `<des:FechaInicial>${formatSatDate(fechaInicio)}</des:FechaInicial>` +
    `<des:FechaFinal>${formatSatDate(fechaFin)}</des:FechaFinal>` +
    `<des:TipoSolicitud>${tipoSolicitud}</des:TipoSolicitud>` +
    (rfcEmisor ? `<des:RfcEmisor>${rfcEmisor}</des:RfcEmisor>` : '') +
    (rfcReceptor ? `<des:RfcReceptor>${rfcReceptor}</des:RfcReceptor>` : '');

  const solicitudToSign = `<des:SolicitaDescarga xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx">${solicitudContent}</des:SolicitaDescarga>`;
  const digestValue = createHash('sha1').update(solicitudToSign).digest('base64');

  const signedInfoXml = `<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">` +
    `<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>` +
    `<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>` +
    `<Reference URI="">` +
    `<Transforms>` +
    `<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>` +
    `</Transforms>` +
    `<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>` +
    `<DigestValue>${digestValue}</DigestValue>` +
    `</Reference>` +
    `</SignedInfo>`;

  const signatureBinary = credential.sign(signedInfoXml, 'sha1');
  const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64');

  return `<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx" xmlns:xd="http://www.w3.org/2000/09/xmldsig#">
  <s:Header/>
  <s:Body>
    <des:SolicitaDescarga>
      <des:solicitud RfcSolicitante="${rfc}" FechaInicial="${formatSatDate(fechaInicio)}" FechaFinal="${formatSatDate(fechaFin)}" TipoSolicitud="${tipoSolicitud}"${rfcEmisor ? ` RfcEmisor="${rfcEmisor}"` : ''}${rfcReceptor ? ` RfcReceptor="${rfcReceptor}"` : ''}>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
          ${signedInfoXml}
          <SignatureValue>${signatureValue}</SignatureValue>
          <KeyInfo>
            <X509Data>
              <X509IssuerSerial>
                <X509IssuerName>${certificate.issuerAsRfc4514()}</X509IssuerName>
                <X509SerialNumber>${certificate.serialNumber().bytes()}</X509SerialNumber>
              </X509IssuerSerial>
              <X509Certificate>${cerB64}</X509Certificate>
            </X509Data>
          </KeyInfo>
        </Signature>
      </des:solicitud>
    </des:SolicitaDescarga>
  </s:Body>
</s:Envelope>`;
}

/**
 * Solicita la descarga de CFDIs al SAT
 */
export async function requestDownload(params: RequestDownloadParams): Promise<SatDownloadRequestResponse> {
  const soapRequest = buildDownloadRequest(params);

  try {
    const response = await fetch(SAT_SOLICITUD_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'text/xml;charset=UTF-8',
        'SOAPAction': 'http://DescargaMasivaTerceros.sat.gob.mx/ISolicitaDescargaService/SolicitaDescarga',
        'Authorization': `WRAP access_token="${params.token}"`,
      },
      body: soapRequest,
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Error HTTP ${response.status}: ${errorText}`);
    }

    const responseXml = await response.text();
    return parseDownloadRequestResponse(responseXml);
  } catch (error: any) {
    console.error('[SAT Download Request Error]', error);
    throw new Error(`Error al solicitar descarga: ${error.message}`);
  }
}

/**
 * Parsea la respuesta de solicitud de descarga
 */
function parseDownloadRequestResponse(responseXml: string): SatDownloadRequestResponse {
  const parser = new XMLParser({
    ignoreAttributes: false,
    removeNSPrefix: true,
    attributeNamePrefix: '@_',
  });

  const result = parser.parse(responseXml);
  const envelope = result.Envelope || result['s:Envelope'];
  const body = envelope?.Body || envelope?.['s:Body'];
  const respuesta = body?.SolicitaDescargaResponse?.SolicitaDescargaResult;

  if (!respuesta) {
    throw new Error('Respuesta inválida del SAT');
  }

  return {
    idSolicitud: respuesta['@_IdSolicitud'] || '',
    codEstatus: respuesta['@_CodEstatus'] || '',
    mensaje: respuesta['@_Mensaje'] || '',
  };
}

/**
 * Verifica el estado de una solicitud de descarga
 */
export async function verifyRequest(
  credential: Credential,
  token: string,
  rfc: string,
  idSolicitud: string
): Promise<SatVerifyResponse> {
  const certificate = credential.certificate();
  const cerB64 = Buffer.from(certificate.pem().replace(/-----.*-----/g, '').replace(/\n/g, '')).toString('base64');

  const verificaContent = `<des:VerificaSolicitudDescarga xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx"><des:solicitud IdSolicitud="${idSolicitud}" RfcSolicitante="${rfc}"/></des:VerificaSolicitudDescarga>`;
  const digestValue = createHash('sha1').update(verificaContent).digest('base64');

  const signedInfoXml = `<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">` +
    `<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>` +
    `<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>` +
    `<Reference URI="">` +
    `<Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms>` +
    `<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>` +
    `<DigestValue>${digestValue}</DigestValue>` +
    `</Reference>` +
    `</SignedInfo>`;

  const signatureBinary = credential.sign(signedInfoXml, 'sha1');
  const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64');

  const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx">
  <s:Header/>
  <s:Body>
    <des:VerificaSolicitudDescarga>
      <des:solicitud IdSolicitud="${idSolicitud}" RfcSolicitante="${rfc}">
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
          ${signedInfoXml}
          <SignatureValue>${signatureValue}</SignatureValue>
          <KeyInfo>
            <X509Data>
              <X509IssuerSerial>
                <X509IssuerName>${certificate.issuerAsRfc4514()}</X509IssuerName>
                <X509SerialNumber>${certificate.serialNumber().bytes()}</X509SerialNumber>
              </X509IssuerSerial>
              <X509Certificate>${cerB64}</X509Certificate>
            </X509Data>
          </KeyInfo>
        </Signature>
      </des:solicitud>
    </des:VerificaSolicitudDescarga>
  </s:Body>
</s:Envelope>`;

  try {
    const response = await fetch(SAT_VERIFICA_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'text/xml;charset=UTF-8',
        'SOAPAction': 'http://DescargaMasivaTerceros.sat.gob.mx/IVerificaSolicitudDescargaService/VerificaSolicitudDescarga',
        'Authorization': `WRAP access_token="${token}"`,
      },
      body: soapRequest,
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Error HTTP ${response.status}: ${errorText}`);
    }

    const responseXml = await response.text();
    return parseVerifyResponse(responseXml);
  } catch (error: any) {
    console.error('[SAT Verify Error]', error);
    throw new Error(`Error al verificar solicitud: ${error.message}`);
  }
}

/**
 * Parsea la respuesta de verificación
 */
function parseVerifyResponse(responseXml: string): SatVerifyResponse {
  const parser = new XMLParser({
    ignoreAttributes: false,
    removeNSPrefix: true,
    attributeNamePrefix: '@_',
  });

  const result = parser.parse(responseXml);
  const envelope = result.Envelope || result['s:Envelope'];
  const body = envelope?.Body || envelope?.['s:Body'];
  const respuesta = body?.VerificaSolicitudDescargaResponse?.VerificaSolicitudDescargaResult;

  if (!respuesta) {
    throw new Error('Respuesta de verificación inválida');
  }

  // Extraer paquetes
  let paquetes: string[] = [];
  const paquetesNode = respuesta.IdsPaquetes;
  if (paquetesNode) {
    if (Array.isArray(paquetesNode)) {
      paquetes = paquetesNode;
    } else if (typeof paquetesNode === 'string') {
      paquetes = [paquetesNode];
    }
  }

  return {
    codEstatus: respuesta['@_CodEstatus'] || '',
    estadoSolicitud: parseInt(respuesta['@_EstadoSolicitud'] || '0', 10),
    codigoEstadoSolicitud: respuesta['@_CodigoEstadoSolicitud'] || '',
    numeroCfdis: parseInt(respuesta['@_NumeroCFDIs'] || '0', 10),
    mensaje: respuesta['@_Mensaje'] || '',
    paquetes,
  };
}

/**
 * Descarga un paquete de CFDIs
 */
export async function downloadPackage(
  credential: Credential,
  token: string,
  rfc: string,
  idPaquete: string
): Promise<SatPackageResponse> {
  const certificate = credential.certificate();
  const cerB64 = Buffer.from(certificate.pem().replace(/-----.*-----/g, '').replace(/\n/g, '')).toString('base64');

  const descargaContent = `<des:PeticionDescargaMasivaTercerosEntrada xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx"><des:peticionDescarga IdPaquete="${idPaquete}" RfcSolicitante="${rfc}"/></des:PeticionDescargaMasivaTercerosEntrada>`;
  const digestValue = createHash('sha1').update(descargaContent).digest('base64');

  const signedInfoXml = `<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">` +
    `<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>` +
    `<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>` +
    `<Reference URI="">` +
    `<Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms>` +
    `<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>` +
    `<DigestValue>${digestValue}</DigestValue>` +
    `</Reference>` +
    `</SignedInfo>`;

  const signatureBinary = credential.sign(signedInfoXml, 'sha1');
  const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64');

  const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx">
  <s:Header/>
  <s:Body>
    <des:PeticionDescargaMasivaTercerosEntrada>
      <des:peticionDescarga IdPaquete="${idPaquete}" RfcSolicitante="${rfc}">
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
          ${signedInfoXml}
          <SignatureValue>${signatureValue}</SignatureValue>
          <KeyInfo>
            <X509Data>
              <X509IssuerSerial>
                <X509IssuerName>${certificate.issuerAsRfc4514()}</X509IssuerName>
                <X509SerialNumber>${certificate.serialNumber().bytes()}</X509SerialNumber>
              </X509IssuerSerial>
              <X509Certificate>${cerB64}</X509Certificate>
            </X509Data>
          </KeyInfo>
        </Signature>
      </des:peticionDescarga>
    </des:PeticionDescargaMasivaTercerosEntrada>
  </s:Body>
</s:Envelope>`;

  try {
    const response = await fetch(SAT_DESCARGA_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'text/xml;charset=UTF-8',
        'SOAPAction': 'http://DescargaMasivaTerceros.sat.gob.mx/IDescargaMasivaService/Descargar',
        'Authorization': `WRAP access_token="${token}"`,
      },
      body: soapRequest,
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Error HTTP ${response.status}: ${errorText}`);
    }

    const responseXml = await response.text();
    return parseDownloadResponse(responseXml);
  } catch (error: any) {
    console.error('[SAT Download Package Error]', error);
    throw new Error(`Error al descargar paquete: ${error.message}`);
  }
}

/**
 * Parsea la respuesta de descarga de paquete
 */
function parseDownloadResponse(responseXml: string): SatPackageResponse {
  const parser = new XMLParser({
    ignoreAttributes: false,
    removeNSPrefix: true,
    attributeNamePrefix: '@_',
  });

  const result = parser.parse(responseXml);
  const envelope = result.Envelope || result['s:Envelope'];
  const body = envelope?.Body || envelope?.['s:Body'];
  const respuesta = body?.RespuestaDescargaMasivaTercerosSalida?.Paquete;

  if (!respuesta) {
    throw new Error('No se pudo obtener el paquete');
  }

  return {
    paquete: respuesta,
  };
}

/**
 * Estados de solicitud del SAT
 */
export const SAT_REQUEST_STATES = {
  ACCEPTED: 1,
  IN_PROGRESS: 2,
  COMPLETED: 3,
  ERROR: 4,
  REJECTED: 5,
  EXPIRED: 6,
} as const;

/**
 * Verifica si la solicitud está completa
 */
export function isRequestComplete(estadoSolicitud: number): boolean {
  return estadoSolicitud === SAT_REQUEST_STATES.COMPLETED;
}

/**
 * Verifica si la solicitud falló
 */
export function isRequestFailed(estadoSolicitud: number): boolean {
  return (
    estadoSolicitud === SAT_REQUEST_STATES.ERROR ||
    estadoSolicitud === SAT_REQUEST_STATES.REJECTED ||
    estadoSolicitud === SAT_REQUEST_STATES.EXPIRED
  );
}

/**
 * Verifica si la solicitud está en progreso
 */
export function isRequestInProgress(estadoSolicitud: number): boolean {
  return (
    estadoSolicitud === SAT_REQUEST_STATES.ACCEPTED ||
    estadoSolicitud === SAT_REQUEST_STATES.IN_PROGRESS
  );
}
