<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\Session;
use App\Core\Tenant;
use App\Models\CashOpening;
use App\Models\CompanySetting;
use App\Models\Customer;
use App\Models\DianResolution;
use App\Models\DocumentNumbering;
use App\Models\PaymentMethod;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Sale;
use App\Models\User;
use App\Services\AuditService;
use App\Services\PosService;

class PosController extends Controller
{
    public function index(Request $request): void
    {
        $companyId = (int) Tenant::companyId();
        $userId = (int) Auth::id();
        $userBranchId = (new User())->assignedBranchId($companyId, $userId);
        $opening = (new CashOpening())->openedByUser($companyId, $userId);
        $products = ($userBranchId !== null && $userBranchId > 0)
            ? (new Product())->listForPos($companyId, (int) $userBranchId)
            : [];
        $productVariantsMap = [];
        if ($products !== []) {
            $allVariantsMap = (new ProductVariant())->listGroupedByProduct($companyId);
            $availableProductIds = array_map(
                static fn (array $row): int => (int) ($row['id'] ?? 0),
                $products
            );
            $availableProductIds = array_values(array_filter($availableProductIds, static fn (int $id): bool => $id > 0));
            $availableLookup = array_fill_keys($availableProductIds, true);

            foreach ($allVariantsMap as $productId => $variants) {
                $id = (int) $productId;
                if (!isset($availableLookup[$id])) {
                    continue;
                }

                $productVariantsMap[$id] = array_values(array_filter(
                    is_array($variants) ? $variants : [],
                    static fn (array $variant): bool => (int) ($variant['activo'] ?? 0) === 1
                ));
            }

            foreach ($products as &$product) {
                $productId = (int) ($product['id'] ?? 0);
                if ($productId <= 0) {
                    continue;
                }

                $variants = is_array($productVariantsMap[$productId] ?? null) ? $productVariantsMap[$productId] : [];
                if ($variants === []) {
                    continue;
                }

                $variantStock = 0;
                $hasVariantQty = false;
                foreach ($variants as $variant) {
                    if (!array_key_exists('cantidad', $variant) || $variant['cantidad'] === null || $variant['cantidad'] === '') {
                        continue;
                    }
                    $hasVariantQty = true;
                    $variantStock += max(0, (int) $variant['cantidad']);
                }

                if ($hasVariantQty) {
                    $product['stock_actual'] = (float) $variantStock;
                }
            }
            unset($product);
        }
        $invoiceRangeAlert = null;
        $posEnabled = true;
        $posBlockReason = '';

        if ($userBranchId === null || $userBranchId <= 0) {
            $posEnabled = false;
            $posBlockReason = 'Tu usuario no tiene sucursal asignada. Solicita asignación para vender en POS.';
        } elseif (!is_array($opening)) {
            $posEnabled = false;
            $posBlockReason = 'Debes abrir una caja de tu sucursal antes de registrar ventas.';
        } elseif ((int) ($opening['caja_id'] ?? 0) <= 0) {
            $posEnabled = false;
            $posBlockReason = 'Debes abrir una caja asignada a tu sucursal para usar POS.';
        } elseif ((int) ($opening['caja_sucursal_id'] ?? 0) !== (int) $userBranchId) {
            $posEnabled = false;
            $posBlockReason = 'La caja abierta no corresponde a tu sucursal asignada.';
        } elseif ($products === []) {
            $posEnabled = false;
            $posBlockReason = 'No hay productos con stock disponible en tu sucursal.';
        }

        if ($userBranchId !== null && $userBranchId > 0) {
            $invoiceRangeAlert = $this->buildInvoiceRangeAlert($companyId, (int) $userBranchId);
        }

        $this->render('pos/index', [
            'title' => 'Punto de venta',
            'products' => $products,
            'product_variants_map' => $productVariantsMap,
            'customers' => (new Customer())->all(['activo' => 1], $companyId),
            'payment_methods' => $this->activePaymentMethods($companyId),
            'opening' => $opening,
            'user_branch_id' => $userBranchId,
            'pos_enabled' => $posEnabled,
            'pos_block_reason' => $posBlockReason,
            'invoice_range_alert' => $invoiceRangeAlert,
        ]);
    }

    public function sell(Request $request): never
    {
        $companyId = (int) Tenant::companyId();
        $userId = (int) Auth::id();
        $userBranchId = (new User())->assignedBranchId($companyId, $userId);
        $opening = (new CashOpening())->openedByUser($companyId, $userId);

        if ($userBranchId === null || $userBranchId <= 0) {
            flash('error', 'Tu usuario no tiene sucursal asignada para vender en POS.');
            redirect('pos');
        }

        if ($opening === null) {
            flash('error', 'Debes abrir caja antes de vender.');
            redirect('pos');
        }

        if ((int) ($opening['caja_id'] ?? 0) <= 0) {
            flash('error', 'Debes abrir una caja asignada para registrar ventas POS.');
            redirect('pos');
        }

        if ((int) ($opening['caja_sucursal_id'] ?? 0) !== (int) $userBranchId) {
            flash('error', 'La caja abierta no pertenece a tu sucursal asignada.');
            redirect('pos');
        }

        $productIds = $request->input('producto_id', []);
        $quantities = $request->input('cantidad', []);
        $prices = $request->input('precio_unitario', []);
        $lineDiscountValues = $request->input('descuento_linea', []);
        $lineDiscountTypes = $request->input('descuento_linea_tipo', []);
        $variantIds = $request->input('variante_id', []);
        $variantNames = $request->input('variante_nombre', []);

        $discountMode = strtolower(trim((string) $request->input('descuento_modo', 'valor')));
        if (!in_array($discountMode, ['valor', 'porcentaje'], true)) {
            $discountMode = 'valor';
        }

        $items = [];
        foreach ((array) $productIds as $index => $productId) {
            $id = (int) $productId;
            $qty = (float) (((array) $quantities)[$index] ?? 0);
            $price = (float) (((array) $prices)[$index] ?? 0);
            $lineDiscount = (float) (((array) $lineDiscountValues)[$index] ?? 0);
            $lineDiscountType = strtolower(trim((string) (((array) $lineDiscountTypes)[$index] ?? 'valor')));
            if (!in_array($lineDiscountType, ['valor', 'porcentaje'], true)) {
                $lineDiscountType = 'valor';
            }
            $variantId = (int) (((array) $variantIds)[$index] ?? 0);
            $variantName = trim((string) (((array) $variantNames)[$index] ?? ''));

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

            $items[] = [
                'producto_id' => $id,
                'variante_id' => $variantId > 0 ? $variantId : null,
                'variante_nombre' => $variantName !== '' ? $variantName : null,
                'cantidad' => $qty,
                'precio_unitario' => $price,
                'descuento' => $lineDiscount,
                'descuento_tipo' => $lineDiscountType,
            ];
        }

        $paymentMethodCode = strtolower(trim((string) $request->input('metodo_pago', 'efectivo')));
        if ($paymentMethodCode === '') {
            $paymentMethodCode = 'efectivo';
        }

        try {
            $saleId = (new PosService())->registerSale([
                'cliente_id' => $request->input('cliente_id', ''),
                'sucursal_id' => (int) $userBranchId,
                'caja_id' => (int) ($opening['caja_id'] ?? 0),
                'apertura_caja_id' => $opening['id'],
                'tipo_venta' => $request->input('tipo_venta', 'contado'),
                'abono_inicial' => (float) $request->input('abono_inicial', 0),
                'descuento' => (float) $request->input('descuento', 0),
                'descuento_modo' => $discountMode,
                'metodo_pago' => $paymentMethodCode,
                'referencia_pago' => $request->input('referencia_pago'),
                'items' => $items,
            ]);

            AuditService::track('pos.venta', 'Venta registrada ID: ' . $saleId);
            if ($this->isCredibancoDataphonePaymentMethod($paymentMethodCode)) {
                flash('success', 'Venta creada en estado pendiente. Se aprobará cuando Credibanco confirme el pago por webhook. Venta #' . $saleId);
                Session::flash('success_actions', [
                    [
                        'label' => 'Nueva venta',
                        'url' => url('pos'),
                    ],
                    [
                        'label' => 'Ver historial',
                        'url' => url('ventas'),
                    ],
                ]);
            } else {
                $successText = 'Venta registrada correctamente. Ticket #' . $saleId;

                // Calcular cambio si el pago fue en efectivo
                if ($paymentMethodCode === 'efectivo') {
                    $pagoCliente = (float) $request->input('pago_cliente', 0);
                    if ($pagoCliente > 0) {
                        $saleRecord = (new Sale())->findById($saleId, $companyId);
                        $saleTotal = (float) ($saleRecord['total'] ?? 0);
                        if ($saleTotal > 0 && $pagoCliente >= $saleTotal) {
                            $cambio = $pagoCliente - $saleTotal;
                            Session::flash('pos_cambio', '$' . number_format($cambio, 0, ',', '.'));
                        }
                    }
                }

                flash('success', $successText);
                Session::flash('success_action', [
                    'label' => 'Imprimir factura',
                    'url' => url('ventas/' . $saleId . '/factura/imprimir'),
                    'target' => '_blank',
                ]);
                Session::flash('success_actions', [
                    [
                        'label' => 'Nueva venta',
                        'url' => url('pos'),
                    ],
                    [
                        'label' => 'Imprimir ticket',
                        'url' => url('ventas/' . $saleId . '/ticket/imprimir'),
                        'target' => '_blank',
                    ],
                    [
                        'label' => 'Descargar PDF',
                        'url' => url('ventas/' . $saleId . '/factura/pdf'),
                        'target' => '_blank',
                    ],
                    [
                        'label' => 'Ver historial',
                        'url' => url('ventas'),
                    ],
                ]);
            }
            redirect('pos');
        } catch (\Throwable $e) {
            flash('error', 'No se pudo registrar la venta: ' . $e->getMessage());
            redirect('pos');
        }
    }

    public function storeCustomer(Request $request): never
    {
        $companyId = (int) Tenant::companyId();
        if ($companyId <= 0) {
            Response::json([
                'ok' => false,
                'message' => 'Empresa no válida para registrar cliente.',
            ], 422);
        }

        $name = trim((string) $request->input('nombre', ''));
        $document = trim((string) $request->input('documento', ''));
        $email = strtolower(trim((string) $request->input('email', '')));
        $phone = trim((string) $request->input('telefono', ''));
        $address = trim((string) $request->input('direccion', ''));

        if ($name === '') {
            Response::json([
                'ok' => false,
                'message' => 'El nombre del cliente es obligatorio.',
            ], 422);
        }

        if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            Response::json([
                'ok' => false,
                'message' => 'El correo del cliente no es válido.',
            ], 422);
        }

        try {
            $id = (new Customer())->create([
                'empresa_id' => $companyId,
                'nombre' => $name,
                'documento' => $document !== '' ? $document : null,
                'email' => $email !== '' ? $email : null,
                'telefono' => $phone !== '' ? $phone : null,
                'direccion' => $address !== '' ? $address : null,
                'saldo_pendiente' => 0,
                'activo' => 1,
                'created_at' => date('Y-m-d H:i:s'),
            ]);

            AuditService::track('clientes.crear.pos', 'Cliente creado desde POS ID: ' . $id);

            Response::json([
                'ok' => true,
                'message' => 'Cliente creado correctamente.',
                'customer' => [
                    'id' => $id,
                    'nombre' => $name,
                    'documento' => $document,
                    'email' => $email,
                    'telefono' => $phone,
                ],
            ], 201);
        } catch (\Throwable $e) {
            Response::json([
                'ok' => false,
                'message' => 'No fue posible crear el cliente: ' . $e->getMessage(),
            ], 500);
        }
    }

    private function buildInvoiceRangeAlert(int $companyId, int $branchId): ?array
    {
        $numbering = (new DocumentNumbering())->activeByTypeForBranch($companyId, 'factura', $branchId);
        if ($numbering === null) {
            return null;
        }

        $current = (int) ($numbering['consecutivo_actual'] ?? 0);
        if ($current <= 0) {
            return [
                'level' => 'danger',
                'message' => 'La numeración activa de factura tiene consecutivo inválido.',
            ];
        }

        $prefix = trim((string) ($numbering['prefijo'] ?? ''));
        $resolution = (new DianResolution())->activeByPrefixForBranch($companyId, $branchId, $prefix);
        if ($resolution === null) {
            return null;
        }

        $rangeFrom = (int) ($resolution['rango_desde'] ?? 0);
        $rangeTo = (int) ($resolution['rango_hasta'] ?? 0);
        if ($rangeFrom <= 0 || $rangeTo <= 0 || $rangeFrom > $rangeTo) {
            return [
                'level' => 'danger',
                'message' => 'La resolución DIAN activa tiene un rango inválido.',
            ];
        }

        if ($current < $rangeFrom || $current > $rangeTo) {
            return [
                'level' => 'danger',
                'message' => 'El consecutivo actual está fuera del rango autorizado por la resolución DIAN.',
            ];
        }

        $numberingFinal = $numbering['consecutivo_final'] !== null ? (int) $numbering['consecutivo_final'] : null;
        $effectiveEnd = $numberingFinal !== null ? min($rangeTo, $numberingFinal) : $rangeTo;

        if ($current > $effectiveEnd) {
            return [
                'level' => 'danger',
                'message' => 'La numeración de factura ya superó el límite permitido.',
            ];
        }

        $remaining = $effectiveEnd - $current + 1;
        if ($remaining <= 5) {
            return [
                'level' => 'danger',
                'message' => 'Quedan ' . $remaining . ' consecutivos de factura antes de agotar el rango DIAN.',
            ];
        }

        if ($remaining <= 20) {
            return [
                'level' => 'warning',
                'message' => 'Quedan ' . $remaining . ' consecutivos de factura. Configura una nueva resolución pronto.',
            ];
        }

        return null;
    }

    private function isCredibancoDataphonePaymentMethod(string $paymentMethodCode): bool
    {
        $code = strtolower(trim($paymentMethodCode));
        return in_array($code, ['credibanco', 'credibanco_dataphone', 'credibanco_datafono'], true);
    }

    private function activePaymentMethods(int $companyId): array
    {
        $this->syncBoldPaymentMethodsForCompany($companyId);

        $methods = [];

        foreach ((new PaymentMethod())->effectiveForCompany($companyId) as $method) {
            if ((int) ($method['activo'] ?? 0) !== 1) {
                continue;
            }

            $code = strtolower(trim((string) ($method['codigo'] ?? '')));
            $name = trim((string) ($method['nombre'] ?? ''));
            if ($code === '' || $name === '') {
                continue;
            }

            $methods[] = [
                'codigo' => $code,
                'nombre' => $name,
            ];
        }

        if ($methods !== []) {
            return $methods;
        }

        return [
            ['codigo' => 'efectivo', 'nombre' => 'Efectivo'],
            ['codigo' => 'tarjeta', 'nombre' => 'Tarjeta'],
            ['codigo' => 'transferencia', 'nombre' => 'Transferencia'],
        ];
    }

    private function syncBoldPaymentMethodsForCompany(int $companyId): void
    {
        if ($companyId <= 0) {
            return;
        }

        $settings = (new CompanySetting())->getMany($companyId, [
            'bold_enabled',
            'bold_api_url',
            'bold_api_key',
            'bold_button_public_key',
            'bold_button_secret_key',
        ]);

        $enabled = trim((string) ($settings['bold_enabled'] ?? '0')) === '1';
        $apiUrl = trim((string) ($settings['bold_api_url'] ?? ''));
        $apiKey = trim((string) ($settings['bold_api_key'] ?? ''));
        $buttonPublicKey = trim((string) ($settings['bold_button_public_key'] ?? ''));
        $buttonSecretKey = trim((string) ($settings['bold_button_secret_key'] ?? ''));

        $dataphoneActive = $enabled
            && $apiUrl !== ''
            && $apiKey !== ''
            && (bool) filter_var($apiUrl, FILTER_VALIDATE_URL)
            && preg_match('/^https?:\/\//i', $apiUrl) === 1;

        $buttonActive = $enabled
            && $buttonPublicKey !== ''
            && $buttonSecretKey !== '';

        $paymentMethodModel = new PaymentMethod();
        $existingDataphone = $paymentMethodModel->findCompanyByCode($companyId, 'bold_dataphone');
        if ($existingDataphone !== null || $dataphoneActive) {
            $this->upsertPaymentMethodByCode($paymentMethodModel, $companyId, 'bold_dataphone', 'Bold Datáfono', $dataphoneActive);
        }

        $existingButton = $paymentMethodModel->findCompanyByCode($companyId, 'bold_button');
        if ($existingButton !== null || $buttonActive) {
            $this->upsertPaymentMethodByCode($paymentMethodModel, $companyId, 'bold_button', 'Bold Botón', $buttonActive);
        }

        // Mantener compatibilidad: desactivar método legacy para evitar duplicados.
        if ($paymentMethodModel->findCompanyByCode($companyId, 'bold') !== null) {
            $this->upsertPaymentMethodByCode($paymentMethodModel, $companyId, 'bold', 'Bold', false);
        }
    }

    private function upsertPaymentMethodByCode(
        PaymentMethod $paymentMethodModel,
        int $companyId,
        string $code,
        string $name,
        bool $active
    ): void {
        $existing = $paymentMethodModel->findCompanyByCode($companyId, $code);

        if ($existing === null) {
            $paymentMethodModel->create([
                'empresa_id' => $companyId,
                'nombre' => $name,
                'codigo' => $code,
                'activo' => $active ? 1 : 0,
                'created_at' => date('Y-m-d H:i:s'),
            ]);
            return;
        }

        $paymentMethodModel->updateById((int) ($existing['id'] ?? 0), [
            'nombre' => $name,
            'activo' => $active ? 1 : 0,
        ], $companyId);
    }
}
