<?php

namespace App\Livewire\Pricing;

use App\Domain\Pricing\Enums\AdjustmentType;
use App\Domain\Pricing\Enums\PricingRuleType;
use App\Domain\Pricing\Models\PricingRule;
use App\Domain\Pricing\Support\ConditionSchema;
use App\Domain\Pricing\Support\Percent;
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 PricingRuleForm extends Component
{
    use UsesBranchScope;

    /**
     * #[Locked] because a public model property is re-resolved from the id in
     * the wire payload on every round-trip, and $editing is what decides
     * between update and create.
     */
    #[Locked]
    public ?PricingRule $pricingRule = null;

    #[Locked]
    public bool $editing = false;

    public string $name_ar = '';
    public string $name = '';
    public string $rule_type = '';
    public string $adjustment_type = '';
    public string $adjustment_value = '';
    public array $conditions = [];

    /**
     * No control in pricing-rule-form.blade.php binds either of these — they
     * are read out of the record in mount() and written back in save(). Left
     * unlocked they were still settable from the browser, which is how a rule
     * could be aimed at another branch's programme without the form ever
     * showing a target field.
     */
    #[Locked]
    public string $target_type = '';

    #[Locked]
    public string $target_id = '';

    public string $branch_id = '';
    public int $priority = 0;
    public bool $is_stackable = true;
    public string $max_discount_percent = '';
    public string $effective_from = '';
    public string $effective_to = '';
    public bool $is_active = true;
    public string $usage_limit = '';

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

        if ($pricingRule && $pricingRule->exists) {
            $this->authorize('pricing.update');
            $this->assertRuleVisible($pricingRule);
            $this->pricingRule = $pricingRule;
            $this->editing = true;
            $this->name_ar = $pricingRule->name_ar;
            $this->name = $pricingRule->name ?? '';
            $this->rule_type = $pricingRule->rule_type?->value ?? '';
            $this->adjustment_type = $pricingRule->adjustment_type?->value ?? '';
            $this->adjustment_value = $this->formatAdjustmentValueForDisplay($pricingRule);
            $this->conditions = $pricingRule->conditions ?? [];
            $this->target_type = $pricingRule->target_type ?? '';
            $this->target_id = (string) ($pricingRule->target_id ?? '');
            $this->branch_id = (string) ($pricingRule->branch_id ?? '');
            $this->priority = $pricingRule->priority ?? 0;
            $this->is_stackable = $pricingRule->is_stackable;
            $this->max_discount_percent = (string) ($pricingRule->max_discount_percent ?? '');
            $this->effective_from = $pricingRule->effective_from?->format('Y-m-d') ?? '';
            $this->effective_to = $pricingRule->effective_to?->format('Y-m-d') ?? '';
            $this->is_active = $pricingRule->is_active;
            $this->usage_limit = (string) ($pricingRule->usage_limit ?? '');
        } 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 {pricingRule}.
     * The record has to be checked here by hand — and by the same predicate the
     * list uses, which reads the pricing_rule_branches pivot as well as the
     * legacy branch_id column.
     *
     * A rule targeting nothing is academy-wide configuration and stays editable
     * from any branch; a null active branch is a user in all-branches mode.
     */
    private function assertRuleVisible(PricingRule $rule): void
    {
        $active = $this->getActiveBranchId();

        if ($active === null) {
            return;
        }

        $targets = $rule->targetBranchIds();

        abort_if($targets !== [] && ! in_array($active, $targets, true), 404);
    }

    private function formatAdjustmentValueForDisplay(PricingRule $rule): string
    {
        if (in_array($rule->adjustment_type, [AdjustmentType::FixedDiscount, AdjustmentType::FixedPrice])) {
            return (string) ($rule->adjustment_value / 100);
        }

        // Percentages live in the column as basis points; humans read percent.
        return (string) Percent::fromBasisPoints((int) $rule->adjustment_value);
    }

    public function rules(): array
    {
        $rules = [
            'name_ar' => 'required|string|max:255',
            'name' => 'nullable|string|max:255',
            'rule_type' => 'required|in:' . implode(',', array_column(PricingRuleType::cases(), 'value')),
            'adjustment_type' => 'required|in:' . implode(',', array_column(AdjustmentType::cases(), 'value')),
            'adjustment_value' => 'required|numeric|min:0.01',
            'conditions' => 'nullable|array',
            'target_type' => 'nullable|string|max:255',
            'target_id' => 'nullable|integer',
            // Not exists:branches,id — a raw table query accepts every branch
            // in the academy. 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())],
            'priority' => 'integer|min:0',
            'is_stackable' => 'boolean',
            'max_discount_percent' => 'nullable|integer|min:1|max:100',
            'effective_from' => 'required|date',
            'effective_to' => 'nullable|date|after:effective_from',
            'is_active' => 'boolean',
            'usage_limit' => 'nullable|integer|min:1',
        ];

        // A `branch` rule's conditions.values IS a list of branch ids — the
        // engine matches it against the participant's branch_id
        // (PricingService::evaluateInList). The checkbox list only offers this
        // branch, but conditions is a plain public array and the browser can
        // put anything in it, so an academy-wide rule authored here could hand
        // a discount to another branch's participants.
        if ($this->rule_type === PricingRuleType::Branch->value) {
            $rules['conditions.values.*'] = [
                Rule::in($this->selectableBranches()->pluck('id')->all()),
            ];
        }

        return $rules;
    }

    public function messages(): array
    {
        return [
            'name_ar.required' => __('الاسم بالعربية مطلوب'),
            'rule_type.required' => __('نوع القاعدة مطلوب'),
            'rule_type.in' => __('نوع القاعدة غير صالح'),
            'adjustment_type.required' => __('نوع التعديل مطلوب'),
            'adjustment_type.in' => __('نوع التعديل غير صالح'),
            'adjustment_value.required' => __('قيمة التعديل مطلوبة'),
            'adjustment_value.min' => __('قيمة التعديل يجب أن تكون أكبر من صفر'),
            'effective_from.required' => __('تاريخ البداية مطلوب'),
            'effective_to.after' => __('تاريخ النهاية يجب أن يكون بعد تاريخ البداية'),
            'max_discount_percent.min' => __('الحد الأقصى للخصم يجب أن يكون بين 1 و 100'),
            'max_discount_percent.max' => __('الحد الأقصى للخصم يجب أن يكون بين 1 و 100'),
            'branch_id.required' => 'يجب اختيار الفرع',
            'branch_id.in' => __('الفرع المحدد غير صالح'),
            'conditions.values.*.in' => __('الفرع المحدد غير صالح'),
        ];
    }

    public function updatedRuleType(): void
    {
        $this->conditions = [];
    }

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

        $adjustmentValue = $this->computeAdjustmentValueForStorage();

        // Every write goes through the schema so condition keys cannot drift
        // away from what the engine reads.
        $conditions = ConditionSchema::normalize($this->rule_type, $this->conditions ?? []);

        if (! ConditionSchema::isMeaningful($this->rule_type, $conditions)) {
            $this->addError('conditions', __('يجب تحديد شرط واحد على الأقل — وإلا سيُطبق الخصم على الجميع'));

            return;
        }

        $data = [
            'name_ar' => $this->name_ar,
            'name' => $this->name ?: null,
            'rule_type' => $this->rule_type,
            'adjustment_type' => $this->adjustment_type,
            'adjustment_value' => $adjustmentValue,
            'conditions' => $conditions,
            'target_type' => $this->target_type ?: null,
            'target_id' => $this->target_id ? (int) $this->target_id : null,
            'branch_id' => $this->branch_id ? (int) $this->branch_id : null,
            'priority' => $this->priority,
            'is_stackable' => $this->is_stackable,
            'max_discount_percent' => $this->max_discount_percent ? (int) $this->max_discount_percent : null,
            'effective_from' => $this->effective_from,
            'effective_to' => $this->effective_to ?: null,
            'is_active' => $this->is_active,
            'usage_limit' => $this->usage_limit ? (int) $this->usage_limit : null,
        ];

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


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

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

    private function computeAdjustmentValueForStorage(): int
    {
        $type = AdjustmentType::tryFrom($this->adjustment_type);
        if (in_array($type, [AdjustmentType::FixedDiscount, AdjustmentType::FixedPrice])) {
            return (int) round((float) $this->adjustment_value * 100);
        }

        // The engine divides by 10000. Storing a bare "20" made every
        // percentage discount 100x too small.
        return Percent::toBasisPoints($this->adjustment_value);
    }

    public function render()
    {
        return view('livewire.pricing.pricing-rule-form', [
            // Feeds both the "which branch does this rule belong to" picker and
            // the branch-condition checkbox list. The topbar switcher is the
            // only branch selector; this returns one entry unless the user is
            // genuinely in all-branches mode.
            'branches' => $this->selectableBranches(),
            'ruleTypes' => collect(PricingRuleType::cases())->mapWithKeys(fn ($t) => [$t->value => $t->label()])->toArray(),
            'adjustmentTypes' => collect(AdjustmentType::cases())->mapWithKeys(fn ($t) => [$t->value => $t->label()])->toArray(),
        ]);
    }
}
