<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Tenant;
use App\Models\CompanySetting;
use App\Models\DianResolution;
use App\Models\Invoice;
use App\Models\Sale;
use App\Services\AuditService;
use App\Services\BillingService;
use App\Services\PdfService;

class SaleController extends Controller
{
    public function index(Request $request): void
    {
        $companyId = (int) Tenant::companyId();

        $perPage = (int) ($request->query('per_page') ?? 20);
        if (!in_array($perPage, [10, 20, 50, 100], true)) {
            $perPage = 20;
        }
        $page    = max(1, (int) ($request->query('page') ?? 1));

        $fechaDesde = trim((string) ($request->query('fecha_desde') ?? ''));
        $fechaHasta = trim((string) ($request->query('fecha_hasta') ?? ''));

        // Filtro rápido mensual: si viene ?mes=2026-04 lo expandimos a rango
        $mes = trim((string) ($request->query('mes') ?? ''));
        if ($mes !== '' && preg_match('/^\d{4}-\d{2}$/', $mes)) {
            $fechaDesde = $mes . '-01';
            $fechaHasta = date('Y-m-t', strtotime($fechaDesde));
        }

        // Por defecto mostrar el mes actual
        if ($fechaDesde === '' && $fechaHasta === '') {
            $fechaDesde = date('Y-m-01');
            $fechaHasta = date('Y-m-d');
        }

        $filters = ['fecha_desde' => $fechaDesde, 'fecha_hasta' => $fechaHasta];
        $saleModel = new Sale();
        $total   = $saleModel->countWithFilters($companyId, $filters);
        $pages   = max(1, (int) ceil($total / $perPage));
        $page    = min($page, $pages);
        $sales   = $saleModel->listWithRelations($companyId, $filters, $page, $perPage);

        $this->render('pos/sales', [
            'title'       => 'Ventas',
            'sales'       => $sales,
            'filters'     => $filters,
            'page'        => $page,
            'pages'       => $pages,
            'total'       => $total,
            'per_page'    => $perPage,
        ]);
    }

    public function show(Request $request): void
    {
        $companyId = (int) Tenant::companyId();
        $saleId = (int) $request->param('id', 0);

        if ($saleId <= 0) {
            flash('error', 'Venta inválida.');
            redirect('ventas');
        }

        $saleModel = new Sale();
        $sale = $saleModel->findWithRelations($companyId, $saleId);
        if ($sale === null) {
            flash('error', 'La venta no existe en la empresa activa.');
            redirect('ventas');
        }

        $invoiceModel = new Invoice();
        $documents = $invoiceModel->listBySaleId($companyId, $saleId);
        $trailTotals = $invoiceModel->trailTotalsBySaleId($companyId, $saleId);

        $this->render('pos/sale_detail', [
            'title' => 'Venta #' . $saleId,
            'sale' => $sale,
            'documents' => $documents,
            'trail_totals' => $trailTotals,
        ]);
    }

    public function annul(Request $request): never
    {
        $companyId = (int) Tenant::companyId();
        $saleId = (int) $request->param('id', 0);

        if ($saleId <= 0) {
            flash('error', 'Venta inválida.');
            redirect('ventas');
        }

        (new Sale())->annul($saleId, $companyId);
        AuditService::track('ventas.anular', 'Venta anulada ID: ' . $saleId);

        flash('success', 'Venta anulada correctamente.');
        redirect('ventas');
    }

    public function creditNote(Request $request): never
    {
        $saleId = (int) $request->param('id', 0);

        try {
            $noteId = (new BillingService())->createCreditNote(
                $saleId,
                (float) $request->input('monto', 0),
                (string) $request->input('motivo', '')
            );

            AuditService::track('facturacion.nota_credito', 'Nota crédito emitida ID: ' . $noteId . ' para venta ID: ' . $saleId);
            flash('success', 'Nota crédito emitida correctamente.');
            redirect('ventas/' . $saleId);
        } catch (\Throwable $e) {
            flash('error', 'No fue posible emitir la nota crédito: ' . $e->getMessage());
            redirect('ventas/' . $saleId);
        }
    }

    public function debitNote(Request $request): never
    {
        $saleId = (int) $request->param('id', 0);

        try {
            $noteId = (new BillingService())->createDebitNote(
                $saleId,
                (float) $request->input('monto', 0),
                (string) $request->input('motivo', '')
            );

            AuditService::track('facturacion.nota_debito', 'Nota débito emitida ID: ' . $noteId . ' para venta ID: ' . $saleId);
            flash('success', 'Nota débito emitida correctamente.');
            redirect('ventas/' . $saleId);
        } catch (\Throwable $e) {
            flash('error', 'No fue posible emitir la nota débito: ' . $e->getMessage());
            redirect('ventas/' . $saleId);
        }
    }

    public function cancelDocument(Request $request): never
    {
        $saleId = (int) $request->param('id', 0);
        $documentId = (int) $request->param('documentId', 0);

        try {
            (new BillingService())->cancelDocument(
                $documentId,
                (string) $request->input('motivo_anulacion', '')
            );

            AuditService::track('facturacion.documento.anular', 'Documento anulado ID: ' . $documentId . ' (venta ID: ' . $saleId . ')');
            flash('success', 'Documento anulado correctamente.');
            redirect('ventas/' . $saleId);
        } catch (\Throwable $e) {
            flash('error', 'No fue posible anular el documento: ' . $e->getMessage());
            redirect('ventas/' . $saleId);
        }
    }

    public function printInvoice(Request $request): void
    {
        $saleId = (int) $request->param('id', 0);
        $this->renderMainDocumentBySale($saleId, false);
    }

    public function downloadInvoicePdf(Request $request): void
    {
        $saleId = (int) $request->param('id', 0);
        $this->renderMainDocumentBySale($saleId, true);
    }

    public function printTicket(Request $request): void
    {
        $companyId = (int) Tenant::companyId();
        $userId = (int) (Auth::id() ?? 0);
        $saleId = (int) $request->param('id', 0);

        if ($companyId <= 0 || $userId <= 0 || $saleId <= 0) {
            flash('error', 'No fue posible cargar el ticket.');
            redirect('ventas');
        }

        $this->assertSalePrintable($companyId, $saleId);

        $invoiceModel = new Invoice();
        $document = $invoiceModel->findMainBySaleId($companyId, $saleId);
        if ($document === null) {
            flash('error', 'La venta seleccionada no tiene factura emitida.');
            redirect('ventas');
        }

        $details = $invoiceModel->details((int) $document['id']);
        $settings = (new CompanySetting())->getMany($companyId, [
            'regimen_fiscal',
            'ambiente_facturacion',
            'resolucion_dian',
            'actividad_economica',
            'responsable_iva',
            'facturacion_electronica',
            'fe_proveedor_tecnologico',
            'fe_proveedor_nit',
            'fe_software_nombre',
            'fe_fabricante_nombre',
            'fe_fabricante_nit',
            'factus_api_url',
            'factus_client_id',
            'factus_client_secret',
            'factus_username',
            'factus_password',
            'factus_software_id',
            'factus_software_pin',
            'factus_numbering_range_id',
            'factus_municipality_id',
        ]);

        $factusConfigured = true;
        foreach ([
            'factus_api_url',
            'factus_client_id',
            'factus_client_secret',
            'factus_username',
            'factus_password',
            'factus_software_id',
            'factus_software_pin',
            'factus_numbering_range_id',
            'factus_municipality_id',
        ] as $requiredFactusKey) {
            if (trim((string) ($settings[$requiredFactusKey] ?? '')) === '') {
                $factusConfigured = false;
                break;
            }
        }

        $invoiceNumber = trim((string) ($document['numero'] ?? ''));
        $prefix = '';
        if ($invoiceNumber !== '' && preg_match('/^[^\d]+/', $invoiceNumber, $matches) === 1) {
            $prefix = trim((string) ($matches[0] ?? ''));
        }

        $branchId = (int) ($document['sucursal_id'] ?? 0);
        $resolutionModel = new DianResolution();
        $resolution = $prefix !== '' && $branchId > 0
            ? $resolutionModel->activeByPrefixForBranch($companyId, $branchId, $prefix)
            : null;

        if (!is_array($resolution)) {
            $resolution = $resolutionModel->latestActiveByBranch($companyId, $branchId);
        }

        $this->render('facturacion/ticket', [
            'title' => 'Ticket ' . (string) ($document['numero'] ?? ''),
            'document' => $document,
            'details' => $details,
            'fiscalSettings' => $settings,
            'factusConfigured' => $factusConfigured,
            'dianResolution' => is_array($resolution) ? $resolution : [],
        ], 'layouts/print');
    }

    public function printDocument(Request $request): void
    {
        $saleId = (int) $request->param('id', 0);
        $documentId = (int) $request->param('documentId', 0);
        $this->renderDocumentById($saleId, $documentId, false);
    }

    public function downloadDocumentPdf(Request $request): void
    {
        $saleId = (int) $request->param('id', 0);
        $documentId = (int) $request->param('documentId', 0);
        $this->renderDocumentById($saleId, $documentId, true);
    }

    private function renderMainDocumentBySale(int $saleId, bool $asPdf): void
    {
        $companyId = (int) Tenant::companyId();
        $userId = (int) (Auth::id() ?? 0);

        if ($companyId <= 0 || $userId <= 0 || $saleId <= 0) {
            flash('error', 'No fue posible cargar el documento.');
            redirect('ventas');
        }

        $this->assertSalePrintable($companyId, $saleId);

        $invoiceModel = new Invoice();
        $document = $invoiceModel->findMainBySaleId($companyId, $saleId);
        if ($document === null) {
            flash('error', 'La venta seleccionada no tiene documento principal emitido.');
            redirect('ventas');
        }

        $this->outputDocument($document, $invoiceModel->details((int) $document['id']), $asPdf);
    }

    private function renderDocumentById(int $saleId, int $documentId, bool $asPdf): void
    {
        $companyId = (int) Tenant::companyId();
        $userId = (int) (Auth::id() ?? 0);

        if ($companyId <= 0 || $userId <= 0 || $saleId <= 0 || $documentId <= 0) {
            flash('error', 'No fue posible cargar el documento.');
            redirect('ventas');
        }

        $this->assertSalePrintable($companyId, $saleId);

        $invoiceModel = new Invoice();
        $document = $invoiceModel->findByIdWithRelations($companyId, $documentId);
        if ($document === null || (int) ($document['venta_id'] ?? 0) !== $saleId) {
            flash('error', 'El documento solicitado no pertenece a la venta seleccionada.');
            redirect('ventas/' . $saleId);
        }

        $this->outputDocument($document, $invoiceModel->details($documentId), $asPdf);
    }

    private function outputDocument(array $document, array $details, bool $asPdf): void
    {
        $titlePrefix = $this->documentLabel((string) ($document['tipo_documento'] ?? 'factura'));
        $title = $titlePrefix . ' ' . (string) ($document['numero'] ?? '');

        if ($asPdf) {
            $filenameSafe = preg_replace('/[^A-Za-z0-9_\-]/', '_', (string) ($document['numero'] ?? ('documento_' . $document['id'])));
            $filenameSafe = is_string($filenameSafe) && $filenameSafe !== '' ? $filenameSafe : ('documento_' . (string) ($document['id'] ?? '0'));
            $filename = $filenameSafe . '.pdf';
            $pdfService = new PdfService();
            $downloaded = $pdfService->downloadIfAvailable('facturacion/print', [
                'title' => $title,
                'document' => $document,
                'invoice' => $document,
                'details' => $details,
                'pdf_fallback' => false,
            ], $filename);

            if ($downloaded) {
                return;
            }

            $this->render('facturacion/print', [
                'title' => $title . ' (guardar como PDF)',
                'document' => $document,
                'invoice' => $document,
                'details' => $details,
                'pdf_fallback' => true,
            ], 'layouts/print');
            return;
        }

        $this->render('facturacion/print', [
            'title' => $title,
            'document' => $document,
            'invoice' => $document,
            'details' => $details,
            'pdf_fallback' => false,
        ], 'layouts/print');
    }

    private function documentLabel(string $type): string
    {
        return match ($type) {
            'nota_credito' => 'Nota crédito',
            'nota_debito' => 'Nota débito',
            default => 'Factura',
        };
    }

    private function assertSalePrintable(int $companyId, int $saleId): void
    {
        $sale = (new Sale())->findWithRelations($companyId, $saleId);
        if ($sale === null) {
            flash('error', 'La venta no existe en la empresa activa.');
            redirect('ventas');
        }

        if ($this->isPendingCredibancoSale($sale)) {
            flash('error', 'La venta está pendiente de aprobación por Credibanco. Aún no se puede imprimir ticket o factura.');
            redirect('ventas/' . $saleId);
        }
    }

    private function isPendingCredibancoSale(array $sale): bool
    {
        $saleStatus = strtolower(trim((string) ($sale['estado'] ?? 'pendiente')));
        if ($saleStatus === 'pagada') {
            return false;
        }

        $methodsRaw = strtolower(trim((string) ($sale['metodos_pago'] ?? '')));
        if ($methodsRaw === '') {
            return false;
        }

        $methods = array_values(array_filter(array_map(
            static fn (string $method): string => trim($method),
            explode(',', $methodsRaw)
        ), static fn (string $method): bool => $method !== ''));

        foreach ($methods as $methodCode) {
            if (in_array($methodCode, ['credibanco', 'credibanco_dataphone', 'credibanco_datafono'], true)) {
                return true;
            }
        }

        return false;
    }
}
