<?php

require_once __DIR__ . '/../../core/database.php';
require_once __DIR__ . '/BillingManualDocumentService.php';
require_once __DIR__ . '/BillingExportInvoiceService.php';
require_once __DIR__ . '/BillingComplementaryDocumentService.php';
require_once __DIR__ . '/BillingDocumentTypeService.php';
require_once __DIR__ . '/BillingRuntimeGate.php';

/**
 * Punto único de entrada para la emisión manual de documentos tributarios.
 *
 * La interfaz pública deliberadamente normaliza las respuestas de tres motores
 * internos ya validados:
 *  - BillingManualDocumentService: factura, boleta nominativa y guía nacional.
 *  - BillingExportInvoiceService: factura de exportación.
 *  - BillingComplementaryDocumentService: NC/ND nacionales y de exportación.
 */
class BillingUnifiedIssuerService
{
    private $pdo;
    private $manual;
    private $export;
    private $complementary;
    private $types;

    private $allowedKeys = array(
        'invoice_electronic',
        'invoice_exempt_electronic',
        'invoice_export_electronic',
        'dispatch_guide_electronic',
        'credit_note_electronic',
        'debit_note_electronic',
        'credit_note_export_electronic',
        'debit_note_export_electronic',
    );

    public function __construct(?PDO $pdo = null)
    {
        $this->pdo = $pdo ?: db();
        $this->manual = new BillingManualDocumentService($this->pdo);
        $this->export = new BillingExportInvoiceService($this->pdo);
        $this->complementary = new BillingComplementaryDocumentService($this->pdo);
        $this->types = new BillingDocumentTypeService($this->pdo);
    }

    public function bootstrap(string $environment = 'sandbox'): array
    {
        $environment = $this->configuredEnvironment();
        billing_assert_runtime_operation('read', $environment, 'Bootstrap del emisor unificado');
        $order = array_flip($this->allowedKeys);
        $types = array();
        $uiLabels = array(
            'invoice_electronic' => 'Factura electrónica afecta',
            'invoice_exempt_electronic' => 'Factura exenta electrónica',
            'invoice_export_electronic' => 'Factura de exportación electrónica',
            'dispatch_guide_electronic' => 'Guía de despacho electrónica',
            'credit_note_electronic' => 'Nota de crédito electrónica',
            'debit_note_electronic' => 'Nota de débito electrónica',
            'credit_note_export_electronic' => 'Nota de crédito de exportación',
            'debit_note_export_electronic' => 'Nota de débito de exportación',
        );

        foreach ($this->types->listIssuedTypes($environment) as $type) {
            $key = (string)($type['document_key'] ?? '');
            if (!array_key_exists($key, $order)) {
                continue;
            }
            if ($key === 'dispatch_guide_electronic') {
                $type['document_family'] = 'dispatch_guide';
                $type['facto_document_type_id'] = 54;
                $type['sii_external_code'] = '52';
                $type['requires_reference'] = 0;
                $type['is_export'] = 0;
                $type['is_tax_exempt'] = 0;
            }
            $types[] = array(
                'document_key' => $key,
                'document_label' => (string)($uiLabels[$key] ?? $type['document_label']),
                'document_family' => (string)$type['document_family'],
                'facto_document_type_id' => (int)$type['facto_document_type_id'],
                'sii_external_code' => (string)($type['sii_external_code'] ?? ''),
                'is_export' => (int)($type['is_export'] ?? 0),
                'is_tax_exempt' => (int)($type['is_tax_exempt'] ?? 0),
                'requires_reference' => (int)($type['requires_reference'] ?? 0),
                'environment_enabled' => !empty($type['environment_enabled']),
            );
        }

        usort($types, static function (array $a, array $b) use ($order): int {
            return ($order[$a['document_key']] ?? 999) <=> ($order[$b['document_key']] ?? 999);
        });

        $manualCatalogs = $this->manual->catalogs();
        // El bootstrap sólo necesita catálogos livianos. Los clientes y productos
        // se buscan de forma dinámica para evitar cargar miles de filas al abrir Emitir.
        $countries = $this->pdo->query("SELECT country_name,facto_country_id FROM billing_countries WHERE is_active=1 ORDER BY country_name")
            ->fetchAll(PDO::FETCH_ASSOC);
        $currencies = $this->pdo->query("SELECT currency_code,currency_name,facto_currency_id FROM billing_currencies WHERE is_active=1 ORDER BY currency_code")
            ->fetchAll(PDO::FETCH_ASSOC);

        return array(
            'environment' => $environment,
            'document_types' => $types,
            'payment_terms' => $manualCatalogs['payment_terms'] ?? array(),
            // Se conservan ambos nombres para compatibilidad con pantallas antiguas.
            'finance_centers' => $manualCatalogs['income_centers'] ?? array(),
            'income_centers' => $manualCatalogs['income_centers'] ?? array(),
            'countries' => $countries,
            'currencies' => $currencies,
            'guide_types' => $this->guideTypes(),
            'transfer_types' => $this->transferTypes(),
            'production_enabled' => billing_runtime_gate_allows('issue', 'production'),
            'runtime_gates' => billing_runtime_gate_state($environment),
        );
    }

    public function searchCustomers(string $query = '', int $limit = 25): array
    {
        return $this->manual->searchCustomers($query, $limit);
    }

    public function searchProducts(string $query = '', int $limit = 30): array
    {
        return $this->manual->searchProducts($query, $limit);
    }

    public function searchSourceDocuments(
        string $documentKey,
        string $query = '',
        string $environment = 'sandbox',
        int $limit = 30
    ): array {
        $environment = $this->configuredEnvironment();
        billing_assert_runtime_operation('read', $environment, 'Búsqueda de documentos tributarios de origen');
        $documentKey = trim($documentKey);
        if (!in_array($documentKey, array(
            'credit_note_electronic',
            'debit_note_electronic',
            'credit_note_export_electronic',
            'debit_note_export_electronic',
        ), true)) {
            return array();
        }

        $isExport = in_array($documentKey, array(
            'credit_note_export_electronic',
            'debit_note_export_electronic',
        ), true);
        $typeIds = $isExport ? array(46) : array(2, 32);
        $limit = max(1, min(60, $limit));
        $placeholders = implode(',', array_fill(0, count($typeIds), '?'));

        $sql = "SELECT
                    d.id,
                    d.document_id,
                    d.document_number,
                    d.issue_date,
                    CAST(d.document_type_id AS UNSIGNED) AS document_type_id,
                    d.receiver_legal_name,
                    d.receiver_tax_id_code,
                    d.net_amount,
                    d.taxes_amount,
                    d.total_amount,
                    d.currency_code,
                    d.exchange_rate_value,
                    d.crm_customer_id,
                    d.crm_quote_id,
                    q.quote_number,
                    c.id AS collection_id,
                    c.total_amount AS collection_total,
                    c.paid_amount,
                    c.balance_amount,
                    c.collection_status,
                    c.currency_code AS collection_currency_code,
                    c.foreign_total_amount,
                    c.foreign_balance_amount
                FROM facto_documents d
                LEFT JOIN crm_quotes q ON q.id=d.crm_quote_id
                LEFT JOIN billing_collections c
                  ON BINARY c.environment=BINARY d.environment
                 AND c.direction='receivable'
                 AND c.facto_document_id=CONVERT(d.document_id USING utf8mb4) COLLATE utf8mb4_unicode_ci
                WHERE d.environment=?
                  AND CAST(d.document_type_id AS UNSIGNED) IN ({$placeholders})
                  AND d.document_id IS NOT NULL
                  AND d.document_id<>''";
        $params = array_merge(array($environment), $typeIds);
        $query = trim($query);
        if ($query !== '') {
            $sql .= " AND (
                        d.document_number LIKE ?
                     OR d.receiver_legal_name LIKE ?
                     OR d.receiver_tax_id_code LIKE ?
                     OR d.document_id LIKE ?
                     OR q.quote_number LIKE ?
                    )";
            $like = '%' . $query . '%';
            array_push($params, $like, $like, $like, $like, $like);
        }
        $sql .= ' ORDER BY d.issue_date DESC,d.id DESC LIMIT ' . $limit;
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function preview(array $input, ?int $userId = null): array
    {
        $key = trim((string)($input['document_key'] ?? ''));
        if (!in_array($key, $this->allowedKeys, true)) {
            throw new InvalidArgumentException('Tipo documental no permitido.');
        }
        // El ambiente nunca se toma desde el navegador. Se resuelve únicamente
        // desde private_config.php para impedir cambios mediante DevTools.
        $environment = $this->configuredEnvironment();
        billing_assert_runtime_operation('preview', $environment, 'Preview del emisor unificado');
        $input['environment'] = $environment;

        if (in_array($key, array(
            'invoice_electronic',
            'invoice_exempt_electronic',
            'dispatch_guide_electronic',
        ), true)) {
            return $this->manual->preview($input, $userId);
        }

        if ($key === 'invoice_export_electronic') {
            $result = $this->export->preview($input, $userId);
            return array(
                'driver' => 'export',
                'operation_id' => (int)$result['operation_id'],
                'document_key' => $key,
                'document_label' => 'Factura de exportación electrónica',
                'message' => (string)($result['message'] ?? ''),
                'summary' => $result['summary'] ?? array(),
                'preview' => array(
                    'facto_document_id' => $result['preview']['facto_document_id'] ?? null,
                    'document_number' => $result['preview']['document_number'] ?? null,
                    'has_pdf' => !empty($result['preview']['has_pdf']),
                    'has_xml' => !empty($result['preview']['has_xml']),
                    'has_transfer_copy' => false,
                    'pdf_url' => $result['preview']['pdf_url'] ?? null,
                ),
            );
        }

        $sourceId = (int)($input['source_document_local_id'] ?? $input['source_id'] ?? 0);
        if ($sourceId <= 0) {
            throw new InvalidArgumentException('Debes seleccionar el documento tributario de origen.');
        }
        $operationType = strpos($key, 'credit_note') === 0 ? 'credit_note' : 'debit_note';
        $result = $this->complementary->previewAdjustment($sourceId, array(
            'environment' => $environment,
            'operation_type' => $operationType,
            'mode' => (string)($input['adjustment_mode'] ?? $input['note_mode'] ?? 'partial'),
            'amount' => (float)($input['adjustment_amount'] ?? $input['note_amount'] ?? 0),
            'reason' => trim((string)($input['adjustment_reason'] ?? $input['note_reason'] ?? '')),
            'issue_date' => (string)($input['issue_date'] ?? date('Y-m-d')),
        ), $userId);

        $source = $result['source'] ?? array();
        $amounts = $result['amounts'] ?? array();
        return array(
            'driver' => 'complementary',
            'operation_id' => (int)$result['operation_id'],
            'document_key' => $key,
            'document_label' => $operationType === 'credit_note' ? 'Nota de crédito' : 'Nota de débito',
            'message' => 'Vista previa FACTO generada. Es un borrador sin validez tributaria.',
            'summary' => array(
                'source_document_number' => $source['document_number'] ?? null,
                'source_facto_document_id' => $source['document_id'] ?? null,
                'receiver_legal_name' => $source['receiver_legal_name'] ?? null,
                'receiver_tax_id_code' => $source['receiver_tax_id_code'] ?? null,
                'currency_code' => $source['currency_code'] ?? ($amounts['currency_code'] ?? 'CLP'),
                'total_amount' => $amounts['total_amount'] ?? 0,
                'net_amount' => $amounts['net_amount'] ?? 0,
                'tax_amount' => $amounts['tax_amount'] ?? 0,
                'reference' => $result['reference'] ?? array(),
            ),
            'preview' => array(
                'facto_document_id' => $result['preview']['document_id'] ?? null,
                'document_number' => $result['preview']['document_number'] ?? null,
                'has_pdf' => !empty($result['preview']['pdf_available']),
                'has_xml' => !empty($result['preview']['xml_available']),
                'has_transfer_copy' => false,
                'pdf_url' => !empty($result['preview']['pdf_available'])
                    ? '/modules/billing/complementary_file.php?operation_id=' . (int)$result['operation_id'] . '&kind=pdf&stage=preview'
                    : null,
            ),
        );
    }

    public function issue(string $driver, int $operationId, string $confirmation, ?int $userId = null): array
    {
        $driver = trim($driver);
        if ($operationId <= 0) {
            throw new InvalidArgumentException('Operación inválida.');
        }

        if ($driver === 'manual') {
            return $this->manual->issue($operationId, $confirmation, $userId);
        }
        if ($driver === 'export') {
            $result = $this->export->issue($operationId, $confirmation, $userId);
            return array(
                'driver' => 'export',
                'operation_id' => $operationId,
                'status' => 'issued',
                'issue' => $result['issue'] ?? array(),
            );
        }
        if ($driver === 'complementary') {
            $result = $this->complementary->issueAdjustment($operationId, $confirmation, $userId);
            return array(
                'driver' => 'complementary',
                'operation_id' => $operationId,
                'status' => (string)($result['status'] ?? 'issued'),
                'issue' => array(
                    'facto_document_id' => $result['facto_document_id'] ?? null,
                    'document_number' => $result['document_number'] ?? null,
                    'has_pdf' => !empty($result['pdf_available']),
                    'has_xml' => !empty($result['xml_available']),
                    'pdf_url' => !empty($result['pdf_available'])
                        ? '/modules/billing/complementary_file.php?operation_id=' . $operationId . '&kind=pdf&stage=issued'
                        : null,
                    'xml_url' => !empty($result['xml_available'])
                        ? '/modules/billing/complementary_file.php?operation_id=' . $operationId . '&kind=xml&stage=issued'
                        : null,
                ),
            );
        }
        throw new InvalidArgumentException('Controlador de emisión inválido.');
    }

    private function normalizeEnvironment(string $environment): string
    {
        $environment = strtolower(trim($environment));
        return $environment === 'production' ? 'production' : 'sandbox';
    }

    private function configuredEnvironment(): string
    {
        if (defined('FACTO_DEFAULT_ENVIRONMENT')) {
            return $this->normalizeEnvironment((string)FACTO_DEFAULT_ENVIRONMENT);
        }
        if (defined('ICYLAB_FACTO_ENVIRONMENT')) {
            return $this->normalizeEnvironment((string)ICYLAB_FACTO_ENVIRONMENT);
        }
        return 'production';
    }

    private function guideTypes(): array
    {
        return array(
            array('id' => 1, 'label' => 'Operación constituye venta'),
            array('id' => 2, 'label' => 'Ventas por efectuar'),
            array('id' => 3, 'label' => 'Consignación'),
            array('id' => 4, 'label' => 'Entrega gratuita'),
            array('id' => 5, 'label' => 'Traslado interno'),
            array('id' => 6, 'label' => 'Otros traslados sin venta'),
            array('id' => 7, 'label' => 'Devolución'),
            array('id' => 8, 'label' => 'Traslado para exportación'),
            array('id' => 9, 'label' => 'Venta para exportación'),
        );
    }

    private function transferTypes(): array
    {
        return array(
            array('id' => 1, 'label' => 'Despacho por cuenta del receptor'),
            array('id' => 2, 'label' => 'Despacho por cuenta del emisor'),
            array('id' => 3, 'label' => 'Despacho por terceros'),
        );
    }
}
