import {
  Fiel,
  HttpsWebClient,
  FielRequestBuilder,
  Service,
  QueryParameters,
  DateTimePeriod,
  DownloadType,
  RequestType,
  DocumentStatus,
  ServiceEndpoints,
} from '@nodecfdi/sat-ws-descarga-masiva';

export interface FielData {
  cerContent: string;
  keyContent: string;
  password: string;
}

/**
 * Crea el servicio de descarga masiva del SAT usando los datos de la FIEL
 */
export function createSatService(fielData: FielData): Service {
  // Crear FIEL usando el método estático create
  const fiel = Fiel.create(fielData.cerContent, fielData.keyContent, fielData.password);

  // Verificar que la FIEL sea válida
  if (!fiel.isValid()) {
    throw new Error('La FIEL no es válida o está vencida');
  }

  // Crear cliente HTTP
  const webClient = new HttpsWebClient();

  // Crear request builder con la FIEL
  const requestBuilder = new FielRequestBuilder(fiel);

  // Crear y retornar el servicio
  return new Service(requestBuilder, webClient, undefined, ServiceEndpoints.cfdi());
}

export interface QueryResult {
  success: boolean;
  requestId?: string;
  message: string;
  statusCode?: string;
}

export interface VerifyResult {
  success: boolean;
  status: 'pending' | 'processing' | 'ready' | 'failed' | 'rejected';
  packageIds: string[];
  totalCfdis: number;
  message: string;
  statusCode?: string;
}

export interface DownloadResult {
  success: boolean;
  packageContent: string; // Base64 encoded ZIP
  message: string;
}

/**
 * Realiza una consulta al SAT para solicitar CFDIs
 */
export async function querySat(
  service: Service,
  fechaInicio: Date,
  fechaFin: Date,
  tipo: 'emitidos' | 'recibidos',
  requestType: 'metadata' | 'cfdi' = 'cfdi'
): Promise<QueryResult> {
  try {
    const period = DateTimePeriod.createFromValues(
      formatDateForSat(fechaInicio),
      formatDateForSat(fechaFin)
    );

    const downloadType = new DownloadType(tipo === 'emitidos' ? 'issued' : 'received');
    const reqType = new RequestType(requestType === 'cfdi' ? 'xml' : 'metadata');

    // XMLs: solo vigentes (active). Metadata: todos (undefined = sin filtro).
    let parameters = QueryParameters.create(period, downloadType, reqType);
    if (requestType === 'cfdi') {
      parameters = parameters.withDocumentStatus(new DocumentStatus('active'));
    }
    const result = await service.query(parameters);

    if (!result.getStatus().isAccepted()) {
      return {
        success: false,
        message: result.getStatus().getMessage(),
        statusCode: result.getStatus().getCode().toString(),
      };
    }

    return {
      success: true,
      requestId: result.getRequestId(),
      message: 'Solicitud aceptada',
      statusCode: result.getStatus().getCode().toString(),
    };
  } catch (error: any) {
    console.error('[SAT Query Error]', error);
    return {
      success: false,
      message: error.message || 'Error al realizar consulta',
    };
  }
}

/**
 * Verifica el estado de una solicitud
 */
export async function verifySatRequest(
  service: Service,
  requestId: string
): Promise<VerifyResult> {
  try {
    const result = await service.verify(requestId);
    const statusRequest = result.getStatusRequest();

    // `codeRequest` es el código SAT específico del estado de la solicitud
    // (5000 Accepted, 5002 Exhausted, 5003 MaximumLimit, 5004 EmptyResult,
    // 5005 Duplicated) y su mensaje explica POR QUÉ el SAT rechazó. Es la
    // pieza clave para diagnosticar rejections — el `getStatus().getCode()`
    // solo devuelve el wrapper HTTP (5000 genérico "Aceptada").
    //
    // Fuente: docs phpcfdi + lib @nodecfdi/sat-ws-descarga-masiva (`CodeRequest`).
    const codeReqObj = typeof (result as any).getCodeRequest === 'function'
      ? (result as any).getCodeRequest()
      : null;
    const codeRequestValue = codeReqObj ? codeReqObj.getValue() : null;
    const codeRequestMessage = codeReqObj ? codeReqObj.getMessage() : null;
    const codeRequestEntry = codeReqObj ? codeReqObj.getEntryId() : null;

    // Debug logging
    console.log('[SAT Verify Debug]', {
      statusRequestValue: statusRequest.getValue(),
      statusRequestEntryId: statusRequest.getEntryId(),
      cfdis: result.getNumberCfdis(),
      packages: result.getPackageIds(),
      statusCode: result.getStatus().getCode(),
      statusMsg: result.getStatus().getMessage(),
      codeRequestValue,
      codeRequestEntry,
      codeRequestMessage,
    });

    // Usar isTypeOf para determinar el estado
    let status: VerifyResult['status'];
    if (statusRequest.isTypeOf('Finished')) {
      status = 'ready';
    } else if (statusRequest.isTypeOf('InProgress')) {
      status = 'processing';
    } else if (statusRequest.isTypeOf('Accepted')) {
      status = 'pending';
    } else if (statusRequest.isTypeOf('Failure')) {
      status = 'failed';
    } else if (statusRequest.isTypeOf('Rejected')) {
      status = 'rejected';
    } else {
      // Default: check by entryId
      const entryId = statusRequest.getEntryId();
      if (entryId === 'Finished') status = 'ready';
      else if (entryId === 'InProgress') status = 'processing';
      else if (entryId === 'Accepted') status = 'pending';
      else status = 'pending';
    }

    // Para estados terminales no-felices, construir mensaje informativo.
    // `codeRequest` (si está disponible) es la razón SAT real del rechazo.
    const statusCode = result.getStatus().getCode().toString();
    const statusMsg = result.getStatus().getMessage();
    const reqValue = statusRequest.getValue();
    const reqEntry = statusRequest.getEntryId();
    let message = statusMsg;
    if (status === 'rejected' || status === 'failed') {
      const codeReqStr = codeRequestValue
        ? ` codeRequest=${codeRequestEntry}(${codeRequestValue}) — ${codeRequestMessage}`
        : '';
      message = `SAT request=${reqEntry}(${reqValue})${codeReqStr} wrapperCode=${statusCode} wrapperMsg="${statusMsg}"`;
    }

    return {
      success: result.getStatus().isAccepted(),
      status,
      packageIds: result.getPackageIds(),
      totalCfdis: result.getNumberCfdis(),
      message,
      statusCode,
    };
  } catch (error: any) {
    console.error('[SAT Verify Error]', error.message || error);
    // Errores de la librería (ej. webError.getResponse is not a function)
    // no son fallos del SAT — devolver 'pending' para reintentar polling
    return {
      success: false,
      status: 'pending',
      packageIds: [],
      totalCfdis: 0,
      message: error.message || 'Error al verificar solicitud',
    };
  }
}

/**
 * Descarga un paquete de CFDIs
 */
export async function downloadSatPackage(
  service: Service,
  packageId: string
): Promise<DownloadResult> {
  try {
    const result = await service.download(packageId);

    if (!result.getStatus().isAccepted()) {
      return {
        success: false,
        packageContent: '',
        message: result.getStatus().getMessage(),
      };
    }

    return {
      success: true,
      packageContent: result.getPackageContent(),
      message: 'Paquete descargado',
    };
  } catch (error: any) {
    console.error('[SAT Download Error]', error);
    return {
      success: false,
      packageContent: '',
      message: error.message || 'Error al descargar paquete',
    };
  }
}

/**
 * Formatea una fecha para el SAT (YYYY-MM-DD HH:mm:ss)
 */
function formatDateForSat(date: Date): string {
  const pad = (n: number) => n.toString().padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
    `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
