<?php

namespace App\Livewire\Inventory;

use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\PurchaseOrder;
use App\Domain\Inventory\Models\Warehouse;
use App\Domain\Inventory\Services\PurchaseOrderService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('أمر شراء جديد')]
class CreatePurchaseOrderWizard extends Component
{
    use UsesBranchScope;

    public int $currentStep = 1;
    public int $totalSteps = 4;
    public bool $completed = false;

    // Step 1: Supplier
    public string $supplier_name = '';
    public string $supplier_contact = '';

    // Step 2: Warehouse & Dates
    // Locked: set server-side by selectWarehouse() from the branch-filtered list
    // render() builds (the blade binds it with wire:click, never wire:model),
    // and confirm() writes it straight onto the order.
    #[Locked]
    public ?int $warehouseId = null;
    public ?array $selectedWarehouse = null;
    public string $order_date = '';
    public string $expected_delivery_date = '';

    // Step 3: Items
    public array $items = [];

    // Step 4: Additional costs
    public string $tax_amount = '0';
    public string $shipping_cost = '0';
    public string $notes = '';

    // Result
    public ?string $orderNumber = null;
    public ?string $orderUuid = null;

    public function mount(): void
    {
        $this->authorize('inventory.manage');
        $this->order_date = now()->format('Y-m-d');
        $this->items[] = ['product_id' => '', 'quantity_ordered' => '1', 'unit_cost' => '', 'notes' => ''];
    }

    public function getStepLabels(): array
    {
        return [
            1 => 'المورد',
            2 => 'المستودع والتاريخ',
            3 => 'الأصناف',
            4 => 'مراجعة وتأكيد',
        ];
    }

    public function selectWarehouse(int $id): void
    {
        $warehouse = Warehouse::findOrFail($id);
        $this->warehouseId = $id;
        $this->selectedWarehouse = [
            'id' => $warehouse->id,
            'name_ar' => $warehouse->name_ar,
        ];
    }

    public function addItem(): void
    {
        $this->items[] = ['product_id' => '', 'quantity_ordered' => '1', 'unit_cost' => '', 'notes' => ''];
    }

    public function removeItem(int $index): void
    {
        unset($this->items[$index]);
        $this->items = array_values($this->items);

        if (empty($this->items)) {
            $this->addItem();
        }
    }

    public function getItemsTotalProperty(): int
    {
        return (int) collect($this->items)->sum(function ($item) {
            return ((int) ($item['quantity_ordered'] ?? 0)) * (int) round((float) ($item['unit_cost'] ?? 0) * 100);
        });
    }

    public function getGrandTotalProperty(): int
    {
        return $this->getItemsTotalProperty()
            + (int) round((float) $this->tax_amount * 100)
            + (int) round((float) $this->shipping_cost * 100);
    }

    public function nextStep(): void
    {
        $this->validate($this->rulesForStep($this->currentStep));

        if ($this->currentStep < $this->totalSteps) {
            $this->currentStep++;
        }
    }

    public function previousStep(): void
    {
        if ($this->currentStep > 1) {
            $this->currentStep--;
        }
    }

    public function goToStep(int $step): void
    {
        if ($step < $this->currentStep) {
            $this->currentStep = $step;
        }
    }

    public function confirm(): void
    {
        // currentStep is a plain public property, so the browser can set it to
        // the review step and call confirm() without nextStep() ever running.
        // $items is a plain public array whose product ids are written straight
        // onto purchase_order_items, and the only thing checking them against
        // the branch is the step-3 rule — which is why it is re-run here, at
        // the write, rather than only on the way past it.
        foreach (range(1, $this->totalSteps) as $step) {
            $rules = $this->rulesForStep($step);

            if (! empty($rules)) {
                $this->validate($rules, $this->messages());
            }
        }

        try {
            $branchId = $this->getActiveBranchId() ?? auth()->user()->branch_id;

            // Deliberately cross-branch: purchase_orders is unique(academy_id,
            // order_number), so the day's counter has to see every branch's
            // orders. Branch-scoped, two branches ordering on the same day would
            // both mint PO-…-001 and the second would hit the constraint.
            $orderNumber = 'PO-' . now()->format('Ymd') . '-' . str_pad(
                PurchaseOrder::withoutBranchScope()->whereDate('created_at', today())->count() + 1,
                3, '0', STR_PAD_LEFT
            );

            $data = [
                'academy_id' => app('current_academy')->id,
                'branch_id' => $branchId,
                'warehouse_id' => $this->warehouseId,
                'order_number' => $orderNumber,
                'supplier_name' => $this->supplier_name,
                'supplier_contact' => $this->supplier_contact ?: null,
                'order_date' => $this->order_date,
                'expected_delivery_date' => $this->expected_delivery_date ?: null,
                'tax_amount' => (int) round((float) $this->tax_amount * 100),
                'shipping_cost' => (int) round((float) $this->shipping_cost * 100),
                'notes' => $this->notes ?: null,
                'metadata' => [],
            ];

            $items = collect($this->items)->map(fn ($item) => [
                'product_id' => (int) $item['product_id'],
                'quantity_ordered' => (int) $item['quantity_ordered'],
                'unit_cost' => (int) round((float) $item['unit_cost'] * 100),
                'notes' => $item['notes'] ?? null,
            ])->toArray();

            $service = app(PurchaseOrderService::class);
            $po = $service->create($data, $items, auth()->user());

            $this->completed = true;
            $this->orderNumber = $po->order_number;
            $this->orderUuid = $po->uuid ?? $po->id;
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    private function rulesForStep(int $step): array
    {
        // exists: is a raw table query — no academy scope, no branch scope — and
        // $items is a plain public array, so both predicates are written out.
        $academyId = app('current_academy')->id;
        $branchId = $this->getActiveBranchId();

        $inBranch = fn ($rule) => $branchId === null
            ? $rule
            : $rule->where(fn ($q) => $q->where('branch_id', $branchId));

        return match ($step) {
            1 => [
                'supplier_name' => 'required|string|max:255',
                'supplier_contact' => 'nullable|string|max:255',
            ],
            2 => [
                'warehouseId' => ['required', 'integer', $inBranch(
                    Rule::exists('warehouses', 'id')->where('academy_id', $academyId)->whereNull('deleted_at')
                )],
                'order_date' => 'required|date',
                'expected_delivery_date' => 'nullable|date|after_or_equal:order_date',
            ],
            3 => [
                'items' => 'required|array|min:1',
                'items.*.product_id' => ['required', $inBranch(
                    Rule::exists('products', 'id')->where('academy_id', $academyId)->whereNull('deleted_at')
                )],
                'items.*.quantity_ordered' => 'required|integer|min:1',
                'items.*.unit_cost' => 'required|numeric|min:0.01',
            ],
            default => [],
        };
    }

    public function messages(): array
    {
        return [
            'supplier_name.required' => 'اسم المورد مطلوب',
            'warehouseId.required' => 'يجب اختيار المستودع',
            'warehouseId.exists' => 'المستودع غير موجود',
            'order_date.required' => 'تاريخ الطلب مطلوب',
            'expected_delivery_date.after_or_equal' => 'تاريخ التسليم يجب أن يكون بعد تاريخ الطلب',
            'items.required' => 'يجب إضافة صنف واحد على الأقل',
            'items.min' => 'يجب إضافة صنف واحد على الأقل',
            'items.*.product_id.required' => 'اختر المنتج',
            'items.*.product_id.exists' => 'المنتج غير موجود',
            'items.*.quantity_ordered.required' => 'الكمية مطلوبة',
            'items.*.quantity_ordered.min' => 'الكمية يجب أن تكون 1 على الأقل',
            'items.*.unit_cost.required' => 'سعر الوحدة مطلوب',
            'items.*.unit_cost.min' => 'سعر الوحدة يجب أن يكون أكبر من صفر',
        ];
    }

    public function render()
    {
        // Both pickers are branch-filtered by the models' own BranchScope, so
        // neither carries a hand-written branch clause.
        return view('livewire.inventory.create-purchase-order-wizard', [
            'warehouses' => Warehouse::query()
                ->where('is_active', true)
                ->orderBy('name_ar')
                ->get(['id', 'name_ar', 'code']),
            'products' => Product::query()
                ->where('is_active', true)
                ->select('id', 'name_ar', 'sku')
                ->orderBy('name_ar')
                ->get(),
        ]);
    }
}
