<?php

namespace App\Http\Controllers\Warehouse;

use App\Http\Controllers\Controller;
use App\Models\Warehouse\EasyInPurchase;
use App\Models\Warehouse\EasyOutDispatch;
use App\Models\Warehouse\Project;
use App\Models\Warehouse\ProjectDivision;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class EasyOutController extends Controller
{
    public function index(Request $request)
    {
        $query = EasyOutDispatch::with(['project', 'division', 'createdBy'])
            ->forPeriod($request->date_from, $request->date_to);

        if ($request->filled('project_id')) {
            $query->where('project_id', $request->project_id);
        }

        if ($request->filled('search')) {
            $query->where(function ($q) use ($request) {
                $q->where('item_description', 'LIKE', "%{$request->search}%")
                  ->orWhere('reference_number', 'LIKE', "%{$request->search}%")
                  ->orWhere('bill_number', 'LIKE', "%{$request->search}%")
                  ->orWhere('mr_no', 'LIKE', "%{$request->search}%");
            });
        }

        $dispatches = $query->orderBy('dispatch_date', 'desc')->orderBy('id', 'desc')->paginate(25)->withQueryString();
        $projects   = Project::active()->orderBy('project_name')->get(['id', 'project_number', 'project_name']);

        return view('warehouse.easy-out.index', compact('dispatches', 'projects'));
    }

    public function create()
    {
        $projects  = Project::active()->orderBy('project_name')->get(['id', 'project_number', 'project_name']);
        $divisions = ProjectDivision::where('status', 'active')->orderBy('division_name')->get(['id', 'division_name']);

        $billNumbers = EasyInPurchase::whereNotNull('invoice_number')
            ->where('invoice_number', '!=', '')
            ->distinct()
            ->orderBy('invoice_number')
            ->pluck('invoice_number');

        $easyInTotals = EasyInPurchase::selectRaw('invoice_number, item_description, unit, SUM(quantity) as total_purchased')
            ->whereNotNull('invoice_number')
            ->where('invoice_number', '!=', '')
            ->groupBy('invoice_number', 'item_description', 'unit')
            ->get();

        $easyOutTotals = EasyOutDispatch::selectRaw('bill_number, item_description, unit, SUM(quantity) as total_dispatched')
            ->whereNotNull('bill_number')
            ->groupBy('bill_number', 'item_description', 'unit')
            ->get()
            ->keyBy(fn ($row) => $this->stockKey($row->bill_number, $row->item_description, $row->unit));

        $easyInItems = $easyInTotals->map(function ($row) use ($easyOutTotals) {
            $key = $this->stockKey($row->invoice_number, $row->item_description, $row->unit);
            $dispatched = (float) ($easyOutTotals->get($key)?->total_dispatched ?? 0);
            $balance    = (float) $row->total_purchased - $dispatched;
            return (object) [
                'bill_number'      => $row->invoice_number,
                'item_description' => $row->item_description,
                'unit'             => $row->unit,
                'total_purchased'  => (float) $row->total_purchased,
                'total_dispatched' => $dispatched,
                'balance'          => $balance,
            ];
        })->filter(fn($r) => $r->balance > 0)->sortBy('item_description')->groupBy('bill_number');

        return view('warehouse.easy-out.create', compact('projects', 'divisions', 'billNumbers', 'easyInItems'));
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'dispatch_date'    => 'required|date',
            'bill_number'      => 'required|string|max:100',
            'project_id'       => 'required|exists:projects,id',
            'purpose'          => 'nullable|string|max:255',
            'received_by'      => 'nullable|string|max:150',
            'items'            => 'required|array|min:1',
            'items.*.mr_no'            => 'nullable|string|max:100',
            'items.*.item_description' => 'required|string|max:255',
            'items.*.unit'             => 'required|string|max:50',
            'items.*.quantity'         => 'required|numeric|min:0.01',
            'items.*.division_id'      => 'nullable|exists:project_divisions,id',
        ]);

        $this->validateAvailableBalances($data['bill_number'], $data['items']);

        $created = DB::transaction(function () use ($data) {
            return collect($data['items'])->map(function ($item) use ($data) {
                return EasyOutDispatch::create([
                    'dispatch_date'    => $data['dispatch_date'],
                    'bill_number'      => $data['bill_number'],
                    'mr_no'            => $item['mr_no'] ?? null,
                    'item_description' => $item['item_description'],
                    'unit'             => $item['unit'],
                    'quantity'         => $item['quantity'],
                    'project_id'       => $data['project_id'],
                    'division_id'      => $item['division_id'] ?? null,
                    'purpose'          => $data['purpose'] ?? null,
                    'received_by'      => $data['received_by'] ?? null,
                ]);
            });
        });

        return redirect()->route('warehouse.easy-out.show', $created->first())
            ->with('success', $created->count() . ' Easy Out dispatch line(s) recorded successfully.');
    }

    public function show(EasyOutDispatch $easyOut)
    {
        $easyOut->load(['project', 'division', 'createdBy']);
        return view('warehouse.easy-out.show', ['dispatch' => $easyOut]);
    }

    public function destroy(EasyOutDispatch $easyOut)
    {
        $easyOut->delete();
        return redirect()->route('warehouse.easy-out.index')
            ->with('success', 'Record deleted.');
    }

    private function validateAvailableBalances(string $billNumber, array $items): void
    {
        $available = $this->availableItemsForBill($billNumber);
        $requestedByItem = collect($items)->groupBy(fn ($item) => $this->stockKey($billNumber, $item['item_description'], $item['unit']));

        $errors = [];

        foreach ($requestedByItem as $key => $rows) {
            $requested = $rows->sum(fn ($item) => (float) $item['quantity']);
            $balance = (float) ($available->get($key)['balance'] ?? 0);
            $label = $rows->first()['item_description'];

            if ($balance <= 0) {
                $errors['items'] = "The selected item '{$label}' is not available for Bill Number {$billNumber}.";
                break;
            }

            if ($requested > $balance) {
                $errors['items'] = "Insufficient balance for '{$label}'. Max allowed: " . number_format($balance, 2) . '.';
                break;
            }
        }

        if ($errors) {
            throw ValidationException::withMessages($errors);
        }
    }

    private function availableItemsForBill(string $billNumber)
    {
        $easyInTotals = EasyInPurchase::selectRaw('item_description, unit, SUM(quantity) as total_purchased')
            ->where('invoice_number', $billNumber)
            ->groupBy('item_description', 'unit')
            ->get();

        $easyOutTotals = EasyOutDispatch::selectRaw('item_description, unit, SUM(quantity) as total_dispatched')
            ->where('bill_number', $billNumber)
            ->groupBy('item_description', 'unit')
            ->get()
            ->keyBy(fn ($row) => $this->stockKey($billNumber, $row->item_description, $row->unit));

        return $easyInTotals->mapWithKeys(function ($row) use ($billNumber, $easyOutTotals) {
            $key = $this->stockKey($billNumber, $row->item_description, $row->unit);
            $dispatched = (float) ($easyOutTotals->get($key)?->total_dispatched ?? 0);
            $balance = (float) $row->total_purchased - $dispatched;

            return [$key => [
                'item_description' => $row->item_description,
                'unit' => $row->unit,
                'balance' => $balance,
            ]];
        })->filter(fn ($item) => $item['balance'] > 0);
    }

    private function stockKey(?string $billNumber, string $description, string $unit): string
    {
        return strtolower(trim((string) $billNumber) . '|' . trim($description) . '|' . trim($unit));
    }
}
