<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Controller;
use App\Core\Request;
use App\Core\Tenant;
use App\Models\Product;
use App\Models\Purchase;
use App\Models\PurchasePayment;
use App\Models\PurchaseOrder;
use App\Models\Supplier;
use App\Models\ProductVariant;
use App\Services\AuditService;
use App\Services\PdfService;
use App\Services\PurchaseOrderNotificationService;
use App\Services\PurchaseOrderService;
use App\Services\PurchaseService;

class PurchaseController extends Controller
{
    public function index(Request $request): void
    {
        $companyId = (int) Tenant::companyId();
        if ($companyId <= 0) {
            flash('error', 'Selecciona una empresa antes de registrar compras.');
            redirect('empresas/seleccionar');
        }

        $purchaseModel = new Purchase();
        $orderModel = new PurchaseOrder();
        $products = (new Product())->all(['activo' => 1], $companyId, 'nombre ASC');
        $orders = $orderModel->listWithProgress($companyId);
        $orderDetails = [];
        $orderReceptions = [];

        foreach ($orders as $order) {
            $orderId = (int) ($order['id'] ?? 0);
            if ($orderId <= 0) {
                continue;
            }

            $orderDetails[$orderId] = $orderModel->detailsByOrder($companyId, $orderId);
            $orderReceptions[$orderId] = $orderModel->receptionHistoryByOrder($companyId, $orderId);
        }

        $productVariantsMap = [];
        $allVariantsMap = (new ProductVariant())->listGroupedByProduct($companyId);
        foreach ($allVariantsMap as $productId => $variants) {
            $productVariantsMap[(int) $productId] = array_values(array_filter(
                is_array($variants) ? $variants : [],
                static fn (array $variant): bool => (int) ($variant['activo'] ?? 0) === 1
            ));
        }

        $this->render('compras/index', [
            'title' => 'Compras',
            'summary' => $purchaseModel->payablesSummary($companyId),
            'purchases' => $purchaseModel->listWithRelations($companyId),
            'payments' => (new PurchasePayment())->recentWithPurchase($companyId),
            'suppliers' => (new Supplier())->all(['activo' => 1], $companyId, 'nombre ASC'),
            'products' => $products,
            'product_variants_map' => $productVariantsMap,
            'orders' => $orders,
            'order_details' => $orderDetails,
            'order_receptions' => $orderReceptions,
        ]);
    }

    public function store(Request $request): never
    {
        $productIds = (array) $request->input('producto_id', []);
        $variantIds = (array) $request->input('variante_id', []);
        $quantities = (array) $request->input('cantidad', []);
        $costs = (array) $request->input('costo_unitario', []);
        $taxes = (array) $request->input('impuesto_linea', []);

        $items = [];
        foreach ($productIds as $index => $productId) {
            $id = (int) $productId;
            $variantId = (int) ($variantIds[$index] ?? 0);
            $qtyValue = (float) ($quantities[$index] ?? 0);
            $cost = (float) ($costs[$index] ?? 0);
            $lineTax = (float) ($taxes[$index] ?? 0);

            if ($id > 0 && $qtyValue > 0 && !$this->isWholeQuantity($qtyValue)) {
                flash('error', 'La cantidad en compras debe ser un número entero.');
                redirect('compras');
            }

            $qty = (int) round($qtyValue);

            if ($id <= 0 || $qty <= 0 || $cost <= 0) {
                continue;
            }

            $items[] = [
                'producto_id' => $id,
                'variante_id' => $variantId > 0 ? $variantId : null,
                'cantidad' => $qty,
                'costo_unitario' => $cost,
                'impuesto' => $lineTax,
            ];
        }

        try {
            $purchaseId = (new PurchaseService())->registerPurchase([
                'proveedor_id' => (int) $request->input('proveedor_id', 0),
                'impuestos' => (float) $request->input('impuestos', 0),
                'items' => $items,
            ]);

            AuditService::track('compras.crear', 'Compra registrada ID: ' . $purchaseId);
            flash('success', 'Compra registrada correctamente con entrada a inventario. ID #' . $purchaseId);
            redirect('compras');
        } catch (\Throwable $e) {
            flash('error', 'No fue posible registrar la compra: ' . $e->getMessage());
            redirect('compras');
        }
    }

    public function pay(Request $request): never
    {
        $companyId = (int) Tenant::companyId();
        if ($companyId <= 0) {
            flash('error', 'Selecciona una empresa antes de registrar pagos.');
            redirect('empresas/seleccionar');
        }

        $purchaseId = (int) $request->param('id', 0);

        try {
            $paymentId = (new PurchaseService())->registerPayment([
                'compra_id' => $purchaseId,
                'monto' => (float) $request->input('monto', 0),
                'metodo_pago' => (string) $request->input('metodo_pago', 'transferencia'),
                'referencia' => (string) $request->input('referencia', ''),
            ]);

            AuditService::track('compras.pago', 'Pago registrado ID: ' . $paymentId . ' para compra ID: ' . $purchaseId);
            flash('success', 'Pago registrado correctamente.');
            redirect('compras');
        } catch (\Throwable $e) {
            flash('error', 'No fue posible registrar el pago: ' . $e->getMessage());
            redirect('compras');
        }
    }

    public function annul(Request $request): never
    {
        $companyId = (int) Tenant::companyId();
        if ($companyId <= 0) {
            flash('error', 'Selecciona una empresa antes de anular compras.');
            redirect('empresas/seleccionar');
        }

        $purchaseId = (int) $request->param('id', 0);

        try {
            (new PurchaseService())->cancelPurchase($purchaseId);
            AuditService::track('compras.anular', 'Compra anulada ID: ' . $purchaseId);
            flash('success', 'Compra anulada y stock reversado correctamente.');
            redirect('compras');
        } catch (\Throwable $e) {
            flash('error', 'No fue posible anular la compra: ' . $e->getMessage());
            redirect('compras');
        }
    }

    public function storeOrder(Request $request): never
    {
        $productIds = (array) $request->input('oc_producto_id', []);
        $variantIds = (array) $request->input('oc_variante_id', []);
        $quantities = (array) $request->input('oc_cantidad', []);
        $costs = (array) $request->input('oc_costo_unitario', []);
        $taxes = (array) $request->input('oc_impuesto_linea', []);

        $items = [];
        foreach ($productIds as $index => $productId) {
            $id = (int) $productId;
            $variantId = (int) ($variantIds[$index] ?? 0);
            $qtyValue = (float) ($quantities[$index] ?? 0);
            $cost = (float) ($costs[$index] ?? 0);
            $lineTax = (float) ($taxes[$index] ?? 0);

            if ($id > 0 && $qtyValue > 0 && !$this->isWholeQuantity($qtyValue)) {
                flash('error', 'La cantidad en orden de compra debe ser un número entero.');
                redirect('compras');
            }

            $qty = (int) round($qtyValue);

            if ($id <= 0 || $qty <= 0 || $cost <= 0) {
                continue;
            }

            $items[] = [
                'producto_id' => $id,
                'variante_id' => $variantId > 0 ? $variantId : null,
                'cantidad' => $qty,
                'costo_unitario' => $cost,
                'impuesto' => $lineTax,
            ];
        }

        try {
            $orderId = (new PurchaseOrderService())->createOrder([
                'proveedor_id' => (int) $request->input('oc_proveedor_id', 0),
                'observacion' => (string) $request->input('oc_observacion', ''),
                'items' => $items,
            ]);

            $mailResult = (new PurchaseOrderNotificationService())->sendToSupplier((int) Tenant::companyId(), $orderId);

            AuditService::track('compras.orden.crear', 'Orden de compra creada ID: ' . $orderId);
            if ((bool) ($mailResult['ok'] ?? false)) {
                flash('success', 'Orden de compra creada correctamente. OC #' . $orderId . '. Correo enviado al proveedor.');
            } else {
                $errorMessage = trim((string) ($mailResult['error'] ?? 'No fue posible enviar el correo.'));
                flash('success', 'Orden de compra creada correctamente. OC #' . $orderId . '. Aviso: ' . $errorMessage);
            }
            redirect('compras');
        } catch (\Throwable $e) {
            flash('error', 'No fue posible crear la orden de compra: ' . $e->getMessage());
            redirect('compras');
        }
    }

    public function receiveOrder(Request $request): never
    {
        $orderId = (int) $request->param('id', 0);
        $rawQuantities = (array) $request->input('recibir_cantidad', []);
        $quantities = [];

        foreach ($rawQuantities as $detailId => $qty) {
            $detailIdInt = (int) $detailId;
            if ($detailIdInt <= 0) {
                continue;
            }

            $qtyValue = (float) $qty;
            if ($qtyValue > 0 && !$this->isWholeQuantity($qtyValue)) {
                flash('error', 'La cantidad de recepción debe ser un número entero.');
                redirect('compras');
            }

            $quantities[$detailIdInt] = (int) round($qtyValue);
        }

        try {
            $result = (new PurchaseOrderService())->receiveOrder(
                $orderId,
                $quantities,
                (string) $request->input('recepcion_observacion', '')
            );

            $receptionId = (int) ($result['reception_id'] ?? 0);
            $purchaseId = (int) ($result['purchase_id'] ?? 0);
            $status = (string) ($result['order_status'] ?? 'parcial');

            AuditService::track(
                'compras.orden.recibir',
                'Recepción registrada. Orden #' . $orderId . ', recepción #' . $receptionId . ', compra #' . $purchaseId
            );

            flash(
                'success',
                'Recepción registrada. Orden #' . $orderId
                . ' en estado ' . $status
                . '. Compra creada #' . $purchaseId
                . '.'
            );
            redirect('compras');
        } catch (\Throwable $e) {
            flash('error', 'No fue posible registrar la recepción: ' . $e->getMessage());
            redirect('compras');
        }
    }

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

        if ($companyId <= 0 || $orderId <= 0) {
            flash('error', 'No fue posible cargar la orden de compra.');
            redirect('compras');
        }

        $orderModel = new PurchaseOrder();
        $orders = $orderModel->listWithProgress($companyId, 200);
        $order = null;
        foreach ($orders as $candidate) {
            if ((int) ($candidate['id'] ?? 0) === $orderId) {
                $order = $candidate;
                break;
            }
        }

        if (!is_array($order)) {
            flash('error', 'La orden de compra no existe para la empresa activa.');
            redirect('compras');
        }

        $details = $orderModel->detailsByOrder($companyId, $orderId);

        $filenameSafe = preg_replace('/[^A-Za-z0-9_\\-]/', '_', (string) ($order['numero'] ?? ('orden_' . $orderId)));
        $filenameSafe = is_string($filenameSafe) && $filenameSafe !== '' ? $filenameSafe : ('orden_' . $orderId);
        $filename = $filenameSafe . '.pdf';

        $pdfService = new PdfService();
        $downloaded = $pdfService->downloadIfAvailable('compras/order_print', [
            'title' => 'Orden de compra ' . (string) ($order['numero'] ?? ''),
            'order' => $order,
            'details' => $details,
            'pdf_fallback' => false,
        ], $filename);

        if ($downloaded) {
            return;
        }

        $this->render('compras/order_print', [
            'title' => 'Orden de compra ' . (string) ($order['numero'] ?? ''),
            'order' => $order,
            'details' => $details,
            'pdf_fallback' => true,
        ], 'layouts/print');
    }

    private function isWholeQuantity(float $quantity): bool
    {
        return abs($quantity - round($quantity)) < 0.0001;
    }
}
