<?php

require_once __DIR__ . '/BillingHelpers.php';

class BillingQuoteCalculator
{
    private $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function calculate(array $quote, array $items, array $documentType, array $options = array())
    {
        $warnings = array();
        $errors = array();
        $lines = array();

        $isExport = (int) $documentType['is_export'] === 1;
        $isExemptDocument = (int) $documentType['is_tax_exempt'] === 1 || $isExport;
        $isDispatchGuide = $documentType['document_family'] === 'dispatch_guide';

        $subtotalBeforeGlobal = 0.0;
        $taxableBeforeGlobal = 0.0;
        $exemptBeforeGlobal = 0.0;

        foreach ($items as $item) {
            $product = $this->resolveProduct($item);
            $sku = '';

            if ($product && !empty($product['sku'])) {
                $sku = trim((string) $product['sku']);
            }
            if ($sku === '') {
                $sku = billing_extract_sku($item['name'] ?? '', $item['description'] ?? '');
            }
            if ($sku === '' && !empty($item['sku'])) {
                $sku = trim((string) $item['sku']);
            }

            if ($sku === '') {
                $errors[] = 'Item sin SKU: ' . ($item['name'] ?? 'sin nombre');
            }

            if (!$product) {
                $warnings[] = 'No se encontro producto maestro para item: ' . ($item['name'] ?? 'sin nombre') . '. Se usaran datos de la linea.';
            }

            $quantity = (float) ($item['quantity'] ?? 0);
            $unitPrice = (float) ($item['price_unit'] ?? 0);
            $baseAmount = billing_money($quantity * $unitPrice);

            if ($quantity <= 0) {
                $errors[] = 'Cantidad invalida en item: ' . ($item['name'] ?? 'sin nombre');
            }
            if ($unitPrice < 0) {
                $errors[] = 'Precio unitario negativo en item: ' . ($item['name'] ?? 'sin nombre');
            }

            $lineDiscountAmount = (float) ($item['discount_amount'] ?? 0);
            $lineDiscountPercent = (float) ($item['discount_percent'] ?? 0);
            if ($lineDiscountAmount <= 0 && $lineDiscountPercent > 0) {
                $lineDiscountAmount = billing_money($baseAmount * ($lineDiscountPercent / 100));
            }
            if ($lineDiscountAmount < 0) {
                $errors[] = 'Descuento negativo no permitido en item: ' . ($item['name'] ?? 'sin nombre');
            }
            if ($lineDiscountAmount > $baseAmount) {
                $errors[] = 'Descuento mayor al total de linea en item: ' . ($item['name'] ?? 'sin nombre');
            }

            $lineNetBeforeGlobal = billing_money($baseAmount - $lineDiscountAmount);

            $taxTreatment = $this->resolveTaxTreatment($item, $product);
            $taxRate = $this->resolveTaxRate($item, $product, $taxTreatment);

            if ($isExport) {
                if ($taxTreatment !== 'exento' || $taxRate > 0) {
                    $warnings[] = 'Documento de exportacion: se forzara IVA 0 para item ' . ($item['name'] ?? 'sin nombre') . '.';
                }
                $taxTreatment = 'exento';
                $taxRate = 0.0;
            }

            if ($isExemptDocument && $taxTreatment !== 'exento') {
                $errors[] = 'Documento exento/exportacion contiene item afecto: ' . ($item['name'] ?? 'sin nombre');
            }

            if (!$isExemptDocument && !$isDispatchGuide && $taxTreatment === 'exento') {
                $errors[] = 'Factura afecta contiene item exento: ' . ($item['name'] ?? 'sin nombre') . '. Emitir factura exenta o separar documentos.';
            }

            if ($taxTreatment === 'exento') {
                $exemptBeforeGlobal += $lineNetBeforeGlobal;
            } else {
                $taxableBeforeGlobal += $lineNetBeforeGlobal;
            }

            $subtotalBeforeGlobal += $lineNetBeforeGlobal;

            $lines[] = array(
                'source_item_id' => isset($item['id']) ? (int) $item['id'] : null,
                'product_id' => $product ? (int) $product['id'] : (isset($item['product_id']) && is_numeric($item['product_id']) ? (int) $item['product_id'] : null),
                'sku' => $sku,
                'name' => $product && !empty($product['name']) ? $product['name'] : ($item['name'] ?? ''),
                'description' => $product && !empty($product['description']) ? $product['description'] : ($item['description'] ?? ''),
                'quantity' => $quantity,
                'unit_measure' => $this->resolveUnitMeasure($item, $product),
                'unit_price' => billing_money($unitPrice),
                'base_amount' => $baseAmount,
                'line_discount_amount' => billing_money($lineDiscountAmount),
                'line_discount_percent' => billing_money($lineDiscountPercent),
                'net_before_global_discount' => $lineNetBeforeGlobal,
                'global_discount_allocated' => 0.0,
                'net_amount' => $lineNetBeforeGlobal,
                'tax_treatment' => $taxTreatment,
                'tax_rate' => $taxRate,
                'tax_amount' => 0.0,
                'gross_amount' => $lineNetBeforeGlobal,
                'finance_center_id' => !empty($item['finance_center_id']) ? (int) $item['finance_center_id'] : ($product && !empty($product['default_income_center_id']) ? (int) $product['default_income_center_id'] : null)
            );
        }

        $globalDiscountAmount = (float) ($quote['discount_amount'] ?? 0);
        $globalDiscountPercent = (float) ($quote['discount_percent'] ?? 0);
        if ($globalDiscountAmount <= 0 && $globalDiscountPercent > 0) {
            $globalDiscountAmount = billing_money($subtotalBeforeGlobal * ($globalDiscountPercent / 100));
        }

        if ($globalDiscountAmount < 0) {
            $errors[] = 'Descuento global negativo no permitido.';
        }
        if ($globalDiscountAmount > $subtotalBeforeGlobal) {
            $errors[] = 'Descuento global mayor al subtotal.';
        }

        $discountOnTaxable = 0.0;
        $discountOnExempt = 0.0;
        if ($globalDiscountAmount > 0 && $subtotalBeforeGlobal > 0) {
            $discountOnTaxable = billing_money($globalDiscountAmount * ($taxableBeforeGlobal / $subtotalBeforeGlobal));
            $discountOnExempt = billing_money($globalDiscountAmount - $discountOnTaxable);
        }

        $taxableAfterGlobal = billing_money(max(0, $taxableBeforeGlobal - $discountOnTaxable));
        $exemptAfterGlobal = billing_money(max(0, $exemptBeforeGlobal - $discountOnExempt));

        $allocatedTotal = 0.0;
        $lastIndex = count($lines) - 1;
        foreach ($lines as $idx => &$line) {
            $allocation = 0.0;
            if ($globalDiscountAmount > 0 && $subtotalBeforeGlobal > 0) {
                if ($idx === $lastIndex) {
                    $allocation = billing_money($globalDiscountAmount - $allocatedTotal);
                } else {
                    $allocation = billing_money($globalDiscountAmount * ($line['net_before_global_discount'] / $subtotalBeforeGlobal));
                    $allocatedTotal += $allocation;
                }
            }

            $line['global_discount_allocated'] = $allocation;
            $line['net_amount'] = billing_money(max(0, $line['net_before_global_discount'] - $allocation));

            if ($line['tax_treatment'] === 'exento' || $isExemptDocument) {
                $line['tax_amount'] = 0.0;
                $line['gross_amount'] = $line['net_amount'];
            } else {
                $line['tax_amount'] = billing_money($line['net_amount'] * ($line['tax_rate'] / 100));
                $line['gross_amount'] = billing_money($line['net_amount'] + $line['tax_amount']);
            }
        }
        unset($line);

        $taxAmount = $isExemptDocument ? 0.0 : billing_money($taxableAfterGlobal * 0.19);
        $netAfterGlobal = billing_money($taxableAfterGlobal + $exemptAfterGlobal);
        $totalAmount = billing_money($netAfterGlobal + $taxAmount);

        $result = array(
            'errors' => $errors,
            'warnings' => $warnings,
            'lines' => $lines,
            'subtotal_before_global_discount' => billing_money($subtotalBeforeGlobal),
            'taxable_before_global_discount' => billing_money($taxableBeforeGlobal),
            'exempt_before_global_discount' => billing_money($exemptBeforeGlobal),
            'global_discount_amount' => billing_money($globalDiscountAmount),
            'global_discount_percent' => billing_money($globalDiscountPercent),
            'discount_on_taxable' => billing_money($discountOnTaxable),
            'discount_on_exempt' => billing_money($discountOnExempt),
            'taxable_net_amount' => $taxableAfterGlobal,
            'exempt_net_amount' => $exemptAfterGlobal,
            'net_amount' => $netAfterGlobal,
            'taxes_amount' => $taxAmount,
            'total_amount' => $totalAmount,
            'source_currency_code' => 'CLP',
            'document_currency_code' => 'CLP',
            'exchange_rate_value' => 1.0,
            'clp_total_amount' => $totalAmount
        );

        if ($isExport) {
            $currencyCode = strtoupper(trim((string)($options['currency_code'] ?? 'USD')));
            $pricingMode = trim((string)($options['export_pricing_mode'] ?? 'convert_from_clp'));
            $exchangeRate = (float)($options['exchange_rate_value'] ?? 0);
            if ($currencyCode !== 'CLP' && $pricingMode === 'convert_from_clp') {
                if ($exchangeRate <= 0) {
                    $result['errors'][] = 'La conversión de la cotización a moneda extranjera requiere tipo de cambio mayor a cero.';
                } else {
                    $result = $this->convertCalculationFromClp($result, $exchangeRate, $currencyCode);
                }
            } else {
                $result['document_currency_code'] = $currencyCode ?: 'CLP';
                $result['exchange_rate_value'] = $currencyCode === 'CLP' ? 1.0 : max($exchangeRate, 0);
                $result['clp_total_amount'] = $currencyCode === 'CLP' ? $result['total_amount'] : billing_money($result['total_amount'] * max($exchangeRate, 0));
            }
        }

        return $result;
    }

    private function convertCalculationFromClp(array $result, $exchangeRate, $currencyCode)
    {
        $fields = array(
            'subtotal_before_global_discount','taxable_before_global_discount','exempt_before_global_discount',
            'global_discount_amount','discount_on_taxable','discount_on_exempt','taxable_net_amount',
            'exempt_net_amount','net_amount','taxes_amount','total_amount'
        );
        foreach ($fields as $field) {
            $result[$field] = round(((float)($result[$field] ?? 0)) / $exchangeRate, 2);
        }
        foreach ($result['lines'] as &$line) {
            foreach (array('unit_price','base_amount','line_discount_amount','net_before_global_discount','global_discount_allocated','net_amount','tax_amount','gross_amount') as $field) {
                $line[$field] = round(((float)($line[$field] ?? 0)) / $exchangeRate, 2);
            }
        }
        unset($line);
        $result['source_currency_code'] = 'CLP';
        $result['document_currency_code'] = $currencyCode;
        $result['exchange_rate_value'] = $exchangeRate;
        $result['clp_total_amount'] = billing_money($result['total_amount'] * $exchangeRate);
        $result['warnings'][] = 'Los montos de la cotización en CLP fueron convertidos a ' . $currencyCode . ' usando tipo de cambio ' . number_format($exchangeRate, 4, ',', '.') . '.';
        return $result;
    }

    private function resolveProduct(array $item)
    {
        $productId = isset($item['product_id']) && is_numeric($item['product_id']) ? (int) $item['product_id'] : 0;
        if ($productId > 0) {
            $stmt = $this->pdo->prepare('SELECT * FROM products WHERE id = ? LIMIT 1');
            $stmt->execute(array($productId));
            $product = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($product) {
                return $product;
            }
        }

        $sku = billing_extract_sku($item['name'] ?? '', $item['description'] ?? '');
        if ($sku !== '') {
            $stmt = $this->pdo->prepare('SELECT * FROM products WHERE sku = ? LIMIT 1');
            $stmt->execute(array($sku));
            $product = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($product) {
                return $product;
            }
        }

        return null;
    }

    private function resolveTaxTreatment(array $item, $product)
    {
        if ($product && isset($product['tax_treatment']) && in_array($product['tax_treatment'], array('afecto', 'exento'), true)) {
            return $product['tax_treatment'];
        }
        if (isset($item['tax_treatment']) && in_array($item['tax_treatment'], array('afecto', 'exento'), true)) {
            return $item['tax_treatment'];
        }
        return 'afecto';
    }

    private function resolveTaxRate(array $item, $product, $taxTreatment)
    {
        if ($taxTreatment === 'exento') {
            return 0.0;
        }
        if ($product && isset($product['tax_rate'])) {
            return (float) $product['tax_rate'];
        }
        if (isset($item['tax_rate'])) {
            return (float) $item['tax_rate'];
        }
        return 19.0;
    }

    private function resolveUnitMeasure(array $item, $product)
    {
        if ($product && !empty($product['dte_unit_code'])) {
            return billing_normalize_unit_measure($product['dte_unit_code']);
        }
        if (!empty($item['unit'])) {
            return billing_normalize_unit_measure($item['unit']);
        }
        if ($product && !empty($product['unit'])) {
            return billing_normalize_unit_measure($product['unit']);
        }
        return 'UN';
    }
}
