<?php

require_once __DIR__ . '/../../core/database.php';
require_once __DIR__ . '/../../core/config.php';
require_once __DIR__ . '/BillingHelpers.php';
require_once __DIR__ . '/BillingRuntimeGate.php';
require_once __DIR__ . '/FactoDocumentResponseParser.php';
require_once __DIR__ . '/../facto/FactoClient.php';
require_once __DIR__ . '/../facto/FactoFileStorageService.php';
require_once __DIR__ . '/../costs/ProductCostSnapshotService.php';
require_once __DIR__ . '/../finance/ExchangeRateService.php';

class BillingExportInvoiceService
{
    private $pdo;
    private $storage;
    private $exchangeRates;

    public function __construct(?PDO $pdo = null)
    {
        $this->pdo = $pdo ?: db();
        $this->storage = new FactoFileStorageService();
        $this->exchangeRates = new ExchangeRateService($this->pdo);
    }

    public function catalogs()
    {
        $customers = $this->pdo->query("SELECT id, customer_name, commercial_name, legal_name, customer_tax_id, export_tax_id_code, export_tax_id_type, email, email_dte, phone, address, district, city, country, country_id, export_country_id, business_activity, payment_terms FROM crm_customers WHERE deleted_at IS NULL AND counterparty_type IN ('customer','both') ORDER BY COALESCE(NULLIF(legal_name,''), NULLIF(commercial_name,''), customer_name) ASC LIMIT 1000")
            ->fetchAll(PDO::FETCH_ASSOC);
        $countries = $this->pdo->query("SELECT country_name, facto_country_id FROM billing_countries WHERE is_active=1 ORDER BY country_name ASC")
            ->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 ASC")
            ->fetchAll(PDO::FETCH_ASSOC);

        return array('customers' => $customers, 'countries' => $countries, 'currencies' => $currencies);
    }

    public function preview(array $input, $userId = null)
    {
        $data = $this->normalizeInput($input);
        billing_assert_runtime_operation('preview', $data['environment'] ?? 'sandbox', 'Preview de factura de exportación');
        $this->validateInput($data);
        $payload = $this->buildPayload($data, true);
        $operationId = $this->createOrResetOperation($data, $payload, $userId);

        try {
            $client = new FactoClient($data['environment']);
            $response = $client->createDocumentDraftPreview($payload, array('generate_stamp' => 1));
            $parser = new FactoDocumentResponseParser($response);
            $parser->assertPreviewReady();
            $files = $this->storage->saveElectronicDocumentFiles('export_preview_' . $operationId, $parser->electronicDocument(), $data['environment']);

            $stmt = $this->pdo->prepare("UPDATE billing_export_operations SET status='preview_ready', preview_response_payload=?, preview_facto_document_id=?, preview_document_number=?, preview_pdf_file_path=?, preview_xml_file_path=?, error_code=NULL, error_message=NULL WHERE id=?");
            $stmt->execute(array(
                $this->json($parser->safeResponse()),
                $parser->documentId(),
                $parser->documentNumber(),
                $files['pdf_file_path'] ?? null,
                $files['xml_file_path'] ?? null,
                $operationId
            ));

            return array(
                'ok' => true,
                'operation_id' => $operationId,
                'message' => 'Preview FACTO verificable generado. Es un borrador y no tiene validez tributaria.',
                'preview' => array(
                    'facto_document_id' => $parser->documentId(),
                    'document_number' => $parser->documentNumber(),
                    'has_pdf' => !empty($files['pdf_file_path']),
                    'has_xml' => !empty($files['xml_file_path']),
                    'pdf_url' => !empty($files['pdf_file_path']) ? '/modules/billing/export_invoice_file.php?operation_id=' . $operationId . '&kind=preview_pdf' : null
                ),
                'summary' => array(
                    'currency_code' => $data['currency_code'],
                    'exchange_rate_value' => $data['exchange_rate_value'],
                    'exchange_rate_date' => $data['exchange_rate_date'],
                    'exchange_rate_source' => $data['exchange_rate_source'],
                    'exchange_rate_status' => $data['exchange_rate_status'],
                    'total_amount' => $data['total_amount'],
                    'total_amount_clp' => $data['total_amount_clp'],
                    'receiver_legal_name' => $data['receiver_legal_name']
                )
            );
        } catch (Throwable $e) {
            $this->pdo->prepare("UPDATE billing_export_operations SET status='error', error_code='PREVIEW_ERROR', error_message=? WHERE id=?")
                ->execute(array($e->getMessage(), $operationId));
            throw $e;
        }
    }

    public function issue($operationId, $confirmation, $userId = null)
    {
        $operationId = (int)$operationId;
        if ($operationId <= 0) {
            throw new InvalidArgumentException('Operación de exportación inválida.');
        }

        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare('SELECT * FROM billing_export_operations WHERE id=? FOR UPDATE');
            $stmt->execute(array($operationId));
            $op = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$op) throw new RuntimeException('Operación de exportación no encontrada.');
            billing_assert_runtime_operation('issue', $op['environment'] ?? 'sandbox', 'Emisión de factura de exportación');
            billing_assert_issue_confirmation($confirmation, $op['environment'] ?? 'sandbox');
            if ($op['status'] === 'issued') throw new RuntimeException('La factura de exportación ya fue emitida.');
            if ($op['status'] !== 'preview_ready') throw new RuntimeException('Debes generar un preview verificable antes de emitir. Estado actual: ' . $op['status'] . '.');
            $payload = json_decode((string)$op['preview_request_payload'], true);
            if (!is_array($payload)) throw new RuntimeException('No fue posible recuperar el payload validado.');
            if (!isset($payload['options']) || !is_array($payload['options'])) $payload['options'] = array();
            unset($payload['options']['draft_preview'], $payload['options']['draft_only']);
            $this->pdo->prepare("UPDATE billing_export_operations SET status='issuing', issue_request_payload=?, issued_by=? WHERE id=?")
                ->execute(array($this->json($payload), $userId ?: null, $operationId));
            $this->pdo->commit();
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }

        try {
            $client = new FactoClient($op['environment']);
            $response = $client->createDocument($payload, array('generate_stamp' => 1));
            $parser = new FactoDocumentResponseParser($response);
            $parser->assertIssuedReady();
            $documentId = $parser->documentId();

            $remote = $client->getDocument($documentId, true);
            $remoteParser = new FactoDocumentResponseParser($remote);
            $remoteParser->assertIssuedReady();
            $this->validateRemoteDocument($op, $remoteParser);

            $files = $this->storage->saveElectronicDocumentFiles($documentId, $remoteParser->electronicDocument(), $op['environment']);
            $this->persistIssuedOperation($op, $payload, $remoteParser, $files, $userId);

            return array(
                'ok' => true,
                'operation_id' => $operationId,
                'message' => 'Factura de exportación emitida y registrada correctamente.',
                'issue' => array(
                    'facto_document_id' => $documentId,
                    'document_number' => $remoteParser->documentNumber(),
                    'has_pdf' => !empty($files['pdf_file_path']),
                    'has_xml' => !empty($files['xml_file_path']),
                    'pdf_url' => !empty($files['pdf_file_path']) ? '/modules/billing/export_invoice_file.php?operation_id=' . $operationId . '&kind=pdf' : null,
                    'xml_url' => !empty($files['xml_file_path']) ? '/modules/billing/export_invoice_file.php?operation_id=' . $operationId . '&kind=xml' : null
                )
            );
        } catch (Throwable $e) {
            $status = 'error';
            $code = 'ISSUE_ERROR';
            if (isset($documentId) && $documentId) {
                $status = 'pending_local_commit';
                $code = 'REMOTE_ISSUED_LOCAL_PENDING';
            }
            $this->pdo->prepare("UPDATE billing_export_operations SET status=?, issue_response_payload=?, facto_document_id=COALESCE(?,facto_document_id), error_code=?, error_message=? WHERE id=?")
                ->execute(array($status, isset($response) ? $this->json((new FactoDocumentResponseParser($response))->safeResponse()) : null, $documentId ?? null, $code, $e->getMessage(), $operationId));
            throw $e;
        }
    }

    public function getOperation($operationId)
    {
        $stmt = $this->pdo->prepare('SELECT * FROM billing_export_operations WHERE id=? LIMIT 1');
        $stmt->execute(array((int)$operationId));
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }

    private function normalizeInput(array $input)
    {
        $issueDate = $this->date($input['issue_date'] ?? date('Y-m-d'));
        $currencyCode = strtoupper(trim((string)($input['currency_code'] ?? 'USD')));
        $manualExchange = (float)($input['exchange_rate_value'] ?? 0);
        $rateResult = $this->exchangeRates->resolveWithManualFallback($currencyCode, $issueDate, $manualExchange);
        if (empty($rateResult['ok'])) {
            throw new InvalidArgumentException($rateResult['message'] ?? 'No fue posible obtener el tipo de cambio para la fecha de emisión.');
        }
        $exchange = (float)$rateResult['rate_to_clp'];
        $currency = $this->currencyByCode($currencyCode);
        $lines = $this->normalizeLines($input['lines'] ?? array());
        $subtotal = 0.0;
        foreach ($lines as $line) $subtotal += (float)$line['total_amount'];
        $globalDiscountType = (($input['global_discount_type'] ?? 'amount') === 'percent') ? 'percent' : 'amount';
        $globalDiscountValue = max(0, (float)($input['global_discount_value'] ?? 0));
        $globalDiscountAmount = $globalDiscountType === 'percent'
            ? round($subtotal * ($globalDiscountValue / 100), 4)
            : round($globalDiscountValue, 4);
        if ($globalDiscountAmount > $subtotal + 0.0001) {
            throw new InvalidArgumentException('El descuento global supera el subtotal del documento.');
        }
        if ($globalDiscountAmount > 0 && $subtotal > 0) {
            $allocated = 0.0;
            $last = count($lines) - 1;
            foreach ($lines as $idx => &$line) {
                $allocation = $idx === $last
                    ? round($globalDiscountAmount - $allocated, 4)
                    : round($globalDiscountAmount * ((float)$line['total_amount'] / $subtotal), 4);
                $allocated += $allocation;
                $line['discount_amount'] = round((float)$line['discount_amount'] + $allocation, 4);
                $line['total_amount'] = round(max(0, (float)$line['total_amount'] - $allocation), 4);
            }
            unset($line);
        }
        $total = 0.0;
        foreach ($lines as $line) $total += (float)$line['total_amount'];
        $total = round($total, 2);

        return array(
            'operation_id' => (int)($input['operation_id'] ?? 0),
            'client_request_id' => trim((string)($input['client_request_id'] ?? $input['manual_key'] ?? '')),
            'environment' => strtolower(trim((string)($input['environment'] ?? 'sandbox'))) === 'production' ? 'production' : 'sandbox',
            'source_mode' => 'manual',
            'customer_id' => !empty($input['customer_id']) ? (int)$input['customer_id'] : null,
            'create_customer' => !empty($input['create_customer']) ? 1 : 0,
            'issue_date' => $issueDate,
            'payment_terms' => trim((string)($input['payment_terms'] ?? ($input['payment_terms_days'] ?? '0'))) ?: '0',
            'finance_center_id' => !empty($input['finance_center_id']) ? (int)$input['finance_center_id'] : null,
            'receiver_legal_name' => trim((string)($input['receiver_legal_name'] ?? '')),
            'receiver_tax_id_code' => trim((string)($input['receiver_tax_id_code'] ?? '')),
            'receiver_tax_id_type' => trim((string)($input['receiver_tax_id_type'] ?? '')),
            'receiver_country_id' => (int)($input['receiver_country_id'] ?? 0),
            'receiver_address' => trim((string)($input['receiver_address'] ?? '')),
            'receiver_district' => trim((string)($input['receiver_district'] ?? '')),
            'receiver_city' => trim((string)($input['receiver_city'] ?? '')),
            'receiver_phone' => trim((string)($input['receiver_phone'] ?? '')),
            'receiver_activity' => trim((string)($input['receiver_activity'] ?? '')),
            'receiver_email' => trim((string)($input['receiver_email'] ?? '')),
            'currency_code' => $currencyCode,
            'currency_id' => (int)$currency['facto_currency_id'],
            'exchange_rate_value' => $exchange,
            'exchange_rate_date' => $rateResult['rate_date'],
            'exchange_rate_source' => $rateResult['source'],
            'exchange_rate_status' => $rateResult['quality_status'],
            'sale_modality' => (int)($input['sale_modality'] ?? 1),
            'sale_clause' => (int)($input['sale_clause'] ?? 6),
            'purchase_order_number' => billing_safe_substr(trim((string)($input['purchase_order_number'] ?? '')), 60),
            'purchase_order_date' => $this->date($input['purchase_order_date'] ?? ($input['issue_date'] ?? date('Y-m-d'))),
            'global_discount_type' => $globalDiscountType,
            'global_discount_value' => $globalDiscountValue,
            'global_discount_amount' => $globalDiscountAmount,
            'observations' => trim((string)($input['observations'] ?? '')),
            'lines' => $lines,
            'net_amount' => $total,
            'tax_amount' => 0.0,
            'total_amount' => $total,
            'total_amount_clp' => round($total * $exchange, 2)
        );
    }

    private function normalizeLines($raw)
    {
        if (is_string($raw)) $raw = json_decode($raw, true);
        if (!is_array($raw)) $raw = array();
        $lines = array();
        foreach ($raw as $idx => $line) {
            if (!is_array($line)) continue;
            $quantity = round((float)($line['quantity'] ?? 0), 4);
            $unitPrice = round((float)($line['unit_price'] ?? 0), 4);
            $gross = round($quantity * $unitPrice, 4);
            $discountType = (($line['discount_type'] ?? '') === 'percent') ? 'percent' : 'amount';
            $discountValue = max(0, (float)($line['discount_value'] ?? 0));
            $discountPercent = max(0, min(100, (float)($line['discount_percent'] ?? ($discountType === 'percent' ? $discountValue : 0))));
            $discount = round(max(0, (float)($line['discount_amount'] ?? ($discountType === 'amount' ? $discountValue : 0))), 4);
            if ($discountPercent > 0) {
                $discount = round($gross * ($discountPercent / 100), 4);
            }
            $total = round(max(0, $gross - $discount), 4);
            $lines[] = array(
                'source_line_id' => !empty($line['source_line_id']) ? (int)$line['source_line_id'] : null,
                'product_id' => !empty($line['product_id']) ? (int)$line['product_id'] : null,
                'sku' => trim((string)($line['sku'] ?? '')),
                'description' => trim((string)($line['description'] ?? '')),
                'long_description' => trim((string)($line['long_description'] ?? '')),
                'quantity' => $quantity,
                'unit_measure' => billing_normalize_unit_measure($line['unit_measure'] ?? 'UN'),
                'unit_price' => $unitPrice,
                'discount_amount' => $discount,
                'total_amount' => $total
            );
        }
        return $lines;
    }

    private function validateInput(array $data)
    {
        if ($data['receiver_legal_name'] === '') throw new InvalidArgumentException('Indica la razón social del receptor extranjero.');
        if ($data['receiver_tax_id_code'] === '') throw new InvalidArgumentException('Indica el identificador tributario extranjero.');
        if ($data['receiver_tax_id_type'] === '') throw new InvalidArgumentException('Indica el tipo de identificador tributario extranjero.');
        if ($data['receiver_country_id'] <= 0 || $data['receiver_country_id'] === 253) throw new InvalidArgumentException('Selecciona un país receptor extranjero.');
        if ($data['receiver_address'] === '' || $data['receiver_city'] === '') throw new InvalidArgumentException('Indica dirección y ciudad del receptor.');
        if ($data['receiver_activity'] === '') throw new InvalidArgumentException('Indica la actividad económica del receptor.');
        if ($data['currency_code'] !== 'CLP' && $data['exchange_rate_value'] <= 0) throw new InvalidArgumentException('El tipo de cambio debe ser mayor a cero.');
        if (!in_array($data['sale_modality'], array(1,2,3,4,9), true)) throw new InvalidArgumentException('Modalidad de venta de exportación inválida.');
        if (!in_array($data['sale_clause'], array(1,2,3,4,5,6,9,10,11,12,17,18), true)) throw new InvalidArgumentException('Cláusula de venta inválida.');
        if (empty($data['lines'])) throw new InvalidArgumentException('Agrega al menos una línea a la factura de exportación.');
        foreach ($data['lines'] as $i => $line) {
            if ($line['description'] === '') throw new InvalidArgumentException('La línea ' . ($i + 1) . ' no tiene descripción.');
            if ($line['quantity'] <= 0) throw new InvalidArgumentException('La línea ' . ($i + 1) . ' tiene cantidad inválida.');
            if ($line['unit_price'] < 0 || $line['discount_amount'] > ($line['quantity'] * $line['unit_price'])) throw new InvalidArgumentException('La línea ' . ($i + 1) . ' tiene valores inválidos.');
        }
        if ($data['total_amount'] <= 0) throw new InvalidArgumentException('El total de la factura debe ser mayor a cero.');
    }

    private function buildPayload(array $data, $preview)
    {
        $settings = $this->companySettings();
        $config = icylab_facto_config($data['environment']);
        $header = array(
            'account_id' => (int)($config['account_id'] ?? 0),
            'document_type_id' => '46',
            'received_issued_flag' => 1,
            'issue_date' => $data['issue_date'],
            'issuer_tax_id_code' => billing_clean_rut($settings['company_tax_id'] ?? ''),
            'issuer_tax_id_type' => 'CL-RUT',
            'issuer_legal_name' => trim((string)($settings['company_name'] ?? '')),
            'issuer_address' => trim((string)($settings['company_address'] ?? '')),
            'issuer_district' => trim((string)($settings['company_comuna'] ?? '')),
            'issuer_city' => trim((string)($settings['company_ciudad'] ?? '')),
            'issuer_country_id' => 253,
            'issuer_phone' => trim((string)($settings['company_phone'] ?? '')),
            'issuer_activity' => trim((string)($settings['company_giro'] ?? '')),
            'receiver_tax_id_code' => $data['receiver_tax_id_code'],
            'receiver_tax_id_type' => $data['receiver_tax_id_type'],
            'receiver_legal_name' => $data['receiver_legal_name'],
            'receiver_address' => $data['receiver_address'],
            'receiver_district' => $data['receiver_district'],
            'receiver_city' => $data['receiver_city'],
            'receiver_country_id' => $data['receiver_country_id'],
            'receiver_phone' => $data['receiver_phone'],
            'receiver_activity' => $data['receiver_activity'],
            'payment_conditions' => $data['payment_terms'],
            'currency_id' => $data['currency_id'],
            'exchange_rate_value' => $data['exchange_rate_value'],
            'sale_modality' => $data['sale_modality'],
            'sale_clause' => $data['sale_clause'],
            'observations' => $data['observations'] !== '' ? $data['observations'] : 'ICYLAB OS - Factura de exportación manual'
        );
        if ($data['receiver_email'] !== '') $header['receiver_email'] = $data['receiver_email'];

        $details = array();
        foreach ($data['lines'] as $line) {
            $detail = array(
                'quantity' => $line['quantity'],
                'sku' => $line['sku'],
                'line_description' => billing_safe_substr($line['description'], 100),
                'unit_measure' => $line['unit_measure'],
                'unit_price' => $line['unit_price'],
                'long_description' => $line['long_description'],
                'modifier_amount' => $line['discount_amount'] > 0 ? -1 * $line['discount_amount'] : 0,
                'modifier_percentage' => 0,
                'total_taxes' => 0,
                'total_amount_line' => $line['total_amount'],
                'taxes' => array()
            );
            $details[] = $detail;
        }

        $payload = array(
            'options' => array('rounding_type' => 'net', 'draft_preview' => $preview ? true : false),
            'header' => $header,
            'details' => $details,
            'totals' => array('net_amount' => $data['net_amount'], 'taxes_amount' => 0, 'total_amount' => $data['total_amount'])
        );
        if ($data['purchase_order_number'] !== '') {
            $payload['references'] = array(array(
                'document_type_id' => '801',
                'reference_number' => $data['purchase_order_number'],
                'reference_code' => 5,
                'reference_date' => $data['purchase_order_date'],
                'description' => 'Orden de compra'
            ));
        }
        return $payload;
    }

    private function createOrResetOperation(array $data, array $payload, $userId)
    {
        $operationId = (int)$data['operation_id'];
        $requestId = trim((string)($data['client_request_id'] ?? ''));
        if ($requestId === '') {
            $requestId = hash('sha256', $data['issue_date'] . '|' . $data['receiver_tax_id_code'] . '|' . $data['currency_code'] . '|' . $data['total_amount'] . '|' . json_encode($data['lines'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
        $idempotency = 'manual_export:' . hash('sha256', $requestId);

        $this->pdo->beginTransaction();
        try {
            if ($operationId <= 0) {
                $stmt = $this->pdo->prepare("SELECT id,status FROM billing_export_operations WHERE idempotency_key=? FOR UPDATE");
                $stmt->execute(array($idempotency));
                $existingByKey = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($existingByKey) $operationId = (int)$existingByKey['id'];
            }
            if ($operationId > 0) {
                $stmt = $this->pdo->prepare("SELECT id,status FROM billing_export_operations WHERE id=? FOR UPDATE");
                $stmt->execute(array($operationId));
                $existing = $stmt->fetch(PDO::FETCH_ASSOC);
                if (!$existing) {
                    $operationId = 0;
                } elseif (in_array($existing['status'], array('issued','issuing','pending_local_commit','remote_uncertain'), true)) {
                    throw new RuntimeException('Ya existe una operación de exportación emitida o pendiente para esta misma solicitud.');
                }
            }
            $params = array(
                $data['environment'],'manual',null,null,$data['customer_id'],$data['create_customer'],46,$data['issue_date'],$data['payment_terms'],$data['finance_center_id'],
                $data['receiver_legal_name'],$data['receiver_tax_id_code'],$data['receiver_tax_id_type'],$data['receiver_country_id'],$data['receiver_address'],$data['receiver_district'],$data['receiver_city'],$data['receiver_phone'],$data['receiver_activity'],$data['receiver_email'],
                $data['currency_code'],$data['currency_id'],$data['exchange_rate_value'],$data['exchange_rate_date'],$data['exchange_rate_source'],$data['exchange_rate_status'],$data['sale_modality'],$data['sale_clause'],'document_currency',$data['global_discount_type'],$data['global_discount_value'],$data['global_discount_amount'],$data['net_amount'],0,$data['total_amount'],$data['total_amount_clp'],'draft',$idempotency,$this->json($payload),$userId ?: null
            );
            if ($operationId > 0) {
                $sql = "UPDATE billing_export_operations SET environment=?,source_mode=?,quote_id=?,opportunity_id=?,customer_id=?,create_customer=?,document_type_id=?,issue_date=?,payment_terms=?,finance_center_id=?,receiver_legal_name=?,receiver_tax_id_code=?,receiver_tax_id_type=?,receiver_country_id=?,receiver_address=?,receiver_district=?,receiver_city=?,receiver_phone=?,receiver_activity=?,receiver_email=?,currency_code=?,currency_id=?,exchange_rate_value=?,exchange_rate_date=?,exchange_rate_source=?,exchange_rate_status=?,sale_modality=?,sale_clause=?,pricing_mode=?,global_discount_type=?,global_discount_value=?,global_discount_amount=?,net_amount=?,tax_amount=?,total_amount=?,total_amount_clp=?,status=?,idempotency_key=?,preview_request_payload=?,preview_response_payload=NULL,preview_facto_document_id=NULL,preview_document_number=NULL,preview_pdf_file_path=NULL,preview_xml_file_path=NULL,error_code=NULL,error_message=NULL,created_by=? WHERE id=?";
                $params[] = $operationId;
                $this->pdo->prepare($sql)->execute($params);
                $this->pdo->prepare('DELETE FROM billing_export_operation_lines WHERE operation_id=?')->execute(array($operationId));
            } else {
                $sql = "INSERT INTO billing_export_operations (environment,source_mode,quote_id,opportunity_id,customer_id,create_customer,document_type_id,issue_date,payment_terms,finance_center_id,receiver_legal_name,receiver_tax_id_code,receiver_tax_id_type,receiver_country_id,receiver_address,receiver_district,receiver_city,receiver_phone,receiver_activity,receiver_email,currency_code,currency_id,exchange_rate_value,exchange_rate_date,exchange_rate_source,exchange_rate_status,sale_modality,sale_clause,pricing_mode,global_discount_type,global_discount_value,global_discount_amount,net_amount,tax_amount,total_amount,total_amount_clp,status,idempotency_key,preview_request_payload,created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
                $this->pdo->prepare($sql)->execute($params);
                $operationId = (int)$this->pdo->lastInsertId();
            }
            $this->insertOperationLines($operationId, $data['lines']);
            $this->pdo->commit();
            return $operationId;
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    private function insertOperationLines($operationId, array $lines)
    {
        $stmt = $this->pdo->prepare('INSERT INTO billing_export_operation_lines (operation_id,source_line_id,product_id,sku,description,long_description,quantity,unit_measure,unit_price,discount_amount,total_amount) VALUES (?,?,?,?,?,?,?,?,?,?,?)');
        foreach ($lines as $line) {
            $stmt->execute(array($operationId,$line['source_line_id'],$line['product_id'],$line['sku'],$line['description'],$line['long_description'],$line['quantity'],$line['unit_measure'],$line['unit_price'],$line['discount_amount'],$line['total_amount']));
        }
    }

    private function persistIssuedOperation(array $op, array $payload, FactoDocumentResponseParser $parser, array $files, $userId)
    {
        $documentId = $parser->documentId();
        $documentNumber = $parser->documentNumber();
        $header = $parser->header();
        $totals = $parser->totals();
        $dueDate = $this->dueDate($op['issue_date'], $op['payment_terms']);
        $customerId = (int)($op['customer_id'] ?? 0);
        if ($customerId <= 0 && (int)($op['create_customer'] ?? 0) === 1) {
            $customerId = $this->findOrCreateExportCustomer($op);
        }
        $this->pdo->beginTransaction();
        try {
            $this->pdo->prepare("UPDATE billing_export_operations SET status='issued', issue_response_payload=?, facto_document_id=?, document_number=?, pdf_file_path=?, xml_file_path=?, customer_id=?, error_code=NULL,error_message=NULL,issued_by=?,issued_at=NOW() WHERE id=?")
                ->execute(array($this->json($parser->safeResponse()),$documentId,$documentNumber,$files['pdf_file_path'] ?? null,$files['xml_file_path'] ?? null,$customerId ?: null,$userId ?: null,$op['id']));

            $stmt = $this->pdo->prepare("INSERT INTO facto_documents (document_id,environment,account_id,issue_date,document_number,document_type_id,issuer_tax_id_code,receiver_legal_name,receiver_tax_id_code,crm_customer_id,finance_center_id,link_status,link_confidence,linked_by_user_id,linked_at,document_status,currency_id,received_issued_flag,net_amount,taxes_amount,total_amount,payment_status,payment_due_date,document_type_taxbureau,pdf_file_path,xml_file_path,raw_payload,payload_hash,last_synced_at,currency_code,exchange_rate_value,receiver_country_id,receiver_tax_id_type,sale_modality,sale_clause,source_mode) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW(),?,?,?,?,?,?,?,?,?,?,?,?,SHA2(?,256),NOW(),?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE document_number=VALUES(document_number),crm_customer_id=VALUES(crm_customer_id),net_amount=VALUES(net_amount),taxes_amount=VALUES(taxes_amount),total_amount=VALUES(total_amount),pdf_file_path=VALUES(pdf_file_path),xml_file_path=VALUES(xml_file_path),raw_payload=VALUES(raw_payload),currency_code=VALUES(currency_code),exchange_rate_value=VALUES(exchange_rate_value),receiver_country_id=VALUES(receiver_country_id),receiver_tax_id_type=VALUES(receiver_tax_id_type),sale_modality=VALUES(sale_modality),sale_clause=VALUES(sale_clause),source_mode=VALUES(source_mode),last_synced_at=NOW()");
            $raw = $this->json($parser->safeResponse());
            $stmt->execute(array(
                $documentId,$op['environment'],$header['account_id'] ?? null,$header['issue_date'] ?? $op['issue_date'],$documentNumber,46,$header['issuer_tax_id_code'] ?? null,$op['receiver_legal_name'],$op['receiver_tax_id_code'],$customerId ?: null,$op['finance_center_id'] ?: null,$customerId ? 'manual_linked' : 'unlinked',$customerId ? 100 : null,$userId ?: null,$header['document_status'] ?? 1,$op['currency_id'],1,
                $totals['net_amount'] ?? $op['net_amount'],0,$totals['total_amount'] ?? $op['total_amount'],'por_cobrar',$dueDate,'110',$files['pdf_file_path'] ?? null,$files['xml_file_path'] ?? null,$raw,$raw,$op['currency_code'],$op['exchange_rate_value'],$op['receiver_country_id'],$op['receiver_tax_id_type'],$op['sale_modality'],$op['sale_clause'],'manual'
            ));

            $this->pdo->prepare("UPDATE facto_documents SET exchange_rate_date=?,exchange_rate_source=?,exchange_rate_status=?,net_amount_clp=?,taxes_amount_clp=0,total_amount_clp=? WHERE environment=? AND BINARY document_id=BINARY ?")
                ->execute(array($op['exchange_rate_date'],$op['exchange_rate_source'],$op['exchange_rate_status'],round((float)($totals['net_amount'] ?? $op['net_amount']) * (float)$op['exchange_rate_value'],2),$op['total_amount_clp'],$op['environment'],$documentId));

            $this->pdo->prepare('DELETE FROM billing_document_lines WHERE environment=? AND facto_document_id=?')->execute(array($op['environment'],$documentId));
            $lines = $this->pdo->prepare('SELECT * FROM billing_export_operation_lines WHERE operation_id=? ORDER BY id');
            $lines->execute(array($op['id']));
            $lineInsert = $this->pdo->prepare("INSERT INTO billing_document_lines (environment,facto_document_id,quote_id,source_line_id,product_id,sku,description,quantity,unit_measure,unit_price,discount_percent,discount_amount,net_amount,tax_amount,total_amount,tax_treatment,finance_center_id,raw_payload) VALUES (?,?,?,?,?,?,?,?,?,?,0,?,?,0,?,'exento',?,?)");
            foreach ($lines->fetchAll(PDO::FETCH_ASSOC) as $line) {
                $lineInsert->execute(array($op['environment'],$documentId,null,$line['source_line_id'],$line['product_id'],$line['sku'],$line['description'],$line['quantity'],$line['unit_measure'],$line['unit_price'],$line['discount_amount'],$line['total_amount'],$line['total_amount'],$op['finance_center_id'] ?: null,$this->json($line)));
            }

            $stmt = $this->pdo->prepare("INSERT INTO billing_collections (environment,direction,source_table,facto_document_id,quote_id,opportunity_id,customer_id,document_number,document_type_id,issue_date,due_date,total_amount,paid_amount,balance_amount,collection_status,payment_terms,last_payment_date,finance_center_id,notes,currency_code,currency_id,exchange_rate_value,exchange_rate_date,exchange_rate_source,exchange_rate_status,foreign_total_amount,foreign_paid_amount,foreign_balance_amount) VALUES (?,'receivable','facto_documents',?,NULL,NULL,?,?,?,?,?,?,?,?,?,?,NULL,?,?,?,?,?,?,?,?,?,0,?) ON DUPLICATE KEY UPDATE customer_id=VALUES(customer_id),document_number=VALUES(document_number),total_amount=VALUES(total_amount),balance_amount=VALUES(balance_amount),collection_status=VALUES(collection_status),payment_terms=VALUES(payment_terms),currency_code=VALUES(currency_code),currency_id=VALUES(currency_id),exchange_rate_value=VALUES(exchange_rate_value),exchange_rate_date=VALUES(exchange_rate_date),exchange_rate_source=VALUES(exchange_rate_source),exchange_rate_status=VALUES(exchange_rate_status),foreign_total_amount=VALUES(foreign_total_amount),foreign_balance_amount=VALUES(foreign_balance_amount),updated_at=CURRENT_TIMESTAMP");
            $stmt->execute(array($op['environment'],$documentId,$customerId ?: null,$documentNumber,46,$op['issue_date'],$dueDate,$op['total_amount_clp'],0,$op['total_amount_clp'],'por_cobrar',$op['payment_terms'],$op['finance_center_id'] ?: null,'Factura de exportación manual emitida desde ICYLAB OS.',$op['currency_code'],$op['currency_id'],$op['exchange_rate_value'],$op['exchange_rate_date'],$op['exchange_rate_source'],$op['exchange_rate_status'],$op['total_amount'],$op['total_amount']));

            $this->exchangeRates->applyToFactoDocumentByDocumentId($op['environment'], $documentId);

            $this->pdo->commit();
            (new ProductCostSnapshotService($this->pdo))->snapshotDocumentSafe($op['environment'], $documentId, $userId);
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    private function findOrCreateExportCustomer(array $op)
    {
        $taxCode = trim((string)$op['receiver_tax_id_code']);
        $taxType = trim((string)$op['receiver_tax_id_type']);
        if ($taxCode !== '') {
            $stmt = $this->pdo->prepare("SELECT id FROM crm_customers WHERE deleted_at IS NULL AND ((export_tax_id_code=? AND export_tax_id_type=?) OR customer_tax_id=?) LIMIT 1");
            $stmt->execute(array($taxCode, $taxType, $taxCode));
            $existing = (int)$stmt->fetchColumn();
            if ($existing > 0) return $existing;
        }

        $countryName = 'Exterior';
        if (!empty($op['receiver_country_id'])) {
            $stmt = $this->pdo->prepare("SELECT country_name FROM billing_countries WHERE facto_country_id=? LIMIT 1");
            $stmt->execute(array((int)$op['receiver_country_id']));
            $countryName = (string)($stmt->fetchColumn() ?: 'Exterior');
        }
        $stmt = $this->pdo->prepare("INSERT INTO crm_customers
            (counterparty_type,customer_name,commercial_name,legal_name,customer_tax_id,export_tax_id_code,export_tax_id_type,tax_id_type,
             email,email_dte,phone,address,payment_terms,default_income_center_id,district,city,country,country_id,export_country_id,
             business_activity,source,is_supplier,supplier_status,created_at,updated_at)
            VALUES ('customer',:customer_name,:commercial_name,:legal_name,NULL,:export_tax_id_code,:export_tax_id_type,:tax_id_type,
                    :email,:email_dte,:phone,:address,:payment_terms,:default_income_center_id,:district,:city,:country,:country_id,
                    :export_country_id,:business_activity,'manual_billing_export',0,'active',NOW(),NOW())");
        $stmt->execute(array(
            ':customer_name' => $op['receiver_legal_name'],
            ':commercial_name' => $op['receiver_legal_name'],
            ':legal_name' => $op['receiver_legal_name'],
            ':export_tax_id_code' => $taxCode,
            ':export_tax_id_type' => $taxType,
            ':tax_id_type' => $taxType,
            ':email' => $op['receiver_email'],
            ':email_dte' => $op['receiver_email'],
            ':phone' => $op['receiver_phone'],
            ':address' => $op['receiver_address'],
            ':payment_terms' => $op['payment_terms'] ?: 'Contado',
            ':default_income_center_id' => $op['finance_center_id'] ?: null,
            ':district' => $op['receiver_district'],
            ':city' => $op['receiver_city'],
            ':country' => $countryName,
            ':country_id' => $op['receiver_country_id'] ?: null,
            ':export_country_id' => $op['receiver_country_id'] ?: null,
            ':business_activity' => $op['receiver_activity'],
        ));
        return (int)$this->pdo->lastInsertId();
    }

    private function validateRemoteDocument(array $op, FactoDocumentResponseParser $parser)
    {
        $header = $parser->header();
        $totals = $parser->totals();
        if ((int)($header['document_type_id'] ?? 0) !== 46) throw new RuntimeException('FACTO devolvió un tipo documental distinto a factura de exportación.');
        if (trim((string)($header['receiver_tax_id_code'] ?? '')) !== trim((string)$op['receiver_tax_id_code'])) throw new RuntimeException('El receptor del documento remoto no coincide con la operación.');
        if (abs((float)($totals['total_amount'] ?? 0) - (float)$op['total_amount']) > 0.02) throw new RuntimeException('El total remoto no coincide con la operación de exportación.');
    }

    private function companySettings()
    {
        $stmt = $this->pdo->query('SELECT * FROM system_settings WHERE id=1 LIMIT 1');
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: array();
    }

    private function currencyByCode($code)
    {
        $stmt = $this->pdo->prepare('SELECT * FROM billing_currencies WHERE currency_code=? AND is_active=1 LIMIT 1');
        $stmt->execute(array($code));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) throw new InvalidArgumentException('Moneda no configurada: ' . $code . '.');
        return $row;
    }

    private function dueDate($issueDate, $paymentTerms)
    {
        $days = 0;
        if (preg_match('/(\d+)/', (string)$paymentTerms, $m)) $days = (int)$m[1];
        $d = new DateTime($issueDate);
        if ($days > 0) $d->modify('+' . $days . ' days');
        return $d->format('Y-m-d');
    }

    private function date($value)
    {
        $raw = substr(trim((string)$value),0,10);
        $d = DateTime::createFromFormat('Y-m-d',$raw);
        if (!$d || $d->format('Y-m-d') !== $raw) throw new InvalidArgumentException('Fecha de emisión inválida.');
        return $raw;
    }

    private function json($value)
    {
        return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
}
