<?php

require_once __DIR__ . '/../../core/database.php';
require_once __DIR__ . '/FactoClient.php';
require_once __DIR__ . '/FactoFileStorageService.php';
require_once __DIR__ . '/../billing/BillingSupplierResolverService.php';
require_once __DIR__ . '/../billing/BillingPayablesService.php';
require_once __DIR__ . '/../billing/BillingRuntimeGate.php';

class FactoInboxDocumentsSyncService
{
    private $pdo;
    private $client;
    private $storage;
    private $environment;
    private $payableCatchupCreated = 0;
    private $payableCatchupExisting = 0;
    private $payableCatchupErrors = 0;

    public function __construct($environment = null)
    {
        $this->pdo = db();
        $this->client = new FactoClient($environment);
        $this->storage = new FactoFileStorageService();
        $this->environment = $this->client->getEnvironment();
    }

    public function syncInboxDocuments($perPage = 100, $params = array(), $fetchFiles = false)
    {
        billing_assert_runtime_operation('sync', $this->environment, 'Sincronización de DTE recibidos FACTO');
        $page = 1;
        $totalSaved = 0;
        $this->payableCatchupCreated = 0;
        $this->payableCatchupExisting = 0;
        $this->payableCatchupErrors = 0;
        $perPage = max(1, min(100, (int) $perPage));

        do {
            $query = array_merge($params, array(
                'page' => $page,
                'per_page' => $perPage
            ));

            $response = $this->client->getInboxDocuments($query);
            $documents = $this->extractEmbeddedList($response, 'inbox_documents');

            foreach ($documents as $document) {
                $saved = $this->saveInboxDocument($document);
                if ($saved) {
                    $totalSaved++;
                    if ($fetchFiles) {
                        $inboxId = $this->pick($document, 'inbox_document_id');
                        if ($inboxId) {
                            $this->refreshInboxDocument($inboxId, true);
                        }
                    }
                }
            }

            $pageCount = isset($response['page_count']) ? (int) $response['page_count'] : 1;
            if ($pageCount < 1) {
                $pageCount = 1;
            }
            $page++;
        } while ($page <= $pageCount);

        $this->log('success', 'Inbox FACTO sincronizado ambiente ' . $this->environment . ': ' . $totalSaved . ' documentos recibidos. CxP aprobadas creadas=' . $this->payableCatchupCreated . ', existentes=' . $this->payableCatchupExisting . ', errores=' . $this->payableCatchupErrors . '.');

        return $totalSaved;
    }

    public function refreshInboxDocument($inboxDocumentId, $saveFiles = true)
    {
        billing_assert_runtime_operation('sync', $this->environment, 'Actualización de DTE recibido FACTO');
        $document = $this->client->getInboxDocument($inboxDocumentId);
        $savedPaths = array();

        if ($saveFiles) {
            $electronicDocument = $this->extractElectronicDocument($document);
            if (!empty($electronicDocument)) {
                $savedPaths = $this->storage->saveElectronicDocumentFiles((string) $inboxDocumentId, $electronicDocument, $this->environment . '/inbox');
                $document['_icylab_saved_files'] = $savedPaths;
            }
        }

        $this->saveInboxDocument($document, $savedPaths);
        return $document;
    }

    public function acceptInboxDocument($inboxDocumentId, $payload = array())
    {
        billing_assert_runtime_operation('finance', $this->environment, 'Aceptación productiva de DTE recibido');
        if (empty($payload)) {
            $payload = array('inbox_document_status' => 2);
        }

        return $this->client->patchInboxDocument($inboxDocumentId, $payload);
    }

    public function rejectInboxDocument($inboxDocumentId, $payload = array())
    {
        billing_assert_runtime_operation('finance', $this->environment, 'Rechazo productivo de DTE recibido');
        if (empty($payload)) {
            $payload = array('inbox_document_status' => 4);
        }

        return $this->client->patchInboxDocument($inboxDocumentId, $payload);
    }

    public function saveInboxDocument($d, $savedPaths = array())
    {
        billing_assert_runtime_operation('sync', $this->environment, 'Persistencia local de DTE recibido FACTO');
        $inboxDocumentId = $this->pick($d, 'inbox_document_id');

        if (!$inboxDocumentId) {
            return false;
        }

        $pdfFilePath = isset($savedPaths['pdf_file_path']) ? $savedPaths['pdf_file_path'] : $this->pickNested($d, array('_icylab_saved_files', 'pdf_file_path'));
        $xmlFilePath = isset($savedPaths['xml_file_path']) ? $savedPaths['xml_file_path'] : $this->pickNested($d, array('_icylab_saved_files', 'xml_file_path'));
        $existingTaxIdentity = $this->getExistingTaxIdentity($inboxDocumentId);
        $taxIdentity = $this->resolveInboxTaxIdentity($d, $existingTaxIdentity);
        $rawPayload = json_encode($d, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $stmt = $this->pdo->prepare("\n            INSERT INTO facto_inbox_documents\n            (\n                environment,\n                inbox_document_id,\n                document_id,\n                account_id,\n                email_subject,\n                sender_email,\n                document_date,\n                receive_date,\n                inbox_document_status,\n                issuer_name,\n                issuer_tax_id_code,\n                issuer_tax_id_type,\n                receiver_tax_id_code,\n                receiver_tax_id_type,\n                document_type_id,\n                document_type_taxbureau,\n                document_number,\n                purchase_order,\n                reception_confirmation_status,\n                reception_confirmation_date,\n                document_approval_status,\n                document_approval_date,\n                product_receipt_status,\n                product_receipt_date,\n                product_receipt_location,\n                net_amount,\n                taxes_amount,\n                total_amount,\n                pdf_file_path,\n                xml_file_path,\n                raw_payload,\n                sync_status,\n                last_synced_at\n            )\n            VALUES\n            (\n                ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'synced', NOW()\n            )\n            ON DUPLICATE KEY UPDATE\n                environment = VALUES(environment),\n                document_id = VALUES(document_id),\n                account_id = VALUES(account_id),\n                email_subject = VALUES(email_subject),\n                sender_email = VALUES(sender_email),\n                document_date = VALUES(document_date),\n                receive_date = VALUES(receive_date),\n                inbox_document_status = VALUES(inbox_document_status),\n                issuer_name = VALUES(issuer_name),\n                issuer_tax_id_code = COALESCE(NULLIF(VALUES(issuer_tax_id_code), ''), issuer_tax_id_code),\n                issuer_tax_id_type = COALESCE(NULLIF(VALUES(issuer_tax_id_type), ''), issuer_tax_id_type),\n                receiver_tax_id_code = COALESCE(NULLIF(VALUES(receiver_tax_id_code), ''), receiver_tax_id_code),\n                receiver_tax_id_type = COALESCE(NULLIF(VALUES(receiver_tax_id_type), ''), receiver_tax_id_type),\n                document_type_id = VALUES(document_type_id),\n                document_type_taxbureau = VALUES(document_type_taxbureau),\n                document_number = VALUES(document_number),\n                purchase_order = VALUES(purchase_order),\n                reception_confirmation_status = VALUES(reception_confirmation_status),\n                reception_confirmation_date = VALUES(reception_confirmation_date),\n                document_approval_status = VALUES(document_approval_status),\n                document_approval_date = VALUES(document_approval_date),\n                product_receipt_status = VALUES(product_receipt_status),\n                product_receipt_date = VALUES(product_receipt_date),\n                product_receipt_location = VALUES(product_receipt_location),\n                net_amount = VALUES(net_amount),\n                taxes_amount = VALUES(taxes_amount),\n                total_amount = VALUES(total_amount),\n                pdf_file_path = COALESCE(VALUES(pdf_file_path), pdf_file_path),\n                xml_file_path = COALESCE(VALUES(xml_file_path), xml_file_path),\n                raw_payload = VALUES(raw_payload),\n                sync_status = 'synced',\n                last_synced_at = NOW(),\n                updated_at = CURRENT_TIMESTAMP\n        ");

        $stmt->execute(array(
            $this->environment,
            $inboxDocumentId,
            $this->pick($d, 'document_id'),
            $this->pick($d, 'account_id'),
            $this->pick($d, 'email_subject'),
            $this->pick($d, 'sender_email'),
            $this->normalizeDate($this->pick($d, 'document_date')),
            $this->normalizeDateTime($this->pick($d, 'receive_date')),
            $this->pick($d, 'inbox_document_status'),
            $this->pick($d, 'issuer_name'),
            $taxIdentity['issuer_tax_id_code'],
            $taxIdentity['issuer_tax_id_type'],
            $taxIdentity['receiver_tax_id_code'],
            $taxIdentity['receiver_tax_id_type'],
            $this->toInt($this->pick($d, 'document_type_id')),
            $this->pick($d, 'document_type_taxbureau'),
            $this->pick($d, 'document_number'),
            $this->pick($d, 'purchase_order'),
            $this->pick($d, 'reception_confirmation_status'),
            $this->normalizeDateTime($this->pick($d, 'reception_confirmation_date')),
            $this->pick($d, 'document_approval_status'),
            $this->normalizeDateTime($this->pick($d, 'document_approval_date')),
            $this->pick($d, 'product_receipt_status'),
            $this->normalizeDateTime($this->pick($d, 'product_receipt_date')),
            $this->pick($d, 'product_receipt_location'),
            $this->toDecimal($this->pick($d, 'net_amount')),
            $this->toDecimal($this->pick($d, 'taxes_amount')),
            $this->toDecimal($this->pick($d, 'total_amount')),
            $pdfFilePath,
            $xmlFilePath,
            $rawPayload
        ));

        // El estado remoto de FACTO es la fuente de verdad de la bandeja.
        // La auditoría de decisiones permanece en billing_inbox_decisions.
        $this->reconcileLocalReceptionStatus(
            $inboxDocumentId,
            $this->pick($d, 'inbox_document_status')
        );

        try {
            if (class_exists('BillingSupplierResolverService')) {
                $resolver = new BillingSupplierResolverService($this->pdo);
                $resolver->resolveDocumentByInboxId($inboxDocumentId, $this->environment, true);
            }
        } catch (Throwable $e) {
            // No bloquear sincronizacion FACTO por resolucion automatica de proveedor.
        }

        $this->catchUpApprovedPayable($inboxDocumentId);

        return true;
    }

    private function reconcileLocalReceptionStatus($inboxDocumentId, $externalStatus)
    {
        $map = array(
            '1' => 'pending',
            '2' => 'approved',
            '3' => 'approved_with_observations',
            '4' => 'rejected',
            '5' => 'merchandise_received'
        );
        $externalStatus = trim((string)$externalStatus);
        if (!isset($map[$externalStatus])) {
            return;
        }

        $stmt = $this->pdo->prepare("
            UPDATE facto_inbox_documents
            SET icylab_reception_status = ?, updated_at = CURRENT_TIMESTAMP
            WHERE BINARY environment = BINARY ?
              AND BINARY inbox_document_id = BINARY ?
              AND COALESCE(icylab_reception_status, '') <> ?
        ");
        $stmt->execute(array(
            $map[$externalStatus],
            $this->environment,
            (string)$inboxDocumentId,
            $map[$externalStatus]
        ));
    }

    private function catchUpApprovedPayable($inboxDocumentId)
    {
        if (!billing_runtime_gate_allows('finance', $this->environment)) {
            return;
        }

        try {
            $stmt = $this->pdo->prepare("SELECT d.id,d.inbox_document_id,d.document_type_id,d.total_amount,d.document_approval_status,d.inbox_document_status,d.icylab_reception_status,
                    c.id AS collection_id
                FROM facto_inbox_documents d
                LEFT JOIN billing_collections c
                  ON BINARY c.environment = BINARY d.environment
                 AND BINARY c.direction = BINARY 'payable'
                 AND BINARY c.inbox_document_id = BINARY d.inbox_document_id
                WHERE BINARY d.environment = BINARY ? AND BINARY d.inbox_document_id = BINARY ? LIMIT 1");
            $stmt->execute(array($this->environment, (string)$inboxDocumentId));
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$row || !in_array((int)$row['document_type_id'], array(9,33), true) || (float)$row['total_amount'] <= 0) {
                return;
            }

            $internal = strtolower(trim((string)$row['icylab_reception_status']));
            $approval = strtolower(trim((string)$row['document_approval_status']));
            $inboxStatus = trim((string)$row['inbox_document_status']);
            $approved = in_array($internal, array('approved','approved_with_observations'), true)
                || in_array($approval, array('1','accepted','approved','approved_with_observations'), true)
                || in_array($inboxStatus, array('2','3'), true);
            if (!$approved) {
                return;
            }

            if (!empty($row['collection_id'])) {
                $this->payableCatchupExisting++;
                return;
            }

            $service = new BillingPayablesService($this->pdo);
            $service->ensurePayableCollection((string)$row['inbox_document_id'], $this->environment);
            $this->payableCatchupCreated++;
        } catch (Throwable $e) {
            $this->payableCatchupErrors++;
            $this->log('warning', 'CxP aprobada no materializada para inbox ' . (string)$inboxDocumentId . ': ' . $e->getMessage());
        }
    }

    private function getExistingTaxIdentity($inboxDocumentId)
    {
        $stmt = $this->pdo->prepare("SELECT issuer_tax_id_code, issuer_tax_id_type, receiver_tax_id_code, receiver_tax_id_type FROM facto_inbox_documents WHERE BINARY environment = BINARY ? AND BINARY inbox_document_id = BINARY ? LIMIT 1");
        $stmt->execute(array($this->environment, (string)$inboxDocumentId));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? $row : array();
    }

    private function resolveInboxTaxIdentity($document, $existing = array())
    {
        $issuer = $this->cleanTaxId($this->pick($document, 'issuer_tax_id_code'));
        $receiver = $this->cleanTaxId($this->pick($document, 'receiver_tax_id_code'));
        $issuerType = $this->pick($document, 'issuer_tax_id_type');
        $receiverType = $this->pick($document, 'receiver_tax_id_type');

        $xml = $this->findDocumentXml($document);
        $xmlIssuer = $this->extractXmlTag($xml, 'RUTEmisor');
        $xmlReceiver = $this->extractXmlTag($xml, 'RUTRecep');
        if ($xmlIssuer) {
            $issuer = $xmlIssuer;
        }
        if ($xmlReceiver) {
            $receiver = $xmlReceiver;
        }

        $existingIssuer = $this->cleanTaxId(isset($existing['issuer_tax_id_code']) ? $existing['issuer_tax_id_code'] : null);
        $existingReceiver = $this->cleanTaxId(isset($existing['receiver_tax_id_code']) ? $existing['receiver_tax_id_code'] : null);
        $companyTaxId = $this->getCompanyTaxId();

        /*
         * Some FACTO inbox list responses expose the supplier RUT in
         * receiver_tax_id_code and omit issuer_tax_id_code. For received DTEs,
         * the configured company is always the receiver. Normalize that shape
         * before persisting it.
         */
        if (!$issuer && $receiver && $companyTaxId && $this->normalizeTaxId($receiver) !== $this->normalizeTaxId($companyTaxId)) {
            $issuer = $receiver;
            $receiver = $companyTaxId;
        }

        if (!$issuer && $existingIssuer) {
            $issuer = $existingIssuer;
        }
        if (!$receiver && $existingReceiver) {
            $receiver = $existingReceiver;
        }
        if (!$receiver && $companyTaxId) {
            $receiver = $companyTaxId;
        }

        if ($issuer && $receiver && $this->normalizeTaxId($issuer) === $this->normalizeTaxId($receiver)) {
            if ($companyTaxId && $this->normalizeTaxId($issuer) !== $this->normalizeTaxId($companyTaxId)) {
                $receiver = $companyTaxId;
            } elseif ($existingReceiver && $this->normalizeTaxId($existingReceiver) !== $this->normalizeTaxId($issuer)) {
                $receiver = $existingReceiver;
            }
        }

        if (!$issuerType && $issuer) {
            $issuerType = isset($existing['issuer_tax_id_type']) && $existing['issuer_tax_id_type'] ? $existing['issuer_tax_id_type'] : 'CL-RUT';
        }
        if (!$receiverType && $receiver) {
            $receiverType = isset($existing['receiver_tax_id_type']) && $existing['receiver_tax_id_type'] ? $existing['receiver_tax_id_type'] : 'CL-RUT';
        }

        return array(
            'issuer_tax_id_code' => $issuer,
            'issuer_tax_id_type' => $issuerType,
            'receiver_tax_id_code' => $receiver,
            'receiver_tax_id_type' => $receiverType
        );
    }

    private function getCompanyTaxId()
    {
        try {
            $stmt = $this->pdo->query("SELECT company_tax_id FROM system_settings WHERE id = 1 LIMIT 1");
            $value = $stmt ? $stmt->fetchColumn() : null;
            return $this->cleanTaxId($value);
        } catch (Throwable $e) {
            return null;
        }
    }

    private function findDocumentXml($value)
    {
        if (!is_array($value)) {
            return null;
        }
        foreach (array('document_xml', 'xml', 'xml_content') as $key) {
            if (isset($value[$key]) && is_string($value[$key]) && trim($value[$key]) !== '') {
                return $value[$key];
            }
        }
        foreach ($value as $child) {
            if (is_array($child)) {
                $found = $this->findDocumentXml($child);
                if ($found) {
                    return $found;
                }
            }
        }
        return null;
    }

    private function extractXmlTag($xml, $tag)
    {
        if (!is_string($xml) || trim($xml) === '') {
            return null;
        }
        $pattern = '/<' . preg_quote($tag, '/') . '\\b[^>]*>\\s*([^<]+)\\s*<\\/' . preg_quote($tag, '/') . '>/i';
        if (preg_match($pattern, $xml, $matches)) {
            return $this->cleanTaxId(html_entity_decode(trim($matches[1]), ENT_QUOTES, 'UTF-8'));
        }
        return null;
    }

    private function cleanTaxId($value)
    {
        $value = trim((string)$value);
        return $value !== '' ? $value : null;
    }

    private function normalizeTaxId($value)
    {
        return strtoupper(preg_replace('/[^0-9K]/i', '', (string)$value));
    }

    private function extractEmbeddedList($response, $key)
    {
        if (isset($response['_embedded'][$key]) && is_array($response['_embedded'][$key])) {
            return $response['_embedded'][$key];
        }

        if (isset($response[$key]) && is_array($response[$key])) {
            return $response[$key];
        }

        return array();
    }

    private function extractElectronicDocument($document)
    {
        if (isset($document['electronic_document']) && is_array($document['electronic_document'])) {
            return $document['electronic_document'];
        }

        return array();
    }

    private function pick($array, $key)
    {
        return isset($array[$key]) && $array[$key] !== '' ? $array[$key] : null;
    }

    private function pickNested($array, $path)
    {
        $cursor = $array;
        foreach ($path as $key) {
            if (!is_array($cursor) || !array_key_exists($key, $cursor)) {
                return null;
            }
            $cursor = $cursor[$key];
        }

        return $cursor !== '' ? $cursor : null;
    }

    private function normalizeDate($value)
    {
        if (!$value) {
            return null;
        }

        $timestamp = strtotime($value);
        return $timestamp ? date('Y-m-d', $timestamp) : null;
    }

    private function normalizeDateTime($value)
    {
        if (!$value) {
            return null;
        }

        $timestamp = strtotime($value);
        return $timestamp ? date('Y-m-d H:i:s', $timestamp) : null;
    }

    private function toInt($value)
    {
        if ($value === null || $value === '') {
            return null;
        }

        return (int) $value;
    }

    private function toDecimal($value)
    {
        if ($value === null || $value === '') {
            return 0;
        }

        return (float) str_replace(',', '.', (string) $value);
    }

    private function log($status, $message)
    {
        $stmt = $this->pdo->prepare("\n            INSERT INTO facto_sync_logs (status, message)\n            VALUES (?, ?)\n        ");

        $stmt->execute(array($status, $message));
    }
}
