<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\StockTransfer;
use App\Services\LocationInventoryService;
use App\Services\LocationService;
use App\Services\StockTransferService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class StockTransferController extends Controller
{
    public function __construct(
        protected StockTransferService $transfers,
        protected LocationService $locationService,
        protected LocationInventoryService $locationInventory
    ) {
        $this->middleware('auth');
    }

    public function index(\Illuminate\Http\Request $request): View
    {
        $user = auth()->user();
        $accessibleIds = $this->locationService->accessibleLocations($user)->pluck('id')->all();

        $transfers = StockTransfer::query()
            ->with(['fromLocation', 'toLocation', 'creator'])
            ->withCount('items')
            ->when(!$this->locationService->userCanViewAllLocations($user), function ($q) use ($accessibleIds) {
                $q->where(function ($q2) use ($accessibleIds) {
                    $q2->whereIn('from_location_id', $accessibleIds)
                        ->orWhereIn('to_location_id', $accessibleIds);
                });
            })
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->status))
            ->latest()
            ->paginate(30);

        return view('admin.transfers.index', compact('transfers'));
    }

    public function create(Request $request): View
    {
        $user = auth()->user();
        $fromLocations = $this->locationService->accessibleLocations($user);
        $toLocations = $this->locationService->transferDestinationLocations($user);

        $currentId = $this->locationService->resolveLocationIdForUser($user);
        $defaultFromId = (int) (
            old('from_location_id')
            ?? ($currentId && $fromLocations->contains(fn ($l) => (int) $l->id === (int) $currentId) ? $currentId : null)
            ?? $fromLocations->first(fn ($l) => $l->isWarehouse())?->id
            ?? $fromLocations->first()?->id
            ?? 0
        );

        $defaultToId = (int) (
            old('to_location_id')
            ?? $toLocations->first(function ($l) use ($defaultFromId) {
                return (int) $l->id !== $defaultFromId && $l->isStore();
            })?->id
            ?? $toLocations->first(fn ($l) => (int) $l->id !== $defaultFromId)?->id
            ?? 0
        );

        $products = $defaultFromId > 0
            ? $this->productsWithStockAtLocation($defaultFromId)
            : collect();

        return view('admin.transfers.create', [
            'locations' => $fromLocations,
            'fromLocations' => $fromLocations,
            'toLocations' => $toLocations,
            'defaultFromId' => $defaultFromId,
            'defaultToId' => $defaultToId,
            'products' => $products,
            'productsUrl' => route('admin.transfers.products'),
        ]);
    }

    /**
     * JSON: products + available stock at the selected From Location.
     */
    public function products(Request $request): JsonResponse
    {
        $data = $request->validate([
            'from_location_id' => 'required|integer|exists:locations,id',
        ]);

        $locationId = (int) $data['from_location_id'];
        $user = auth()->user();
        if (!$this->locationService->userCanAccessLocation($user, $locationId)) {
            abort(403, 'You do not have access to this location.');
        }

        $products = $this->productsWithStockAtLocation($locationId);

        return response()->json([
            'success' => true,
            'data' => $products->map(fn ($p) => [
                'id' => (int) $p->id,
                'name' => $p->name,
                'sku' => $p->sku,
                'stock' => (int) $p->location_stock,
                'text' => $p->name . ' (Stock: ' . (int) $p->location_stock . ')',
            ])->values(),
        ]);
    }

    /**
     * Active products with available qty > 0 at a location (not company-wide current_stock).
     *
     * @return \Illuminate\Support\Collection<int, Product>
     */
    protected function productsWithStockAtLocation(int $locationId)
    {
        if ($this->locationInventory->useLocationInventory()) {
            $availableExpr = 'GREATEST(COALESCE(inventory_balances.on_hand, 0) - COALESCE(inventory_balances.reserved, 0) - COALESCE(inventory_balances.damaged, 0), 0)';

            return Product::query()
                ->active()
                ->join('inventory_balances', function ($join) use ($locationId) {
                    $join->on('inventory_balances.product_id', '=', 'products.id')
                        ->where('inventory_balances.location_id', '=', $locationId);
                })
                ->whereRaw("{$availableExpr} > 0")
                ->orderByDesc(DB::raw($availableExpr))
                ->orderBy('products.name')
                ->limit(500)
                ->get([
                    'products.id',
                    'products.name',
                    'products.sku',
                    DB::raw("{$availableExpr} as location_stock"),
                ]);
        }

        return Product::query()
            ->active()
            ->where('current_stock', '>', 0)
            ->orderByDesc('current_stock')
            ->orderBy('name')
            ->limit(500)
            ->get(['id', 'name', 'sku', 'current_stock'])
            ->map(function (Product $p) {
                $p->location_stock = (int) $p->current_stock;

                return $p;
            });
    }

    public function store(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'from_location_id' => 'required|integer|exists:locations,id|different:to_location_id',
            'to_location_id' => 'required|integer|exists:locations,id|different:from_location_id',
            'notes' => 'nullable|string',
            'items' => 'required|array|min:1',
            'items.*.product_id' => 'required|integer|exists:products,id',
            'items.*.quantity' => 'required|integer|min:1',
        ], [
            'from_location_id.different' => 'From location and To location must be different.',
            'to_location_id.different' => 'From location and To location must be different.',
        ]);

        $fromLocationId = (int) $data['from_location_id'];
        $toLocationId = (int) $data['to_location_id'];

        if ($fromLocationId === $toLocationId) {
            return back()
                ->withInput()
                ->withErrors(['to_location_id' => 'From location and To location must be different.']);
        }

        $user = auth()->user();
        $this->locationService->assertCanCreateTransfer($user, $fromLocationId, $toLocationId);

        $stockErrors = $this->validateTransferItemStock($fromLocationId, $data['items']);
        if ($stockErrors !== []) {
            return back()->withInput()->withErrors($stockErrors);
        }

        try {
            $transfer = $this->transfers->create(
                $fromLocationId,
                $toLocationId,
                $data['items'],
                $data['notes'] ?? null
            );
        } catch (\App\Exceptions\StockException $e) {
            return back()->withInput()->withErrors(['items' => $e->getMessage()]);
        }

        return redirect()->route('admin.transfers.show', $transfer)->with('success', 'Transfer draft created.');
    }

    /**
     * Ensure each line qty (and combined qty per product) <= available at From Location.
     *
     * @param  array<int, array{product_id:mixed, quantity:mixed}>  $items
     * @return array<string, string>
     */
    protected function validateTransferItemStock(int $fromLocationId, array $items): array
    {
        $errors = [];
        $totalsByProduct = [];

        foreach ($items as $index => $item) {
            $productId = (int) ($item['product_id'] ?? 0);
            $qty = (int) ($item['quantity'] ?? 0);
            if ($productId <= 0 || $qty <= 0) {
                continue;
            }

            $available = $this->locationInventory->getAvailableStock($productId, $fromLocationId);
            if ($qty > $available) {
                $productName = Product::query()->whereKey($productId)->value('name') ?? "Product #{$productId}";
                $errors["items.{$index}.quantity"] = "{$productName}: transfer qty ({$qty}) cannot exceed available stock ({$available}) at From location.";
            }

            $totalsByProduct[$productId] = ($totalsByProduct[$productId] ?? 0) + $qty;
        }

        foreach ($totalsByProduct as $productId => $totalQty) {
            $available = $this->locationInventory->getAvailableStock($productId, $fromLocationId);
            if ($totalQty > $available) {
                $productName = Product::query()->whereKey($productId)->value('name') ?? "Product #{$productId}";
                $errors['items'] = ($errors['items'] ?? '') !== ''
                    ? $errors['items']
                    : "{$productName}: total transfer qty ({$totalQty}) across lines exceeds available stock ({$available}) at From location.";
            }
        }

        return $errors;
    }

    public function show(StockTransfer $transfer): View
    {
        $this->authorizeTransferAccess($transfer);
        $transfer->load(['fromLocation', 'toLocation', 'items.product', 'creator', 'approver']);

        return view('admin.transfers.show', compact('transfer'));
    }

    public function approve(StockTransfer $transfer): RedirectResponse
    {
        $this->authorizeTransferAccess($transfer);
        $this->transfers->approve($transfer);

        return back()->with('success', 'Transfer approved.');
    }

    public function ship(StockTransfer $transfer): RedirectResponse
    {
        $this->authorizeTransferAccess($transfer);
        $this->transfers->ship($transfer);

        return back()->with('success', 'Transfer shipped (in transit).');
    }

    public function receive(StockTransfer $transfer): RedirectResponse
    {
        $this->authorizeTransferAccess($transfer);
        $this->transfers->receive($transfer);

        return back()->with('success', 'Transfer received into destination stock.');
    }

    public function cancel(StockTransfer $transfer): RedirectResponse
    {
        $this->authorizeTransferAccess($transfer);
        $this->transfers->cancel($transfer);

        return back()->with('success', 'Transfer cancelled.');
    }

    protected function authorizeTransferAccess(StockTransfer $transfer): void
    {
        $user = auth()->user();
        if ($this->locationService->userCanViewAllLocations($user)) {
            return;
        }

        $ids = $this->locationService->accessibleLocations($user)->pluck('id')->map(fn ($id) => (int) $id)->all();
        if (
            !in_array((int) $transfer->from_location_id, $ids, true)
            && !in_array((int) $transfer->to_location_id, $ids, true)
        ) {
            abort(403, 'You do not have access to this transfer.');
        }
    }
}
