<?php

namespace App\Livewire\Pricing;

use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('سعر أساسي')]
class BasePriceForm extends Component
{
    use UsesBranchScope;

    /**
     * The two things a base price may be attached to, keyed by the short value
     * the <select> posts. Read in both directions: mount() turns the stored
     * class back into the select's value, save() turns it forward again.
     *
     * mount() used to assign the raw FQCN into $priceable_type, which the
     * `in:` rule rejects — so editing a price failed validation until the user
     * re-picked both fields, and priceableExistsRule() had no type to scope by.
     */
    private const PRICEABLE_TYPES = [
        'training_program' => TrainingProgram::class,
        'activity' => Activity::class,
    ];

    /**
     * Both are #[Locked]: a public model property is re-fetched from the wire
     * payload's id on every round-trip, and $editing decides between update
     * and create. Either one settable from the browser turns this form into a
     * write against a record the user never opened.
     */
    #[Locked]
    public ?BasePrice $basePrice = null;

    #[Locked]
    public bool $editing = false;

    public string $name_ar = '';
    public string $name = '';
    public string $priceable_type = '';
    public string $priceable_id = '';
    public string $branch_id = '';
    public string $amount = '';
    public string $effective_from = '';
    public string $effective_to = '';
    public int $priority = 0;
    public bool $is_active = true;
    public string $notes = '';

    public function mount(?BasePrice $basePrice = null): void
    {
        $this->authorize('pricing.create');

        if ($basePrice && $basePrice->exists) {
            $this->authorize('pricing.update');
            $this->assertBranchVisible($basePrice->branch_id);
            $this->basePrice = $basePrice;
            $this->editing = true;
            $this->name_ar = $basePrice->name_ar;
            $this->name = $basePrice->name ?? '';
            $this->priceable_type = (string) array_search(
                $basePrice->priceable_type,
                self::PRICEABLE_TYPES,
                true
            );
            $this->priceable_id = (string) ($basePrice->priceable_id ?? '');
            $this->branch_id = (string) ($basePrice->branch_id ?? '');
            $this->amount = (string) ($basePrice->amount / 100);
            $this->effective_from = $basePrice->effective_from?->format('Y-m-d') ?? '';
            $this->effective_to = $basePrice->effective_to?->format('Y-m-d') ?? '';
            $this->priority = $basePrice->priority ?? 0;
            $this->is_active = $basePrice->is_active;
            $this->notes = $basePrice->notes ?? '';
        } else {
            $this->effective_from = now()->format('Y-m-d');
        }
    }

    /**
     * Route model binding is substituted before ResolveBranchContext runs, so
     * BranchScope is not yet enforcing when the router resolves {basePrice}.
     * The record has to be checked here by hand.
     *
     * A price belongs to the branch that charges it, so only that branch may
     * open it.
     */
    private function assertBranchVisible(?int $branchId): void
    {
        $active = $this->getActiveBranchId();

        // A null active branch is a user genuinely in all-branches mode, who
        // may reach anything. A null on the record itself is a row the backfill
        // missed: strictly scoped, it belongs to no branch and is editable from
        // none, so refuse rather than let one branch quietly adopt it.
        abort_if($active !== null && $branchId !== $active, 404);
    }

    /**
     * The morph target must exist inside this academy and be reachable from
     * this branch. `exists:` is a raw table query — it sees neither the academy
     * scope nor BranchScope — so both predicates are spelled out here.
     */
    private function priceableExistsRule(): mixed
    {
        $academyId = app()->has('current_academy') ? app('current_academy')->id : null;
        $branchId = $this->getActiveBranchId();

        if ($this->priceable_type === 'training_program') {
            $rule = Rule::exists('training_programs', 'id')->whereNull('deleted_at');

            if ($academyId) {
                $rule->where('academy_id', $academyId);
            }

            // exists: is a raw query, so the global scope on TrainingProgram
            // does not reach it. A price may only be attached to a programme
            // this branch actually runs.
            if ($branchId !== null) {
                $rule->where('branch_id', $branchId);
            }

            return $rule;
        }

        if ($this->priceable_type === 'activity') {
            // Activities are academy-level; branch has no meaning for them.
            $rule = Rule::exists('activities', 'id');

            if ($academyId) {
                $rule->where('academy_id', $academyId);
            }

            return $rule;
        }

        // priceable_type already failed its own `in:` rule; nothing to check.
        return 'integer';
    }

    public function rules(): array
    {
        return [
            'name_ar' => 'required|string|max:255',
            'name' => 'nullable|string|max:255',
            'priceable_type' => 'required|string|in:training_program,activity',
            'priceable_id' => ['required', 'integer', $this->priceableExistsRule()],
            // Not exists:branches,id — that accepts every branch in the academy
            // from an input the browser controls. Only what the picker may
            // legitimately offer, plus the empty "all branches" value.
            // Required, not nullable: the row is strictly branch-scoped, so an
            // empty picker would file it against no branch and hide it from
            // every one of them.
            'branch_id' => ['required', Rule::in($this->selectableBranches()->pluck('id')->all())],
            'amount' => 'required|numeric|min:0.01',
            'effective_from' => 'required|date',
            'effective_to' => 'nullable|date|after:effective_from',
            'priority' => 'integer|min:0',
            'is_active' => 'boolean',
            'notes' => 'nullable|string|max:1000',
        ];
    }

    public function messages(): array
    {
        return [
            'name_ar.required' => __('الاسم بالعربية مطلوب'),
            'priceable_type.required' => __('نوع العنصر مطلوب'),
            'priceable_id.required' => __('العنصر مطلوب'),
            'amount.required' => __('المبلغ مطلوب'),
            'amount.min' => __('المبلغ يجب أن يكون أكبر من صفر'),
            'effective_from.required' => __('تاريخ البداية مطلوب'),
            'effective_to.after' => __('تاريخ النهاية يجب أن يكون بعد تاريخ البداية'),
            'priceable_id.exists' => __('العنصر المحدد غير صالح'),
            'branch_id.required' => 'يجب اختيار الفرع',
            'branch_id.in' => __('الفرع المحدد غير صالح'),
        ];
    }

    public function save(): void
    {
        $this->validate();

        $data = [
            'name_ar' => $this->name_ar,
            'name' => $this->name ?: null,
            'priceable_type' => self::PRICEABLE_TYPES[$this->priceable_type],
            'priceable_id' => (int) $this->priceable_id,
            'branch_id' => $this->branch_id ? (int) $this->branch_id : null,
            'amount' => (int) round((float) $this->amount * 100),
            'effective_from' => $this->effective_from,
            'effective_to' => $this->effective_to ?: null,
            'priority' => $this->priority,
            'is_active' => $this->is_active,
            'notes' => $this->notes ?: null,
        ];

        if ($this->editing && $this->basePrice) {
            $this->basePrice->update($data);
            session()->flash('success', __('تم تحديث السعر بنجاح'));
        } else {
            $data['created_by'] = auth()->id();
            $created = BasePrice::create($data);


            session()->flash('success', __('تم إنشاء السعر بنجاح'));
        }

        $this->redirect(route('pricing.base-prices'), navigate: true);
    }

    public function render()
    {
        $priceableOptions = [];
        if ($this->priceable_type === 'training_program') {
            $priceableOptions = TrainingProgram::orderBy('name_ar')->pluck('name_ar', 'id')->toArray();
        } elseif ($this->priceable_type === 'activity') {
            $priceableOptions = Activity::orderBy('name_ar')->pluck('name_ar', 'id')->toArray();
        }

        return view('livewire.pricing.base-price-form', [
            // The topbar switcher is the only branch selector. This one picks
            // which branch the price applies to, so it offers the branch you
            // are standing in (or every branch, for an all-branches user) plus
            // the academy-wide empty option the view renders itself.
            'branches' => $this->selectableBranches(),
            'priceableOptions' => $priceableOptions,
        ]);
    }
}
