<?php

namespace App\Http\Controllers;

use App\Http\Requests\ActualizarCarritoRequest;
use App\Http\Requests\AgregarCarritoRequest;
use App\Http\Requests\FinalizarFacturaRequest;
use App\Models\CarritoItem;
use App\Models\Cliente;
use App\Models\Cupon;
use App\Models\DetalleFactura;
use App\Models\Factura;
use App\Models\Inventario;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class FacturacionController extends Controller
{
    public function index(Request $request): View
    {
        $carrito = CarritoItem::with('inventario.tipoImpuesto')
            ->where('user_id', auth()->id())
            ->get();

        $items = Inventario::with(['tipoImpuesto', 'categoria'])->where('active', true)->where('stock_actual', '>', 0)->orderBy('nombre')->get();

        $cupon = null;
        $cuponError = null;

        if ($request->filled('cupon')) {
            [$cupon, $cuponError] = $this->buscarCupon($request->query('cupon'));
        }

        $totales = $this->calcularTotales($carrito, $cupon);

        return view('facturacion.index', compact('carrito', 'items', 'totales', 'cupon', 'cuponError'));
    }

    public function addItem(AgregarCarritoRequest $request): RedirectResponse
    {
        $data = $request->validated();
        $inventario = Inventario::findOrFail($data['inventario_id']);

        $existente = CarritoItem::where('user_id', auth()->id())->where('inventario_id', $inventario->id)->first();
        $cantidadTotal = ($existente->cantidad ?? 0) + $data['cantidad'];

        if ($cantidadTotal > $inventario->stock_actual) {
            return back()->withErrors(['cantidad' => "Stock insuficiente. Disponible: {$inventario->stock_actual}."]);
        }

        CarritoItem::updateOrCreate(
            ['user_id' => auth()->id(), 'inventario_id' => $inventario->id],
            ['cantidad' => $cantidadTotal]
        );

        return redirect()->route('facturacion.index')->with('status', 'Producto agregado al carrito.');
    }

    public function updateItem(ActualizarCarritoRequest $request, CarritoItem $item): RedirectResponse
    {
        abort_unless($item->user_id === auth()->id(), 403);

        $cantidad = $request->validated()['cantidad'];

        if ($cantidad > $item->inventario->stock_actual) {
            return back()->withErrors(['cantidad' => "Stock insuficiente. Disponible: {$item->inventario->stock_actual}."]);
        }

        $item->update(['cantidad' => $cantidad]);

        return redirect()->route('facturacion.index')->with('status', 'Cantidad actualizada.');
    }

    public function removeItem(CarritoItem $item): RedirectResponse
    {
        abort_unless($item->user_id === auth()->id(), 403);

        $item->delete();

        return redirect()->route('facturacion.index')->with('status', 'Producto eliminado del carrito.');
    }

    public function store(FinalizarFacturaRequest $request): RedirectResponse
    {
        $data = $request->validated();

        $carrito = CarritoItem::with('inventario.tipoImpuesto')->where('user_id', auth()->id())->get();

        if ($carrito->isEmpty()) {
            return back()->withErrors(['carrito' => 'El carrito está vacío.']);
        }

        $cupon = null;

        if (! empty($data['codigo_cupon'])) {
            [$cupon, $cuponError] = $this->buscarCupon($data['codigo_cupon']);

            if ($cuponError) {
                return back()->withErrors(['codigo_cupon' => $cuponError])->withInput();
            }
        }

        $totales = $this->calcularTotales($carrito, $cupon);

        $efectivo = (float) ($data['efectivo'] ?? 0);
        $debito = (float) ($data['debito'] ?? 0);
        $credito = (float) ($data['credito'] ?? 0);
        $cheque = (float) ($data['cheque'] ?? 0);
        $pagado = $efectivo + $debito + $credito + $cheque;

        if ($pagado < $totales['total']) {
            return back()->withErrors(['pago' => sprintf('El monto pagado (%.2f) es menor al total de la factura (%.2f).', $pagado, $totales['total'])])->withInput();
        }

        $factura = DB::transaction(function () use ($carrito, $data, $totales, $cupon, $efectivo, $debito, $credito, $cheque, $pagado) {
            if ($cupon) {
                $cupon = Cupon::lockForUpdate()->findOrFail($cupon->id);

                if (! $cupon->esValido()) {
                    abort(422, 'El cupón dejó de ser válido: '.$cupon->motivoInvalidez());
                }

                $cupon->increment('usos_actuales');
            }

            $cliente = Cliente::updateOrCreate(
                ['cedula' => $data['cedula']],
                [
                    'nombre' => $data['nombre'],
                    'telefono' => $data['telefono'] ?? null,
                    'direccion' => $data['direccion'] ?? null,
                ]
            );

            $siguienteNumero = (int) Factura::max('numero') + 1;

            $factura = Factura::create([
                'numero' => $siguienteNumero,
                'cliente_id' => $cliente->id,
                'user_id' => auth()->id(),
                'cupon_id' => $cupon?->id,
                'descuento_porcentaje' => $totales['descuento_porcentaje'],
                'descuento_monto' => $totales['descuento_monto'],
                'subtotal' => $totales['subtotal'],
                'impuesto_total' => $totales['impuesto_total'],
                'impuestos_desglose' => $totales['desglose'],
                'total' => $totales['total'],
                'efectivo' => $efectivo,
                'debito' => $debito,
                'credito' => $credito,
                'cheque' => $cheque,
                'vuelto' => $pagado - $totales['total'],
                'observaciones' => $data['observaciones'] ?? null,
                'status' => $credito > 0 ? Factura::STATUS_NOTA_CREDITO : Factura::STATUS_PAGADA,
            ]);

            // Mismo factor de prorrateo que calcularTotales(): valido para descuento
            // porcentual o de monto fijo, ya que se deriva del monto ya calculado.
            $factor = $totales['subtotal_bruto'] > 0
                ? 1 - ($totales['descuento_monto'] / $totales['subtotal_bruto'])
                : 1;

            foreach ($carrito as $item) {
                $inventario = Inventario::lockForUpdate()->findOrFail($item->inventario_id);

                if ($item->cantidad > $inventario->stock_actual) {
                    abort(422, "Stock insuficiente para {$inventario->nombre} al momento de facturar.");
                }

                $subtotalBruto = round($inventario->precio * $item->cantidad, 2);
                $subtotalLinea = round($subtotalBruto * $factor, 2);
                $impuestoLinea = round($subtotalLinea * ($inventario->tipoImpuesto->porcentaje / 100), 2);

                DetalleFactura::create([
                    'factura_id' => $factura->id,
                    'inventario_id' => $inventario->id,
                    'descripcion' => $inventario->nombre,
                    'cantidad' => $item->cantidad,
                    'precio_unitario' => $inventario->precio,
                    'subtotal' => $subtotalLinea,
                    'tipo_impuesto_id' => $inventario->tipo_impuesto_id,
                    'impuesto_porcentaje' => $inventario->tipoImpuesto->porcentaje,
                    'impuesto_monto' => $impuestoLinea,
                ]);

                $inventario->decrement('stock_actual', $item->cantidad);
            }

            CarritoItem::where('user_id', auth()->id())->delete();

            return $factura;
        });

        return redirect()->route('facturas.show', $factura)->with('status', 'Factura generada correctamente.');
    }

    /**
     * @return array{0: ?Cupon, 1: ?string}
     */
    private function buscarCupon(string $codigo): array
    {
        $cupon = Cupon::where('codigo', strtoupper(trim($codigo)))->first();

        if (! $cupon) {
            return [null, 'El cupón no existe.'];
        }

        if (! $cupon->esValido()) {
            return [null, $cupon->motivoInvalidez()];
        }

        return [$cupon, null];
    }

    /**
     * Agrupa el carrito por tipo de impuesto, aplica el cupón (si hay) al subtotal
     * antes de calcular el impuesto de cada grupo (el descuento se calcula ANTES
     * del impuesto: el impuesto se aplica sobre el subtotal ya descontado), y
     * devuelve todos los totales. El cupón puede ser de porcentaje o monto fijo.
     */
    private function calcularTotales($carrito, ?Cupon $cupon = null): array
    {
        $porTipo = [];
        $subtotalBruto = 0;

        foreach ($carrito as $item) {
            $tipo = $item->inventario->tipoImpuesto;
            $importe = round($item->cantidad * $item->inventario->precio, 2);
            $subtotalBruto += $importe;

            if (! isset($porTipo[$tipo->id])) {
                $porTipo[$tipo->id] = [
                    'tipo_impuesto_id' => $tipo->id,
                    'nombre' => $tipo->nombre,
                    'porcentaje' => (float) $tipo->porcentaje,
                    'base' => 0,
                ];
            }

            $porTipo[$tipo->id]['base'] += $importe;
        }

        $subtotalBruto = round($subtotalBruto, 2);
        $descuentoMonto = $cupon ? $cupon->calcularDescuento($subtotalBruto) : 0;
        $descuentoPorcentaje = ($cupon && $cupon->esPorcentaje()) ? (float) $cupon->porcentaje : 0;
        $subtotal = round($subtotalBruto - $descuentoMonto, 2);
        $factor = $subtotalBruto > 0 ? (1 - ($descuentoMonto / $subtotalBruto)) : 1;

        $impuestoTotal = 0;
        foreach ($porTipo as &$grupo) {
            $grupo['base'] = round($grupo['base'] * $factor, 2);
            $grupo['monto'] = round($grupo['base'] * ($grupo['porcentaje'] / 100), 2);
            $impuestoTotal += $grupo['monto'];
        }
        unset($grupo);

        $impuestoTotal = round($impuestoTotal, 2);

        return [
            'subtotal_bruto' => $subtotalBruto,
            'descuento_porcentaje' => $descuentoPorcentaje,
            'descuento_monto' => $descuentoMonto,
            'subtotal' => $subtotal,
            'impuesto_total' => $impuestoTotal,
            'total' => round($subtotal + $impuestoTotal, 2),
            'desglose' => array_values($porTipo),
        ];
    }
}
