<?php

namespace App\Http\Controllers;

use App\Models\Compra;
use App\Models\OrdenCompra;
use App\Models\OrdenCompraDetalle;
use App\Models\Producto;
use App\Models\Proveedor;
use App\Models\RecepcionCompra;
use App\Models\RecepcionCompraDetalle;
use App\Models\Sucursal;
use App\Services\InventoryFlowService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class OrdenCompraController extends Controller
{
    public function __construct(private readonly InventoryFlowService $inventoryFlowService)
    {
    }

    public function index(): View
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $tenantSucursalId = tenant_sucursal_id();

        $ordenes = OrdenCompra::query()
            ->where('empresa_id', $empresaId)
            ->when($tenantSucursalId, static function ($query, int $sucursalId): void {
                $query->where(static function ($nested) use ($sucursalId): void {
                    $nested->whereNull('sucursal_id')
                        ->orWhere('sucursal_id', $sucursalId);
                });
            })
            ->with(['proveedor', 'usuario', 'sucursal', 'detalles.producto'])
            ->orderByDesc('id')
            ->paginate(20);

        $proveedores = Proveedor::query()
            ->where('empresa_id', $empresaId)
            ->where('activo', 1)
            ->orderBy('nombre')
            ->get();

        $productos = Producto::query()
            ->where('empresa_id', $empresaId)
            ->where('activo', 1)
            ->when($tenantSucursalId, static function ($query, int $sucursalId): void {
                $query->where(static function ($nested) use ($sucursalId): void {
                    $nested->whereNull('sucursal_id')
                        ->orWhere('sucursal_id', $sucursalId);
                });
            })
            ->orderBy('nombre')
            ->get();

        $sucursales = Sucursal::query()
            ->where('empresa_id', $empresaId)
            ->where('activa', 1)
            ->orderBy('nombre')
            ->get();

        return view('modules.compras.ordenes.index', [
            'ordenes' => $ordenes,
            'proveedores' => $proveedores,
            'productos' => $productos,
            'sucursales' => $sucursales,
            'tenantSucursalId' => $tenantSucursalId,
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $empresaId = tenant_empresa_id();
        abort_if(! $empresaId, 403, 'No hay empresa activa.');

        $payload = $this->normalizeOrderStorePayload($request);

        $validated = Validator::make($payload, [
            'proveedor_id' => ['required', 'integer'],
            'sucursal_id' => ['nullable', 'integer'],
            'fecha' => ['required', 'date'],
            'observacion' => ['nullable', 'string'],
            'lineas' => ['required', 'array', 'min:1'],
            'lineas.*.producto_id' => ['required', 'integer'],
            'lineas.*.cantidad' => ['required', 'numeric', 'gt:0'],
            'lineas.*.costo_unitario' => ['required', 'numeric', 'min:0'],
            'lineas.*.impuesto' => ['nullable', 'numeric', 'min:0'],
        ])->validate();

        $proveedor = Proveedor::query()
            ->whereKey((int) $validated['proveedor_id'])
            ->where('empresa_id', $empresaId)
            ->first();

        if (! $proveedor) {
            throw ValidationException::withMessages([
                'proveedor_id' => 'El proveedor no pertenece a la empresa activa.',
            ]);
        }

        $tenantSucursalId = tenant_sucursal_id();
        $sucursalId = $validated['sucursal_id'] ? (int) $validated['sucursal_id'] : $tenantSucursalId;

        if (
            $sucursalId
            && ! Sucursal::query()
                ->whereKey($sucursalId)
                ->where('empresa_id', $empresaId)
                ->exists()
        ) {
            throw ValidationException::withMessages([
                'sucursal_id' => 'La sucursal no pertenece a la empresa activa.',
            ]);
        }

        $productos = Producto::query()
            ->where('empresa_id', $empresaId)
            ->whereIn('id', collect($validated['lineas'])->pluck('producto_id')->map(static fn ($id): int => (int) $id)->all())
            ->get()
            ->keyBy('id');

        $lineas = [];
        $subtotal = 0.0;
        $impuestos = 0.0;

        foreach ($validated['lineas'] as $index => $linea) {
            $productoId = (int) $linea['producto_id'];
            $producto = $productos->get($productoId);

            if (! $producto) {
                throw ValidationException::withMessages([
                    "lineas.$index.producto_id" => 'El producto no pertenece a la empresa activa.',
                ]);
            }

            if ($sucursalId && $producto->sucursal_id && (int) $producto->sucursal_id !== $sucursalId) {
                throw ValidationException::withMessages([
                    "lineas.$index.producto_id" => 'El producto no pertenece a la sucursal seleccionada.',
                ]);
            }

            $cantidad = (float) $linea['cantidad'];
            $costoUnitario = (float) $linea['costo_unitario'];
            $impuestoLinea = (float) ($linea['impuesto'] ?? 0);
            $subtotalLinea = $cantidad * $costoUnitario;
            $totalLinea = $subtotalLinea + $impuestoLinea;

            $subtotal += $subtotalLinea;
            $impuestos += $impuestoLinea;

            $lineas[] = [
                'empresa_id' => $empresaId,
                'producto_id' => $productoId,
                'cantidad' => $cantidad,
                'cantidad_recibida' => 0,
                'costo_unitario' => $costoUnitario,
                'impuesto' => $impuestoLinea,
                'total_linea' => $totalLinea,
            ];
        }

        $usuarioId = (int) (auth_user()?->id ?? 0);
        abort_if($usuarioId <= 0, 403, 'Usuario invalido para crear orden de compra.');

        DB::transaction(function () use (
            $empresaId,
            $sucursalId,
            $proveedor,
            $validated,
            $subtotal,
            $impuestos,
            $usuarioId,
            $lineas
        ): void {
            $orden = OrdenCompra::query()->create([
                'empresa_id' => $empresaId,
                'sucursal_id' => $sucursalId,
                'proveedor_id' => $proveedor->id,
                'usuario_id' => $usuarioId,
                'numero' => $this->generateOrderNumber($empresaId),
                'fecha' => $validated['fecha'],
                'subtotal' => $subtotal,
                'impuestos' => $impuestos,
                'total' => $subtotal + $impuestos,
                'estado' => 'abierta',
                'observacion' => $validated['observacion'] ?? null,
            ]);

            $rows = [];
            foreach ($lineas as $linea) {
                $rows[] = [
                    'orden_compra_id' => $orden->id,
                    'empresa_id' => $linea['empresa_id'],
                    'producto_id' => $linea['producto_id'],
                    'cantidad' => $linea['cantidad'],
                    'cantidad_recibida' => $linea['cantidad_recibida'],
                    'costo_unitario' => $linea['costo_unitario'],
                    'impuesto' => $linea['impuesto'],
                    'total_linea' => $linea['total_linea'],
                ];
            }

            OrdenCompraDetalle::query()->insert($rows);
        });

        $legacyPayload = $request->has('oc_producto_id') || $request->has('oc_proveedor_id');

        return $legacyPayload
            ? redirect()->route('compras.index')->with('status', 'Orden de compra creada correctamente.')
            : redirect()->route('compras.ordenes.index')->with('status', 'Orden de compra creada correctamente.');
    }

    public function showReceive(OrdenCompra $orden): View
    {
        $this->ensureOrderAccess($orden);

        $orden->load(['proveedor', 'sucursal', 'usuario', 'detalles.producto']);

        return view('modules.compras.ordenes.receive', [
            'orden' => $orden,
            'lineasPendientes' => $orden->detalles->mapWithKeys(function (OrdenCompraDetalle $detalle): array {
                $pendiente = (float) $detalle->cantidad - (float) $detalle->cantidad_recibida;

                return [(string) $detalle->id => max(0.0, $pendiente)];
            }),
        ]);
    }

    public function storeReceive(Request $request, OrdenCompra $orden): RedirectResponse
    {
        $this->ensureOrderAccess($orden);

        if (in_array($orden->estado, ['anulada', 'completa'], true)) {
            return redirect()->route('compras.ordenes.index')
                ->with('error', 'Esta orden no admite nuevas recepciones.');
        }

        $payload = $this->normalizeOrderReceivePayload($request);

        $validated = Validator::make($payload, [
            'numero_recepcion' => ['nullable', 'string', 'max:80'],
            'fecha_recepcion' => ['required', 'date'],
            'observacion' => ['nullable', 'string'],
            'lineas' => ['required', 'array'],
            'lineas.*' => ['nullable', 'numeric', 'min:0'],
        ])->validate();

        $orden->load(['detalles.producto']);

        $lineasCantidad = collect($validated['lineas'])
            ->mapWithKeys(static fn ($qty, $id): array => [(int) $id => (float) $qty]);

        $selected = [];
        foreach ($orden->detalles as $detalle) {
            $cantidadRecibir = (float) ($lineasCantidad->get((int) $detalle->id, 0.0) ?? 0.0);
            if ($cantidadRecibir <= 0) {
                continue;
            }

            $pendiente = (float) $detalle->cantidad - (float) $detalle->cantidad_recibida;
            if ($pendiente <= 0) {
                throw ValidationException::withMessages([
                    "lineas.{$detalle->id}" => 'La linea seleccionada ya fue recibida totalmente.',
                ]);
            }

            if ($cantidadRecibir > $pendiente) {
                throw ValidationException::withMessages([
                    "lineas.{$detalle->id}" => 'La cantidad supera el pendiente de recibir para la linea.',
                ]);
            }

            $selected[] = [
                'detalle' => $detalle,
                'cantidad' => $cantidadRecibir,
            ];
        }

        if ($selected === []) {
            throw ValidationException::withMessages([
                'lineas' => 'Debes ingresar al menos una linea con cantidad a recibir.',
            ]);
        }

        $empresaId = (int) $orden->empresa_id;
        $usuarioId = (int) (auth_user()?->id ?? 0);
        abort_if($usuarioId <= 0, 403, 'Usuario invalido para registrar recepcion.');

        DB::transaction(function () use ($empresaId, $usuarioId, $validated, $orden, $selected): void {
            $numeroRecepcion = trim((string) ($validated['numero_recepcion'] ?? ''));
            if ($numeroRecepcion === '') {
                $numeroRecepcion = $this->generateReceptionNumber($empresaId);
            } elseif (RecepcionCompra::query()->where('empresa_id', $empresaId)->where('numero_recepcion', $numeroRecepcion)->exists()) {
                throw ValidationException::withMessages([
                    'numero_recepcion' => 'Ya existe una recepcion con ese numero para la empresa.',
                ]);
            }

            $recepcion = RecepcionCompra::query()->create([
                'empresa_id' => $empresaId,
                'orden_compra_id' => $orden->id,
                'sucursal_id' => $orden->sucursal_id,
                'usuario_id' => $usuarioId,
                'numero_recepcion' => $numeroRecepcion,
                'fecha_recepcion' => $validated['fecha_recepcion'],
                'estado' => 'recibida',
                'observacion' => $validated['observacion'] ?? null,
                'created_at' => now(),
            ]);

            $subtotal = 0.0;
            $impuestos = 0.0;
            $compraDetalleRows = [];

            foreach ($selected as $entry) {
                /** @var OrdenCompraDetalle $detalle */
                $detalle = $entry['detalle'];
                $cantidadRecibir = (float) $entry['cantidad'];
                $impuestoUnitario = (float) $detalle->cantidad > 0
                    ? ((float) $detalle->impuesto / (float) $detalle->cantidad)
                    : 0.0;

                $subtotalLinea = $cantidadRecibir * (float) $detalle->costo_unitario;
                $impuestoLinea = $cantidadRecibir * $impuestoUnitario;
                $totalLinea = $subtotalLinea + $impuestoLinea;

                RecepcionCompraDetalle::query()->create([
                    'recepcion_id' => $recepcion->id,
                    'empresa_id' => $empresaId,
                    'orden_detalle_id' => $detalle->id,
                    'producto_id' => $detalle->producto_id,
                    'variante_id' => $detalle->variante_id,
                    'cantidad' => $cantidadRecibir,
                    'costo_unitario' => $detalle->costo_unitario,
                    'impuesto' => $impuestoLinea,
                    'total_linea' => $totalLinea,
                ]);

                $detalle->cantidad_recibida = (float) $detalle->cantidad_recibida + $cantidadRecibir;
                $detalle->save();

                $this->inventoryFlowService->applyMovement(
                    empresaId: $empresaId,
                    sucursalId: $orden->sucursal_id ? (int) $orden->sucursal_id : null,
                    producto: $detalle->producto,
                    tipo: 'entrada',
                    cantidad: $cantidadRecibir,
                    costoUnitario: (float) $detalle->costo_unitario,
                    referencia: $numeroRecepcion
                );

                $subtotal += $subtotalLinea;
                $impuestos += $impuestoLinea;

                $compraDetalleRows[] = [
                    'empresa_id' => $empresaId,
                    'producto_id' => $detalle->producto_id,
                    'variante_id' => $detalle->variante_id,
                    'cantidad' => $cantidadRecibir,
                    'costo_unitario' => $detalle->costo_unitario,
                    'impuesto' => $impuestoLinea,
                    'total_linea' => $totalLinea,
                ];
            }

            $compra = Compra::query()->create([
                'empresa_id' => $empresaId,
                'orden_compra_id' => $orden->id,
                'recepcion_compra_id' => $recepcion->id,
                'proveedor_id' => $orden->proveedor_id,
                'usuario_id' => $usuarioId,
                'fecha' => $validated['fecha_recepcion'],
                'subtotal' => $subtotal,
                'impuestos' => $impuestos,
                'total' => $subtotal + $impuestos,
                'estado' => 'recibida',
                'created_at' => now(),
            ]);

            $rows = [];
            foreach ($compraDetalleRows as $linea) {
                $rows[] = [
                    'compra_id' => $compra->id,
                    'empresa_id' => $linea['empresa_id'],
                    'producto_id' => $linea['producto_id'],
                    'variante_id' => $linea['variante_id'],
                    'cantidad' => $linea['cantidad'],
                    'costo_unitario' => $linea['costo_unitario'],
                    'impuesto' => $linea['impuesto'],
                    'total_linea' => $linea['total_linea'],
                ];
            }
            DB::table('compra_detalles')->insert($rows);

            $pendiente = $orden->detalles()
                ->selectRaw('SUM(cantidad - cantidad_recibida) as pendiente')
                ->value('pendiente');

            $orden->estado = (float) ($pendiente ?? 0) <= 0.00001 ? 'completa' : 'parcial';
            $orden->save();
        });

        $legacyPayload = $request->has('recibir_cantidad') || $request->has('recepcion_observacion');

        return $legacyPayload
            ? redirect()->route('compras.index')->with('status', 'Recepcion registrada y stock actualizado correctamente.')
            : redirect()->route('compras.ordenes.receive', $orden)->with('status', 'Recepcion registrada y stock actualizado correctamente.');
    }

    private function ensureOrderAccess(OrdenCompra $orden): void
    {
        abort_unless((int) $orden->empresa_id === (int) tenant_empresa_id(), 403, 'No tienes acceso a esta orden de compra.');

        $tenantSucursalId = tenant_sucursal_id();
        if ($tenantSucursalId && $orden->sucursal_id && (int) $orden->sucursal_id !== (int) $tenantSucursalId) {
            abort(403, 'No tienes acceso a esta orden desde la sucursal activa.');
        }
    }

    private function generateOrderNumber(int $empresaId): string
    {
        for ($attempt = 0; $attempt < 8; $attempt++) {
            $number = 'OC-'.now()->format('Ymd-His').'-'.Str::upper(Str::random(4));
            if (! OrdenCompra::query()->where('empresa_id', $empresaId)->where('numero', $number)->exists()) {
                return $number;
            }
        }

        return 'OC-'.now()->format('Ymd-His').'-'.Str::upper(Str::random(6));
    }

    private function generateReceptionNumber(int $empresaId): string
    {
        for ($attempt = 0; $attempt < 8; $attempt++) {
            $number = 'RC-'.now()->format('Ymd-His').'-'.Str::upper(Str::random(4));
            if (! RecepcionCompra::query()->where('empresa_id', $empresaId)->where('numero_recepcion', $number)->exists()) {
                return $number;
            }
        }

        return 'RC-'.now()->format('Ymd-His').'-'.Str::upper(Str::random(6));
    }

    private function normalizeOrderStorePayload(Request $request): array
    {
        $lineas = $request->input('lineas');
        if (is_array($lineas) && $lineas !== []) {
            return [
                'proveedor_id' => $request->input('proveedor_id'),
                'sucursal_id' => $request->input('sucursal_id'),
                'fecha' => $request->input('fecha'),
                'observacion' => $request->input('observacion'),
                'lineas' => $lineas,
            ];
        }

        $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', []);

        $mappedLineas = [];
        foreach ($productIds as $index => $productId) {
            $mappedLineas[] = [
                'producto_id' => $productId,
                'variante_id' => $variantIds[$index] ?? null,
                'cantidad' => $quantities[$index] ?? null,
                'costo_unitario' => $costs[$index] ?? null,
                'impuesto' => $taxes[$index] ?? 0,
            ];
        }

        return [
            'proveedor_id' => $request->input('oc_proveedor_id'),
            'sucursal_id' => $request->input('sucursal_id', tenant_sucursal_id()),
            'fecha' => $request->input('fecha', now()->format('Y-m-d H:i:s')),
            'observacion' => $request->input('oc_observacion'),
            'lineas' => $mappedLineas,
        ];
    }

    private function normalizeOrderReceivePayload(Request $request): array
    {
        $lineas = $request->input('lineas');
        if (is_array($lineas) && $lineas !== []) {
            return [
                'numero_recepcion' => $request->input('numero_recepcion'),
                'fecha_recepcion' => $request->input('fecha_recepcion'),
                'observacion' => $request->input('observacion'),
                'lineas' => $lineas,
            ];
        }

        $legacyQuantities = (array) $request->input('recibir_cantidad', []);
        $mappedLineas = [];
        foreach ($legacyQuantities as $detailId => $qty) {
            $mappedLineas[(int) $detailId] = $qty;
        }

        return [
            'numero_recepcion' => $request->input('numero_recepcion'),
            'fecha_recepcion' => $request->input('fecha_recepcion', now()->format('Y-m-d H:i:s')),
            'observacion' => $request->input('observacion', $request->input('recepcion_observacion')),
            'lineas' => $mappedLineas,
        ];
    }
}
