<?php

namespace App\Livewire\Receptionist;

use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\DTOs\ProrationResult;
use App\Domain\Shared\Enums\ProrationMode;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\ProrationService;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingProgram;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('تسجيل في برنامج')]
class EnrollExistingWizard extends Component
{
    use UsesBranchScope;

    public int $currentStep = 1;
    public int $totalSteps = 4;

    // Step 1: Search
    public string $search = '';
    public ?int $selected_participant_id = null;
    public ?string $selected_participant_name = null;

    // Step 2: Program selection
    public ?int $selected_activity_id = null;
    public ?int $selected_program_id = null;

    // Free player toggle
    public bool $is_free = false;

    /**
     * What a mid-month joiner pays for. Settable from the browser by design;
     * see the same property on NewRegistrationWizard for why that is safe.
     */
    public string $proration_mode = ProrationMode::RemainingSessions->value;

    // Step 3: Payment
    public bool $pay_now = false;
    public string $payment_method = 'cash';

    // Result
    public bool $completed = false;
    public ?string $created_participant_uuid = null;
    public ?string $created_invoice_uuid = null;

    public function mount(): void
    {
        $this->authorize('enrollments.create');

        // An enrolment desk stands in one branch. Refuse the screen in
        // all-branches mode rather than guessing which branch to enrol into.
        // The id is deliberately not kept on the component — a public property
        // holding a branch is settable from the browser, which would make it an
        // authorisation bypass rather than a filter.
        $this->getActiveBranchIdOrFail();
    }

    public function rules(): array
    {
        $branchId = $this->getActiveBranchIdOrFail();

        return match ($this->currentStep) {
            1 => [
                // `exists:` is a raw table query — no Eloquent global scope
                // reaches it — and the id it checks comes from a public
                // property the browser can set.
                'selected_participant_id' => [
                    'required',
                    Rule::exists('participants', 'id')->where('branch_id', $branchId),
                ],
            ],
            2 => [
                // activities are academy-level: they carry no branch.
                'selected_activity_id' => 'required|exists:activities,id',
                // A branch runs its own programmes, so only this branch's
                // pass. exists: is raw SQL and never sees BranchScope.
                'selected_program_id' => [
                    'required',
                    Rule::exists('training_programs', 'id')->where(
                        fn ($q) => $q->where('branch_id', $branchId)
                    ),
                ],
            ],
            3 => [
                'payment_method' => 'required_if:pay_now,true|in:cash,card,wallet',
            ],
            default => [],
        };
    }

    public function messages(): array
    {
        return [
            'selected_participant_id.required' => 'يرجى اختيار المشترك',
            'selected_participant_id.exists' => 'المشترك المختار غير موجود',
            'selected_activity_id.required' => 'يرجى اختيار النشاط',
            'selected_activity_id.exists' => 'النشاط المختار غير موجود',
            'selected_program_id.required' => 'يرجى اختيار البرنامج',
            'selected_program_id.exists' => 'البرنامج المختار غير موجود',
            'payment_method.required_if' => 'يرجى اختيار طريقة الدفع',
            'payment_method.in' => 'طريقة الدفع غير صالحة',
        ];
    }

    public function selectParticipant(int $id, string $name): void
    {
        // Both arguments come from the browser. Resolve the row instead of
        // trusting them: Participant carries BranchScope, so another branch's
        // player does not come back at all, and the name shown is the one on
        // file rather than whatever was posted.
        $participant = Participant::with('person')->find($id);

        if (! $participant) {
            return;
        }

        $this->selected_participant_id = $participant->id;
        $this->selected_participant_name = $participant->person?->name_ar ?? $participant->person?->name ?? $name;
        $this->is_free = (bool) $participant->is_free;
    }

    public function nextStep(): void
    {
        $this->validate();
        $this->currentStep = min($this->currentStep + 1, $this->totalSteps);
    }

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

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

    public function updatedSelectedActivityId(): void
    {
        $this->selected_program_id = null;
    }

    public function confirm(): void
    {
        try {
            $enrollmentService = app(\App\Domain\Training\Services\EnrollmentService::class);

            $participant = Participant::findOrFail($this->selected_participant_id);
            $program = TrainingProgram::findOrFail($this->selected_program_id);

            if ($this->is_free && !$participant->is_free) {
                $participant->update(['is_free' => true]);
            }

            $enrollment = $enrollmentService->enrollInProgram(
                $participant,
                $program,
                auth()->user(),
                [
                    'pay_now' => $this->is_free ? false : $this->pay_now,
                    'payment_method' => (!$this->is_free && $this->pay_now) ? $this->payment_method : null,
                    // What step 3 showed the desk is what gets invoiced.
                    // Without this the service would fall back to its default
                    // and quietly bill a different figure from the one quoted.
                    'proration_mode' => $this->proration_mode,
                ]
            );

            $this->completed = true;
            $this->currentStep = 4;

            $this->created_participant_uuid = $participant->uuid;
            if ($enrollment->invoice_id) {
                $invoice = \App\Domain\Financial\Models\Invoice::find($enrollment->invoice_id);
                $this->created_invoice_uuid = $invoice?->uuid;
            }

            session()->flash('success', __('تم التسجيل في البرنامج بنجاح'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    /**
     * TrainingProgram carries BranchScope, so a programme id posted by hand
     * that belongs to another branch resolves to null here rather than to that
     * branch's timetable.
     */
    #[Computed]
    public function selectedProgram(): ?TrainingProgram
    {
        return $this->selected_program_id
            ? TrainingProgram::find($this->selected_program_id)
            : null;
    }

    #[Computed]
    public function selectedProgramFee(): int
    {
        if (!$this->selected_program_id) {
            return 0;
        }

        // Go through PricingService rather than reading BasePrice directly.
        // The raw lookup ignored base_prices.metadata->membership_type, so a
        // club member was quoted the non-member price (or whichever row
        // happened to sort first), and every pricing rule and discount was
        // skipped — quoting a different figure here than the registration
        // wizard quotes for the same player and programme.
        $program = TrainingProgram::find($this->selected_program_id);
        $participant = $this->selected_participant_id
            ? Participant::find($this->selected_participant_id)
            : null;

        if (! $program) {
            return 0;
        }

        try {
            return app(PricingService::class)
                ->calculate($program, $participant, $this->getActiveBranchIdOrFail())
                ->finalAmount;
        } catch (DomainException $e) {
            // No active base price is a hard stop by design — surface zero and
            // let the existing validation block the enrolment rather than
            // inventing a price.
            return 0;
        }
    }

    #[Computed]
    public function proratedProgramFee(): ProrationResult
    {
        $baseFee = $this->selectedProgramFee;
        $service = app(ProrationService::class);
        if (!$service->isEnabled() || $baseFee === 0) {
            return ProrationResult::fullMonth($baseFee, $service->renewalDay());
        }

        return $service->calculate(
            $baseFee,
            null,
            $this->selectedProgram?->defaultGroup(),
            $this->proration_mode,
        );
    }

    public function render()
    {
        $searchResults = collect();
        if (strlen($this->search) >= 2) {
            // No branch predicate: Participant carries BranchScope, and adding
            // one here would only duplicate it.
            $searchResults = Participant::query()
                ->with('person')
                ->whereIn('status', ['active', 'registered'])
                ->where(function ($q) {
                    $search = $this->search;
                    $q->where('participant_number', 'ilike', "%{$search}%")
                      ->orWhereHas('person', function ($pq) use ($search) {
                          $pq->where('name_ar', 'ilike', "%{$search}%")
                             ->orWhere('name', 'ilike', "%{$search}%")
                             ->orWhere('phone', 'like', "%{$search}%")
                             ->orWhere('national_id', 'like', "%{$search}%");
                      });
                })
                ->limit(10)
                ->get();
        }

        $activities = Activity::where('is_active', true)->orderBy('name_ar')->get();
        $programs = collect();

        if ($this->selected_activity_id && $this->selected_participant_id) {
            // Get programs not already enrolled in
            $enrolledProgramIds = Enrollment::where('participant_id', $this->selected_participant_id)
                ->whereIn('status', ['active', 'pending'])
                ->pluck('training_program_id');

            $programs = TrainingProgram::where('activity_id', $this->selected_activity_id)
                ->where('status', 'active')
                ->whereNotIn('id', $enrolledProgramIds)
                // whereHas applies the related model's global scopes, so
                // TrainingGroup's BranchScope already restricts this to groups
                // running in the active branch.
                ->whereHas('groups', fn ($q) => $q->whereIn('status', ['active', 'forming']))
                ->orderBy('name_ar')
                ->get();
        }

        return view('livewire.receptionist.enroll-existing-wizard', [
            'searchResults' => $searchResults,
            'activities' => $activities,
            'programs' => $programs,
        ]);
    }
}
