<?php

require_once __DIR__ . '/../../core/database.php';
require_once __DIR__ . '/../../core/config.php';
require_once __DIR__ . '/BillingHelpers.php';
require_once __DIR__ . '/BillingDocumentTypeService.php';
require_once __DIR__ . '/BillingQuoteCalculator.php';
require_once __DIR__ . '/BillingPaymentTermsService.php';
require_once __DIR__ . '/BillingDraftPayloadFactory.php';
require_once __DIR__ . '/../finance/ExchangeRateService.php';

class BillingIssueValidationService
{
    private $pdo;
    private $documentTypes;
    private $calculator;
    private $paymentTerms;
    private $payloadFactory;
    private $exchangeRates;

    public function __construct(?PDO $pdo = null)
    {
        $this->pdo = $pdo ?: db();
        $this->documentTypes = new BillingDocumentTypeService($this->pdo);
        $this->calculator = new BillingQuoteCalculator($this->pdo);
        $this->paymentTerms = new BillingPaymentTermsService();
        $this->payloadFactory = new BillingDraftPayloadFactory($this->pdo);
        $this->exchangeRates = new ExchangeRateService($this->pdo);
    }

    public function validateQuoteForIssue($quoteId, $documentKey, array $options = array())
    {
        $environment = billing_normalize_environment($options['environment'] ?? null);
        $quoteId = (int) $quoteId;
        $documentKey = trim((string) $documentKey);
        $errors = array();
        $warnings = array();

        if ($quoteId <= 0) {
            throw new InvalidArgumentException('ID de cotizacion invalido.');
        }
        if ($documentKey === '') {
            throw new InvalidArgumentException('Tipo de documento requerido.');
        }

        $documentType = $this->documentTypes->getIssuedType($documentKey, $environment);
        if (!$documentType['environment_enabled']) {
            $errors[] = 'Tipo de documento no habilitado para ambiente ' . $environment . ': ' . $documentType['document_label'];
        }

        $quoteBundle = $this->loadQuoteBundle($quoteId);
        $quote = $quoteBundle['quote'];
        $customer = $quoteBundle['customer'];
        $items = $quoteBundle['items'];

        $issueDate = $this->cleanDate($options['issue_date'] ?? date('Y-m-d'));
        $exchangeRateResult = null;
        if (!empty($documentType['is_export'])) {
            $currencyCode = strtoupper(trim((string)($options['currency_code'] ?? 'USD'))) ?: 'USD';
            $exchangeRateResult = $this->exchangeRates->resolveWithManualFallback(
                $currencyCode,
                $issueDate,
                $options['exchange_rate_value'] ?? null
            );
            if (!empty($exchangeRateResult['ok'])) {
                $options['currency_code'] = $currencyCode;
                $options['exchange_rate_value'] = (float)$exchangeRateResult['rate_to_clp'];
                $options['exchange_rate_date'] = $exchangeRateResult['rate_date'];
                $options['exchange_rate_source'] = $exchangeRateResult['source'];
                $options['exchange_rate_status'] = $exchangeRateResult['quality_status'];
                if (!empty($exchangeRateResult['is_fallback_date'])) {
                    $warnings[] = 'El tipo de cambio corresponde al último día hábil disponible: ' . $exchangeRateResult['rate_date'] . '.';
                }
                if (($exchangeRateResult['quality_status'] ?? '') === 'manual') {
                    $warnings[] = 'Se utilizó un tipo de cambio manual porque Findic no estaba disponible.';
                }
            } else {
                $errors[] = $exchangeRateResult['message'] ?? 'No fue posible resolver el tipo de cambio histórico.';
            }
        }

        $this->validateQuoteState($quote, $documentType, $errors, $warnings, !empty($options['_reconcile_log_id']));
        if (empty($options['_reconcile_log_id'])) {
            $this->validateDuplicateIssue($quote, $documentType, $documentKey, $environment, $errors);
        }
        $this->validateCustomer($customer, $documentType, $options, $errors, $warnings);
        $this->validateDocumentSpecificOptions($documentType, $options, $errors, $warnings);

        if (empty($items)) {
            $errors[] = 'La cotizacion no tiene items.';
        }

        $calculation = $this->calculator->calculate($quote, $items, $documentType, $options);
        $errors = array_merge($errors, $calculation['errors']);
        $warnings = array_merge($warnings, $calculation['warnings']);

        $this->validateTotalsAgainstQuote($quote, $calculation, $documentType, $options, $errors);

        $paymentConditions = $this->paymentTerms->normalizeToFacto(
            $options['payment_terms'] ?? ($customer['facto_payment_term'] ?? ($customer['payment_terms'] ?? '0')),
            $options['custom_payment_days'] ?? null
        );
        $paymentDueDate = $this->paymentTerms->dueDate($issueDate, $paymentConditions);

        $payload = $this->payloadFactory->build($quote, $customer, $documentType, $calculation, array_merge($options, array(
            'environment' => $environment,
            'issue_date' => $issueDate,
            'payment_terms' => $paymentConditions
        )));

        $idempotencyKey = $this->idempotencyKey($quoteId, $documentKey, $environment, $documentType, $options);

        return array(
            'ok' => empty($errors),
            'errors' => array_values(array_unique($errors)),
            'warnings' => array_values(array_unique($warnings)),
            'environment' => $environment,
            'idempotency_key' => $idempotencyKey,
            'quote' => $this->publicQuote($quote),
            'customer' => $this->publicCustomer($customer),
            'document_type' => $this->publicDocumentType($documentType),
            'payment' => array(
                'issue_date' => $issueDate,
                'payment_conditions' => $paymentConditions,
                'due_date' => $paymentDueDate,
                'mark_as_paid_available' => empty($documentType['is_export'])
            ),
            'calculation' => $calculation,
            'exchange_rate' => $exchangeRateResult,
            'payload_preview' => $payload
        );
    }

    public function listAvailableDocumentTypes($environment = 'sandbox')
    {
        return $this->documentTypes->listIssuedTypes($environment);
    }

    private function loadQuoteBundle($quoteId)
    {
        $stmt = $this->pdo->prepare("\n            SELECT\n                q.*,\n                c.id AS c_id,\n                c.facto_customer_supplier_id,\n                c.counterparty_type,\n                c.customer_name,\n                c.commercial_name,\n                c.legal_name,\n                c.customer_tax_id,\n                c.export_tax_id_code,\n                c.export_tax_id_type,\n                c.tax_id_type,\n                c.email,\n                c.email_dte,\n                c.phone,\n                c.address,\n                c.payment_terms,\n                c.default_income_center_id,\n                c.default_cost_center_id,\n                c.district,\n                c.city,\n                c.region,\n                c.country,\n                c.country_id,\n                c.export_country_id,\n                c.business_activity,\n                c.facto_payment_term,\n                o.title AS opportunity_title,\n                o.stage AS opportunity_stage\n            FROM crm_quotes q\n            JOIN crm_customers c ON c.id = q.customer_id\n            LEFT JOIN crm_opportunities o ON o.id = q.opportunity_id\n            WHERE q.id = ?\n            LIMIT 1\n        ");
        $stmt->execute(array($quoteId));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            throw new RuntimeException('Cotizacion no encontrada.');
        }

        $customer = array(
            'id' => (int) $row['c_id'],
            'facto_customer_supplier_id' => $row['facto_customer_supplier_id'] ?? null,
            'counterparty_type' => $row['counterparty_type'] ?? 'customer',
            'customer_name' => $row['customer_name'] ?? '',
            'commercial_name' => $row['commercial_name'] ?? '',
            'legal_name' => $row['legal_name'] ?? '',
            'customer_tax_id' => $row['customer_tax_id'] ?? '',
            'export_tax_id_code' => $row['export_tax_id_code'] ?? '',
            'export_tax_id_type' => $row['export_tax_id_type'] ?? '',
            'tax_id_type' => $row['tax_id_type'] ?? 'CL-RUT',
            'email' => $row['email'] ?? '',
            'email_dte' => $row['email_dte'] ?? '',
            'phone' => $row['phone'] ?? '',
            'address' => $row['address'] ?? '',
            'payment_terms' => $row['payment_terms'] ?? '',
            'default_income_center_id' => $row['default_income_center_id'] ?? null,
            'default_cost_center_id' => $row['default_cost_center_id'] ?? null,
            'district' => $row['district'] ?? '',
            'city' => $row['city'] ?? '',
            'region' => $row['region'] ?? '',
            'country' => $row['country'] ?? '',
            'country_id' => $row['country_id'] ?? null,
            'export_country_id' => $row['export_country_id'] ?? null,
            'business_activity' => $row['business_activity'] ?? '',
            'facto_payment_term' => $row['facto_payment_term'] ?? ''
        );

        $quote = $row;

        $stmtItems = $this->pdo->prepare('SELECT * FROM crm_quote_items WHERE quote_id = ? ORDER BY id ASC');
        $stmtItems->execute(array($quoteId));
        $items = $stmtItems->fetchAll(PDO::FETCH_ASSOC);

        return array('quote' => $quote, 'customer' => $customer, 'items' => $items);
    }

    private function validateQuoteState(array $quote, array $documentType, array &$errors, array &$warnings, $isReconciliation = false)
    {
        if (!empty($quote['deleted_at'])) {
            $errors[] = 'La cotizacion esta eliminada.';
        }

        $status = trim((string) ($quote['status'] ?? ''));
        // Una conciliación no emite un nuevo DTE: sólo completa el cierre local
        // de uno ya creado. Por eso admite que el estado comercial haya sido
        // avanzado por una sincronización o una recuperación anterior.
        if ($isReconciliation && in_array($status, array('Aceptada', 'Facturada', 'Esperando pago', 'Pagada'), true)) {
            return;
        }
        if ($documentType['document_family'] === 'dispatch_guide') {
            if (!in_array($status, array('Aceptada', 'Facturada', 'Esperando pago', 'Pagada'), true)) {
                $errors[] = 'La guia solo puede prepararse desde cotizaciones aceptadas o ya facturadas.';
            }
            return;
        }

        if ($documentType['document_family'] === 'credit_note' || $documentType['document_family'] === 'debit_note') {
            if (!in_array($status, array('Facturada', 'Esperando pago', 'Pagada'), true)) {
                $errors[] = 'Las notas requieren una cotizacion ya facturada.';
            }
            return;
        }

        if ($status !== 'Aceptada') {
            $errors[] = 'Solo se puede emitir factura desde cotizacion Aceptada. Estado actual: ' . ($status ?: 'sin estado') . '.';
        }
    }

    private function validateDuplicateIssue(array $quote, array $documentType, $documentKey, $environment, array &$errors)
    {
        $quoteId = (int) $quote['id'];

        if (in_array($documentType['document_family'], array('invoice', 'export'), true)) {
            if (!empty($quote['facto_document_id'])) {
                $errors[] = 'La cotizacion ya tiene FACTO document_id vinculado: ' . $quote['facto_document_id'];
            }
            if (!empty($quote['invoice_number'])) {
                $errors[] = 'La cotizacion ya tiene folio/factura registrada: ' . $quote['invoice_number'];
            }
        }

        $stmt = $this->pdo->prepare("\n            SELECT id, status, document_number, facto_document_id\n            FROM billing_issue_logs\n            WHERE quote_id = ?\n              AND document_type_key = ?\n              AND environment = ?\n              AND status IN ('sent','success','pending_reconcile')\n            ORDER BY id DESC\n            LIMIT 1\n        ");
        $stmt->execute(array($quoteId, $documentKey, $environment));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row) {
            $errors[] = 'Ya existe una emision o intento exitoso/pending para este documento. Log #' . $row['id'] . '.';
        }
    }

    private function validateCustomer(array $customer, array $documentType, array $options, array &$errors, array &$warnings)
    {
        $isExport = (int)$documentType['is_export'] === 1;
        $legalName = trim((string)($options['receiver_legal_name'] ?? ($customer['legal_name'] ?: ($customer['commercial_name'] ?: $customer['customer_name']))));
        if ($legalName === '') {
            $errors[] = 'Cliente sin razón social/nombre legal.';
        }

        if ($isExport) {
            $required = array(
                'receiver_tax_id_code' => 'identificador tributario extranjero',
                'receiver_tax_id_type' => 'tipo de identificador tributario',
                'receiver_address' => 'dirección',
                'receiver_city' => 'ciudad',
                'receiver_activity' => 'actividad económica'
            );
            $fallbacks = array(
                'receiver_tax_id_code' => $customer['export_tax_id_code'] ?: $customer['customer_tax_id'],
                'receiver_tax_id_type' => $customer['export_tax_id_type'] ?: $customer['tax_id_type'],
                'receiver_address' => $customer['address'],
                'receiver_city' => $customer['city'],
                'receiver_activity' => $customer['business_activity']
            );
            foreach ($required as $field => $label) {
                $value = trim((string)($options[$field] ?? $fallbacks[$field] ?? ''));
                if ($value === '') $errors[] = 'Cliente de exportación sin ' . $label . '.';
            }
            $countryId = (int)($options['receiver_country_id'] ?? ($customer['export_country_id'] ?: $customer['country_id']));
            if ($countryId <= 0 || $countryId === 253) {
                $errors[] = 'La factura de exportación requiere país receptor extranjero.';
            }
        } else {
            $required = array(
                'customer_tax_id' => 'RUT', 'business_activity' => 'giro', 'address' => 'dirección',
                'district' => 'comuna', 'city' => 'ciudad', 'phone' => 'teléfono', 'payment_terms' => 'condición de pago'
            );
            foreach ($required as $field => $label) {
                if (trim((string)($customer[$field] ?? '')) === '') $errors[] = 'Cliente sin ' . $label . '.';
            }
        }

        if (trim((string)($options['receiver_email'] ?? ($customer['email_dte'] ?: $customer['email']))) === '') {
            $warnings[] = 'Cliente sin email DTE/email; el documento podrá emitirse, pero no enviarse automáticamente.';
        }
    }

    private function validateDocumentSpecificOptions(array $documentType, array $options, array &$errors, array &$warnings)
    {
        if ((int) $documentType['is_export'] === 1) {
            $currencyCode = strtoupper(trim((string) ($options['currency_code'] ?? 'USD')));
            if ($currencyCode === '') {
                $errors[] = 'Exportacion requiere moneda.';
            }
            if ($currencyCode !== 'CLP') {
                $exchangeRate = (float) ($options['exchange_rate_value'] ?? 0);
                if ($exchangeRate <= 0) {
                    $errors[] = 'Exportacion en moneda extranjera requiere tipo de cambio mayor a cero.';
                }
            }
            if ((int)($options['sale_modality'] ?? 0) <= 0) $errors[] = 'Exportación requiere modalidad de venta.';
            if ((int)($options['sale_clause'] ?? 0) <= 0) $errors[] = 'Exportación requiere cláusula de venta.';
            $taxType = trim((string)($options['receiver_tax_id_type'] ?? ''));
            if ($taxType === 'CL-RUT') $warnings[] = 'El receptor de exportación usa CL-RUT. Verifica que corresponda a un receptor extranjero.';
        }

        if ($documentType['document_family'] === 'dispatch_guide') {
            if (empty($options['guide_type'])) {
                $warnings[] = 'Guia sin tipo seleccionado; se usara 1 = operacion constituye venta en payload preliminar.';
            }
            if (empty($options['transfer_type'])) {
                $warnings[] = 'Guia sin tipo de traslado; se usara 2 = despacho por cuenta del emisor en payload preliminar.';
            }
        }

        if (in_array($documentType['document_family'], array('credit_note', 'debit_note'), true)) {
            if (empty($options['source_facto_document_id'])) {
                $errors[] = 'Notas de credito/debito requieren documento FACTO original.';
            }
            if (empty($options['reference_code'])) {
                $errors[] = 'Notas de credito/debito requieren codigo de referencia.';
            }
        }
    }

    private function validateTotalsAgainstQuote(array $quote, array $calculation, array $documentType, array $options, array &$errors)
    {
        if ((int)$documentType['is_export'] === 1) {
            $mode = trim((string)($options['export_pricing_mode'] ?? 'convert_from_clp'));
            if ($mode === 'convert_from_clp') {
                $rate = (float)($options['exchange_rate_value'] ?? 0);
                if ($rate > 0) {
                    $clpEquivalent = billing_money(((float)$calculation['total_amount']) * $rate);
                    if (abs($clpEquivalent - billing_money($quote['total'] ?? 0)) > 2.0) {
                        $errors[] = 'El total convertido no cuadra con la cotización. Cotización CLP: ' . billing_money($quote['total'] ?? 0) . ' / Equivalente CLP: ' . billing_money($clpEquivalent) . '.';
                    }
                }
            }
            return;
        }

        $checks = array(
            array('label'=>'Subtotal','quote'=>$quote['subtotal'] ?? 0,'calc'=>$calculation['subtotal_before_global_discount']),
            array('label'=>'Descuento global','quote'=>$quote['discount_amount'] ?? 0,'calc'=>$calculation['global_discount_amount']),
            array('label'=>'IVA','quote'=>$quote['iva'] ?? 0,'calc'=>$calculation['taxes_amount']),
            array('label'=>'Total','quote'=>$quote['total'] ?? 0,'calc'=>$calculation['total_amount'])
        );
        foreach ($checks as $check) {
            if (!billing_money_equals($check['quote'], $check['calc'])) {
                $errors[] = $check['label'] . ' no cuadra. Cotización: ' . billing_money($check['quote']) . ' / Recalculado: ' . billing_money($check['calc']) . '.';
            }
        }
    }

    private function idempotencyKey($quoteId, $documentKey, $environment, array $documentType, array $options)
    {
        if (in_array($documentType['document_family'], array('credit_note', 'debit_note'), true) && !empty($options['source_facto_document_id'])) {
            return 'source_document:' . (int) $options['source_facto_document_id'] . ':note:' . $documentKey . ':quote:' . (int) $quoteId . ':env:' . $environment . ':v1';
        }
        return 'quote:' . (int) $quoteId . ':document:' . $documentKey . ':env:' . $environment . ':v1';
    }

    private function cleanDate($value)
    {
        $ts = strtotime((string) $value);
        return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
    }

    private function publicQuote(array $quote)
    {
        return array(
            'id' => (int) $quote['id'],
            'quote_number' => $quote['quote_number'] ?? '',
            'status' => $quote['status'] ?? '',
            'subtotal' => billing_money($quote['subtotal'] ?? 0),
            'discount_percent' => billing_money($quote['discount_percent'] ?? 0),
            'discount_amount' => billing_money($quote['discount_amount'] ?? 0),
            'iva' => billing_money($quote['iva'] ?? 0),
            'total' => billing_money($quote['total'] ?? 0),
            'opportunity_id' => isset($quote['opportunity_id']) ? (int) $quote['opportunity_id'] : null,
            'facto_document_id' => $quote['facto_document_id'] ?? null,
            'invoice_number' => $quote['invoice_number'] ?? null,
            'finance_center_id' => isset($quote['finance_center_id']) ? (int) $quote['finance_center_id'] : null
        );
    }

    private function publicCustomer(array $customer)
    {
        return array(
            'id' => (int) $customer['id'],
            'name' => $customer['legal_name'] ?: ($customer['commercial_name'] ?: $customer['customer_name']),
            'tax_id' => $customer['customer_tax_id'],
            'email_dte' => $customer['email_dte'] ?: $customer['email'],
            'payment_terms' => $customer['payment_terms'],
            'district' => $customer['district'],
            'city' => $customer['city'],
            'country_id' => $customer['country_id'],
            'export_country_id' => $customer['export_country_id'],
            'default_income_center_id' => isset($customer['default_income_center_id']) ? (int) $customer['default_income_center_id'] : null,
            'default_cost_center_id' => isset($customer['default_cost_center_id']) ? (int) $customer['default_cost_center_id'] : null
        );
    }

    private function publicDocumentType(array $documentType)
    {
        return array(
            'document_key' => $documentType['document_key'],
            'document_label' => $documentType['document_label'],
            'facto_document_type_id' => (int) $documentType['facto_document_type_id'],
            'sii_external_code' => $documentType['sii_external_code'],
            'document_family' => $documentType['document_family'],
            'is_export' => (int) $documentType['is_export'],
            'is_tax_exempt' => (int) $documentType['is_tax_exempt']
        );
    }
}
