<?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';

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

    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);
    }

    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'];
        }

        if ($documentType['document_family'] === 'receipt') {
            $errors[] = 'Boletas no estan habilitadas para ICYLAB OS V1.';
        }

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

        $this->validateQuoteState($quote, $documentType, $errors, $warnings);
        $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);
        $errors = array_merge($errors, $calculation['errors']);
        $warnings = array_merge($warnings, $calculation['warnings']);

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

        $issueDate = $this->cleanDate($options['issue_date'] ?? date('Y-m-d'));
        $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' => true
            ),
            'calculation' => $calculation,
            '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)
    {
        if (!empty($quote['deleted_at'])) {
            $errors[] = 'La cotizacion esta eliminada.';
        }

        $status = trim((string) ($quote['status'] ?? ''));
        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)
    {
        $required = array(
            'customer_tax_id' => 'RUT',
            'business_activity' => 'giro',
            'address' => 'direccion',
            'district' => 'comuna',
            'city' => 'ciudad',
            'phone' => 'telefono',
            'payment_terms' => 'condicion de pago'
        );

        $legalName = trim((string) ($customer['legal_name'] ?: ($customer['commercial_name'] ?: $customer['customer_name'])));
        if ($legalName === '') {
            $errors[] = 'Cliente sin razon social/nombre legal.';
        }

        foreach ($required as $field => $label) {
            if (trim((string) ($customer[$field] ?? '')) === '') {
                $errors[] = 'Cliente sin ' . $label . '.';
            }
        }

        if (trim((string) ($customer['email_dte'] ?: ($customer['email'] ?? ''))) === '') {
            $errors[] = 'Cliente sin email DTE/email.';
        }

        if ((int) $documentType['is_export'] === 1) {
            $exportTaxId = trim((string) ($customer['export_tax_id_code'] ?: ($customer['customer_tax_id'] ?? '')));
            if ($exportTaxId === '') {
                $errors[] = 'Cliente de exportacion sin identificador tributario.';
            }

            $countryId = (int) ($options['receiver_country_id'] ?? ($customer['export_country_id'] ?: ($customer['country_id'] ?? 0)));
            if ($countryId <= 0 || $countryId === 253) {
                $errors[] = 'Documento de exportacion requiere pais receptor extranjero.';
            }
        }
    }

    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 ($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 &$errors)
    {
        $checks = array(
            'subtotal' => array('label' => 'Subtotal', 'quote' => $quote['subtotal'] ?? 0, 'calc' => $calculation['subtotal_before_global_discount']),
            'discount_amount' => array('label' => 'Descuento global', 'quote' => $quote['discount_amount'] ?? 0, 'calc' => $calculation['global_discount_amount']),
            'iva' => array('label' => 'IVA', 'quote' => $quote['iva'] ?? 0, 'calc' => $calculation['taxes_amount']),
            'total' => 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. Cotizacion: ' . 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']
        );
    }
}
