<?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__ . '/BillingIssueValidationService.php';
require_once __DIR__ . '/../facto/FactoClient.php';
require_once __DIR__ . '/../facto/FactoFileStorageService.php';

class BillingDraftPreviewService
{
    private $pdo;
    private $validator;
    private $storage;

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

    public function createPreview($quoteId, $documentKey, array $options = array())
    {
        $environment = billing_normalize_environment($options['environment'] ?? 'sandbox');
        if ($environment === 'production') {
            // En producción el preview es deliberadamente local. La API pública de
            // FACTO documenta POST /documents como creación; por ello la única
            // llamada remota ocurre después de la confirmación productiva explícita.
            billing_assert_runtime_operation('finance', $environment, 'Validación local previa a emisión');
        } else {
            billing_assert_runtime_operation('preview', $environment, 'Preview tributario FACTO');
        }

        $validation = $this->validator->validateQuoteForIssue($quoteId, $documentKey, $options);
        if (empty($validation['ok'])) {
            return array(
                'ok' => false,
                'blocked' => true,
                'validation' => $validation,
                'message' => 'La validacion tributaria sigue bloqueada. Corrige los bloqueos antes de generar preview FACTO.'
            );
        }

        $payload = $validation['payload_preview'];
        if (!isset($payload['options']) || !is_array($payload['options'])) {
            $payload['options'] = array();
        }
        $payload['options']['draft_preview'] = 1;
        unset($payload['options']['draft_only']);

        if ($environment === 'production') {
            $logId = $this->upsertLog(
                $validation,
                $payload,
                'validated',
                array(
                    'preview_mode' => 'local_validation',
                    'remote_request_sent' => false,
                    'validated_at' => date('c'),
                ),
                null,
                null,
                null,
                null
            );

            return array(
                'ok' => true,
                'blocked' => false,
                'validation' => $validation,
                'preview' => array(
                    'log_id' => (int)$logId,
                    'environment' => 'production',
                    'mode' => 'local_validation',
                    'local_only' => true,
                    'remote_request_sent' => false,
                    'facto_document_id' => null,
                    'document_number' => null,
                    'has_pdf' => false,
                    'has_xml' => false,
                    'pdf_url' => null,
                    'xml_available' => false,
                    'message' => 'Vista previa local aprobada. No se envió ningún documento a FACTO.',
                ),
                'facto_response_summary' => array(
                    'preview_mode' => 'local_validation',
                    'remote_request_sent' => false,
                ),
                'message' => 'Vista previa local aprobada. Revisa receptor, detalle y montos antes de confirmar la emisión real.',
            );
        }

        $logId = $this->upsertLog(
            $validation,
            $payload,
            'validated',
            null,
            null,
            null,
            null,
            null
        );

        try {
            $client = new FactoClient($environment);
            $response = $client->createDocument($payload, array('generate_stamp' => 1), array(
                'document_key' => $validation['document_type']['document_key'] ?? $documentKey,
                'user_id' => function_exists('current_user_id') ? (int)current_user_id() : 0,
                'customer_id' => (int)($validation['customer']['id'] ?? 0),
                'total_amount' => (float)($validation['calculation']['total_amount'] ?? 0),
            ));
            $electronicDocument = $this->extractElectronicDocument($response);
            $factoDocumentId = $this->extractDocumentId($response);
            $documentNumber = $this->extractDocumentNumber($response);
            $fileBase = $this->previewFileBaseName($validation, $factoDocumentId, $logId);
            $saved = $this->savePreviewFiles($electronicDocument, $environment, $fileBase);

            $businessError = $this->extractBusinessError($response);
            $status = $businessError ? 'error' : 'validated';

            $logId = $this->upsertLog(
                $validation,
                $payload,
                $status,
                $response,
                null,
                $factoDocumentId,
                $documentNumber,
                $saved
            );

            if ($businessError) {
                $this->markLogError($logId, 'FACTO_RESULT_ERROR', $businessError);
            }

            return array(
                'ok' => $businessError ? false : true,
                'blocked' => false,
                'validation' => $validation,
                'preview' => array(
                    'log_id' => (int) $logId,
                    'environment' => $environment,
                    'facto_document_id' => $factoDocumentId,
                    'document_number' => $documentNumber,
                    'has_pdf' => !empty($saved['pdf_file_path']),
                    'has_xml' => !empty($saved['xml_file_path']),
                    'pdf_url' => !empty($saved['pdf_file_path']) ? '/modules/billing/preview_pdf.php?log_id=' . (int) $logId : null,
                    'xml_available' => !empty($saved['xml_file_path']),
                    'message' => !empty($saved['pdf_file_path'])
                        ? 'Preview FACTO generado correctamente en ' . $environment . '.'
                        : 'FACTO respondio correctamente, pero no devolvio PDF en la respuesta de preview.'
                ),
                'facto_response_summary' => $this->responseSummary($response),
                'message' => $businessError ?: 'Preview FACTO generado correctamente.'
            );
        } catch (FactoApiException $e) {
            $this->upsertLog(
                $validation,
                $payload,
                'error',
                $e->getDecodedResponse() ?: $e->getResponseBody(),
                $e->getHttpCode(),
                null,
                null,
                null
            );

            return array(
                'ok' => false,
                'blocked' => false,
                'validation' => $validation,
                'message' => $e->getMessage(),
                'http_code' => $e->getHttpCode(),
                'facto_error' => $e->getDecodedResponse()
            );
        } catch (Throwable $e) {
            $this->upsertLog(
                $validation,
                $payload,
                'error',
                array('error' => $e->getMessage()),
                null,
                null,
                null,
                null
            );

            throw $e;
        }
    }

    private function upsertLog(array $validation, array $payload, $status, $responsePayload = null, $httpCode = null, $factoDocumentId = null, $documentNumber = null, $savedFiles = null)
    {
        $quote = $validation['quote'];
        $customer = $validation['customer'];
        $documentType = $validation['document_type'];
        $payment = $validation['payment'];
        $userId = function_exists('current_user_id') ? (int) current_user_id() : 0;

        $pdfPath = is_array($savedFiles) && !empty($savedFiles['pdf_file_path']) ? $savedFiles['pdf_file_path'] : null;
        $xmlPath = is_array($savedFiles) && !empty($savedFiles['xml_file_path']) ? $savedFiles['xml_file_path'] : null;
        $responseEncoded = $responsePayload === null ? null : $this->jsonEncode($responsePayload);

        $stmt = $this->pdo->prepare("\n            INSERT INTO billing_issue_logs\n            (quote_id, opportunity_id, customer_id, document_type_key, document_type_id, environment, mode, status, idempotency_key, request_payload, response_payload, http_code, facto_document_id, document_number, pdf_file_path, xml_file_path, created_by)\n            VALUES\n            (?, ?, ?, ?, ?, ?, 'draft_preview', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n            ON DUPLICATE KEY UPDATE\n                mode = VALUES(mode),\n                status = VALUES(status),\n                request_payload = VALUES(request_payload),\n                response_payload = VALUES(response_payload),\n                http_code = VALUES(http_code),\n                facto_document_id = COALESCE(VALUES(facto_document_id), facto_document_id),\n                document_number = COALESCE(VALUES(document_number), document_number),\n                pdf_file_path = COALESCE(VALUES(pdf_file_path), pdf_file_path),\n                xml_file_path = COALESCE(VALUES(xml_file_path), xml_file_path),\n                updated_at = CURRENT_TIMESTAMP\n        ");

        $stmt->execute(array(
            (int) $quote['id'],
            isset($quote['opportunity_id']) ? (int) $quote['opportunity_id'] : null,
            isset($customer['id']) ? (int) $customer['id'] : null,
            $documentType['document_key'],
            (int) $documentType['facto_document_type_id'],
            $validation['environment'],
            $status,
            $validation['idempotency_key'],
            $this->jsonEncode($payload),
            $responseEncoded,
            $httpCode,
            $factoDocumentId,
            $documentNumber,
            $pdfPath,
            $xmlPath,
            $userId ?: null
        ));

        $stmt = $this->pdo->prepare('SELECT id FROM billing_issue_logs WHERE idempotency_key = ? LIMIT 1');
        $stmt->execute(array($validation['idempotency_key']));
        return (int) $stmt->fetchColumn();
    }

    private function markLogError($logId, $code, $message)
    {
        $stmt = $this->pdo->prepare('UPDATE billing_issue_logs SET error_code = ?, error_message = ? WHERE id = ?');
        $stmt->execute(array($code, $message, (int) $logId));
    }

    private function extractElectronicDocument($response)
    {
        if (!is_array($response)) {
            return array();
        }

        if (isset($response['electronic_document']) && is_array($response['electronic_document'])) {
            return $response['electronic_document'];
        }

        if (isset($response['document']['electronic_document']) && is_array($response['document']['electronic_document'])) {
            return $response['document']['electronic_document'];
        }

        $doc = array();
        foreach (array('document_pdf', 'document_xml', 'stamp_img', 'document_transfer_copy_pdf') as $key) {
            if (!empty($response[$key])) {
                $doc[$key] = $response[$key];
            }
        }

        return $doc;
    }

    private function extractDocumentId($response)
    {
        if (!is_array($response)) {
            return null;
        }
        if (!empty($response['document_id'])) return (string) $response['document_id'];
        if (!empty($response['header']['document_id'])) return (string) $response['header']['document_id'];
        if (!empty($response['document']['document_id'])) return (string) $response['document']['document_id'];
        return null;
    }

    private function extractDocumentNumber($response)
    {
        if (!is_array($response)) {
            return null;
        }
        if (!empty($response['document_number'])) return (string) $response['document_number'];
        if (!empty($response['header']['document_number'])) return (string) $response['header']['document_number'];
        if (!empty($response['document']['header']['document_number'])) return (string) $response['document']['header']['document_number'];
        return null;
    }

    private function extractBusinessError($response)
    {
        if (!is_array($response)) {
            return null;
        }

        if (!empty($response['result']['error_message'])) {
            return (string) $response['result']['error_message'];
        }

        if (!empty($response['error_message'])) {
            return (string) $response['error_message'];
        }

        return null;
    }

    private function savePreviewFiles(array $electronicDocument, $environment, $fileBase)
    {
        $saved = array('pdf_file_path' => null, 'xml_file_path' => null);

        if (!empty($electronicDocument['document_pdf'])) {
            $saved['pdf_file_path'] = $this->storage->saveBase64File(
                $electronicDocument['document_pdf'],
                $environment,
                'preview_pdf',
                $fileBase . '.pdf'
            );
        }

        if (!empty($electronicDocument['document_xml'])) {
            $saved['xml_file_path'] = $this->storage->saveBase64File(
                $electronicDocument['document_xml'],
                $environment,
                'preview_xml',
                $fileBase . '.xml'
            );
        }

        return $saved;
    }

    private function previewFileBaseName(array $validation, $factoDocumentId, $logId)
    {
        $quote = $validation['quote'];
        $doc = $validation['document_type'];
        $raw = ($quote['quote_number'] ?? ('quote_' . $quote['id'])) . '_' . $doc['document_key'] . '_' . ($factoDocumentId ?: ('preview_' . $logId));
        $raw = preg_replace('/[^A-Za-z0-9_\-]/', '_', $raw);
        return $raw ?: ('billing_preview_' . (int) $logId);
    }

    private function responseSummary($response)
    {
        if (!is_array($response)) {
            return array('type' => gettype($response));
        }

        return array(
            'document_id' => $this->extractDocumentId($response),
            'document_number' => $this->extractDocumentNumber($response),
            'has_electronic_document' => !empty($this->extractElectronicDocument($response)),
            'result_status' => $response['result']['status'] ?? null,
            'result_error_message' => $response['result']['error_message'] ?? null,
            'top_level_keys' => array_keys($response)
        );
    }

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