<?php

namespace App\Services;

use PhpCfdi\SatWsDescargaMasiva\Service;
use PhpCfdi\SatWsDescargaMasiva\WebClient\GuzzleWebClient;
use PhpCfdi\SatWsDescargaMasiva\RequestBuilder\FielRequestBuilder\Fiel;
use PhpCfdi\SatWsDescargaMasiva\RequestBuilder\FielRequestBuilder\FielRequestBuilder;
use PhpCfdi\SatWsDescargaMasiva\Shared\DateTimePeriod;
use PhpCfdi\SatWsDescargaMasiva\Services\Query\QueryParameters;
use PhpCfdi\SatWsDescargaMasiva\PackageReader\CfdiPackageReader;
use PhpCfdi\SatWsDescargaMasiva\PackageReader\MetadataPackageReader;
use PhpCfdi\SatWsDescargaMasiva\PackageReader\Exceptions\OpenZipFileException;
use Exception;

class SATDescargaMasivaService
{
    private Service $service;
    private string $cerPath;
    private string $keyPath;
    private string $password;

    public function __construct()
    {
        // Rutas de los certificados
        $this->cerPath = storage_path('certs/foos851205ph8.cer');
        $this->keyPath = storage_path('certs/Claveprivada_FIEL_FOOS851205PH8_20250509_115905.key');
        $this->password = 'FLORES01';

        // Validar que los archivos existan
        if (!file_exists($this->cerPath)) {
            throw new Exception("Archivo CER no encontrado: {$this->cerPath}");
        }

        if (!file_exists($this->keyPath)) {
            throw new Exception("Archivo KEY no encontrado: {$this->keyPath}");
        }

        // Crear la FIEL
        $fiel = Fiel::create(
            file_get_contents($this->cerPath),
            file_get_contents($this->keyPath),
            $this->password
        );

        // Verificar que la FIEL sea válida
        if (!$fiel->isValid()) {
            throw new Exception("La FIEL no es válida. Verifica que sea una FIEL (no CSD) y que esté vigente.");
        }

        // Crear el web client
        $webClient = new GuzzleWebClient();

        // Crear el request builder
        $requestBuilder = new FielRequestBuilder($fiel);

        // Crear el servicio
        $this->service = new Service($requestBuilder, $webClient);
    }

    /**
     * Crear una consulta de descarga masiva
     *
     * @param string $fechaInicio Fecha de inicio en formato 'Y-m-d H:i:s'
     * @param string $fechaFin Fecha de fin en formato 'Y-m-d H:i:s'
     * @return array
     * @throws Exception
     */
    public function crearConsulta(string $fechaInicio, string $fechaFin): array
    {
        try {
            // Crear el período de tiempo
            $period = DateTimePeriod::createFromValues($fechaInicio, $fechaFin);

            // Crear los parámetros de la consulta
            $request = QueryParameters::create($period);

            // Presentar la consulta
            $query = $this->service->query($request);

            // Retornar la respuesta
            return [
                'success' => true,
                'request_id' => $query->getRequestId(),
                'status_code' => $query->getStatus()->getCode(),
                'status_message' => $query->getStatus()->getMessage(),
                'fecha_inicio' => $fechaInicio,
                'fecha_fin' => $fechaFin,
            ];
        } catch (Exception $e) {
            throw new Exception("Error al crear la consulta: " . $e->getMessage());
        }
    }

    /**
     * Verificar el estado de una consulta
     *
     * @param string $requestId UUID de la solicitud
     * @return array
     * @throws Exception
     */
    public function verificarConsulta(string $requestId): array
    {
        try {
            // Verificar la consulta
            $verify = $this->service->verify($requestId);

            // Obtener los paquetes disponibles
            $packages = [];
            foreach ($verify->getPackagesIds() as $packageId) {
                $packages[] = $packageId;
            }

            return [
                'success' => true,
                'request_id' => $requestId,
                'status_code' => $verify->getStatus()->getCode(),
                'status_message' => $verify->getStatus()->getMessage(),
                'code_request' => $verify->getCodeRequest()->getValue(),
                'number_cfdi' => $verify->getNumberCfdis(),
                'packages' => $packages,
                'package_count' => count($packages),
            ];
        } catch (Exception $e) {
            throw new Exception("Error al verificar la consulta: " . $e->getMessage());
        }
    }

    /**
     * Descargar los paquetes de una consulta
     *
     * @param string $requestId UUID de la solicitud
     * @return array
     * @throws Exception
     */
    public function descargarPaquetes(string $requestId): array
    {
        try {
            // Primero verificar la consulta para obtener los paquetes
            $verify = $this->service->verify($requestId);

            $downloadedPackages = [];
            $errors = [];

            // Descargar cada paquete
            foreach ($verify->getPackagesIds() as $packageId) {
                try {
                    $download = $this->service->download($packageId);

                    // Guardar el paquete en storage
                    $filename = "package_{$packageId}.zip";
                    $filepath = storage_path("app/sat_downloads/{$filename}");
                    
                    // Crear el directorio si no existe
                    $directory = dirname($filepath);
                    if (!is_dir($directory)) {
                        mkdir($directory, 0755, true);
                    }

                    // Guardar el contenido
                    file_put_contents($filepath, $download->getPackageContent());

                    $downloadedPackages[] = [
                        'package_id' => $packageId,
                        'filename' => $filename,
                        'filepath' => $filepath,
                        'size' => strlen($download->getPackageContent()),
                        'status' => 'downloaded'
                    ];
                } catch (Exception $e) {
                    $errors[] = [
                        'package_id' => $packageId,
                        'error' => $e->getMessage()
                    ];
                }
            }

            return [
                'success' => true,
                'request_id' => $requestId,
                'total_packages' => count($verify->getPackagesIds()),
                'downloaded' => count($downloadedPackages),
                'failed' => count($errors),
                'packages' => $downloadedPackages,
                'errors' => $errors,
            ];
        } catch (Exception $e) {
            throw new Exception("Error al descargar los paquetes: " . $e->getMessage());
        }
    }

    /**
     * Extraer XMLs de un archivo ZIP de CFDIs
     *
     * @param string $packageId ID del paquete a extraer
     * @return array
     * @throws Exception
     */
    public function extraerXMLs(string $packageId): array
    {
        try {
            // Construir la ruta al archivo ZIP
            $zipfile = storage_path("app/sat_downloads/package_{$packageId}.zip");

            // Verificar que el archivo exista
            if (!file_exists($zipfile)) {
                throw new Exception("Archivo ZIP no encontrado: {$zipfile}. Asegúrate de haber descargado el paquete primero.");
            }

            // Crear directorio para los XMLs extraídos
            $extractDir = storage_path("app/sat_downloads/extracted/{$packageId}");
            if (!is_dir($extractDir)) {
                mkdir($extractDir, 0755, true);
            }

            // Abrir el archivo ZIP de CFDIs
            try {
                $cfdiReader = CfdiPackageReader::createFromFile($zipfile);
            } catch (OpenZipFileException $exception) {
                throw new Exception("Error al abrir el archivo ZIP: " . $exception->getMessage());
            }

            $extractedFiles = [];
            $totalSize = 0;

            // Leer todos los CFDIs dentro del archivo ZIP
            foreach ($cfdiReader->cfdis() as $uuid => $content) {
                $xmlFilename = "{$uuid}.xml";
                $xmlFilepath = "{$extractDir}/{$xmlFilename}";
                
                // Guardar el XML
                file_put_contents($xmlFilepath, $content);
                
                $fileSize = strlen($content);
                $totalSize += $fileSize;

                $extractedFiles[] = [
                    'uuid' => $uuid,
                    'filename' => $xmlFilename,
                    'filepath' => $xmlFilepath,
                    'size' => $fileSize,
                ];
            }

            return [
                'success' => true,
                'package_id' => $packageId,
                'zip_file' => $zipfile,
                'extract_directory' => $extractDir,
                'total_files' => count($extractedFiles),
                'total_size' => $totalSize,
                'files' => $extractedFiles,
            ];
        } catch (Exception $e) {
            throw new Exception("Error al extraer los XMLs: " . $e->getMessage());
        }
    }

    /**
     * Leer metadata de un archivo ZIP
     *
     * @param string $packageId ID del paquete a leer
     * @return array
     * @throws Exception
     */
    public function leerMetadata(string $packageId): array
    {
        try {
            // Construir la ruta al archivo ZIP
            $zipfile = storage_path("app/sat_downloads/package_{$packageId}.zip");

            // Verificar que el archivo exista
            if (!file_exists($zipfile)) {
                throw new Exception("Archivo ZIP no encontrado: {$zipfile}. Asegúrate de haber descargado el paquete primero.");
            }

            // Abrir el archivo de Metadata
            try {
                $metadataReader = MetadataPackageReader::createFromFile($zipfile);
            } catch (OpenZipFileException $exception) {
                throw new Exception("Error al abrir el archivo ZIP: " . $exception->getMessage());
            }

            $metadataList = [];

            // Leer todos los registros de metadata
            foreach ($metadataReader->metadata() as $uuid => $metadata) {
                $metadataList[] = [
                    'uuid' => $metadata->uuid,
                    'fecha_emision' => $metadata->fechaEmision,
                    'rfc_emisor' => $metadata->rfcEmisor,
                    'nombre_emisor' => $metadata->nombreEmisor,
                    'rfc_receptor' => $metadata->rfcReceptor,
                    'nombre_receptor' => $metadata->nombreReceptor,
                    'rfc_pac' => $metadata->rfcPac,
                    'total' => $metadata->total,
                    'efecto_comprobante' => $metadata->efectoComprobante,
                    'estatus' => $metadata->estatus,
                    'fecha_cancelacion' => $metadata->fechaCancelacion,
                ];
            }

            return [
                'success' => true,
                'package_id' => $packageId,
                'zip_file' => $zipfile,
                'total_records' => count($metadataList),
                'metadata' => $metadataList,
            ];
        } catch (Exception $e) {
            throw new Exception("Error al leer la metadata: " . $e->getMessage());
        }
    }
}
