<?php

namespace App\Livewire\Enrollments;

use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\EnrollmentService;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

/**
 * Move one or many players from one training group to another.
 *
 * Deliberately NOT cross-branch, and that is the whole of the opt-out: there is
 * no withoutBranchScope() / forBranch() anywhere in this file. Source groups,
 * source enrolments, destination programmes and destination groups all come
 * back through the branch global scope, so the wizard only ever moves a player
 * between groups of the branch the user is standing in.
 *
 * It is not scoped this way out of caution. EnrollmentService::transfer()
 * creates the replacement enrolment (and, through AttendanceGenerationService,
 * its future attendance) without an explicit branch, so BelongsToBranch stamps
 * them from the *session*. Pointing this screen at another branch's group would
 * therefore produce an enrolment filed under branch A whose group lives in
 * branch B — invisible from the branch that now trains the player, and counted
 * by the branch that no longer does. A destination-branch picker belongs here
 * only once the service takes an explicit ?int $branchId; until then widening
 * the group list corrupts data rather than enabling a feature.
 */
#[Layout('layouts.app')]
#[Title('نقل مشتركين')]
class TransferParticipantWizard extends Component
{
    use UsesBranchScope;

    // Everything below that carries an id or a resolved row is #[Locked]: it is
    // written only by the select*/toggle* methods, each of which resolves the id
    // through a branch-scoped model first. A plain public property here would be
    // a browser-supplied id feeding a lookup — an authorisation question, not a
    // filter — and $selectedParticipants in particular is the array confirm()
    // iterates when it actually moves people.
    #[Locked]
    public int $currentStep = 1;

    #[Locked]
    public int $totalSteps = 4;

    #[Locked]
    public bool $completed = false;

    // Mode: 'single' or 'bulk'
    public string $mode = 'bulk';

    // Step 1: Source selection
    #[Locked]
    public ?int $sourceGroupId = null;

    #[Locked]
    public ?array $sourceGroupInfo = null;

    public string $participantSearch = '';

    // Selected participants (enrollment_id => participant info)
    #[Locked]
    public array $selectedParticipants = [];

    #[Locked]
    public bool $selectAll = false;

    // Single mode
    #[Locked]
    public ?int $singleParticipantId = null;

    #[Locked]
    public ?int $singleEnrollmentId = null;

    #[Locked]
    public ?array $singleParticipantInfo = null;

    #[Locked]
    public array $singleEnrollments = [];

    // Step 2: Destination
    #[Locked]
    public ?int $destinationProgramId = null;

    #[Locked]
    public ?int $destinationGroupId = null;

    #[Locked]
    public ?array $destinationGroupInfo = null;

    // Step 3: Reason
    public string $reason = '';

    // Step 4: Results
    #[Locked]
    public array $transferResults = [];

    #[Locked]
    public int $successCount = 0;

    #[Locked]
    public int $failCount = 0;

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

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

    // --- Mode ---

    public function setMode(string $mode): void
    {
        $this->mode = $mode;
        $this->reset(['sourceGroupId', 'sourceGroupInfo', 'participantSearch', 'selectedParticipants', 'selectAll', 'singleParticipantId', 'singleEnrollmentId', 'singleParticipantInfo', 'singleEnrollments']);
    }

    // --- Bulk Mode: Source Group ---

    public function clearSourceGroup(): void
    {
        $this->reset(['sourceGroupId', 'sourceGroupInfo', 'selectedParticipants', 'selectAll']);
    }

    public function selectSourceGroup(int $id): void
    {
        // findOrFail runs under BranchScope: a group id from another branch
        // resolves to nothing and 404s rather than seeding the wizard with it.
        $group = TrainingGroup::with('program')->findOrFail($id);
        $this->sourceGroupId = $id;
        $this->sourceGroupInfo = [
            'id' => $group->id,
            'name_ar' => $group->name_ar,
            'program_name' => $group->program?->name_ar ?? '-',
            'current_count' => $group->current_count,
            'max_capacity' => $group->max_capacity,
            'program_id' => $group->training_program_id,
        ];
        $this->selectedParticipants = [];
        $this->selectAll = false;
    }

    public function toggleParticipant(int $enrollmentId): void
    {
        if (isset($this->selectedParticipants[$enrollmentId])) {
            unset($this->selectedParticipants[$enrollmentId]);
            $this->selectAll = false;
        } else {
            // Branch-scoped by the model; pinned to the source group so the
            // selection cannot pick up an enrolment the list never showed.
            $enrollment = Enrollment::with('participant.person')
                ->where('training_group_id', $this->sourceGroupId)
                ->find($enrollmentId);

            if ($enrollment) {
                $this->selectedParticipants[$enrollmentId] = [
                    'enrollment_id' => $enrollmentId,
                    'participant_id' => $enrollment->participant_id,
                    'name' => $enrollment->participant?->person?->name_ar ?? $enrollment->participant?->person?->name ?? '-',
                    'participant_number' => $enrollment->participant?->participant_number,
                ];
            }
        }
    }

    public function toggleSelectAll(): void
    {
        if ($this->selectAll) {
            $this->selectedParticipants = [];
            $this->selectAll = false;
            return;
        }

        $enrollments = Enrollment::where('training_group_id', $this->sourceGroupId)
            ->where('status', 'active')
            ->with('participant.person')
            ->get();

        $this->selectedParticipants = [];
        foreach ($enrollments as $enrollment) {
            $this->selectedParticipants[$enrollment->id] = [
                'enrollment_id' => $enrollment->id,
                'participant_id' => $enrollment->participant_id,
                'name' => $enrollment->participant?->person?->name_ar ?? $enrollment->participant?->person?->name ?? '-',
                'participant_number' => $enrollment->participant?->participant_number,
            ];
        }
        $this->selectAll = true;
    }

    // --- Single Mode ---

    public function selectSingleParticipant(int $id): void
    {
        $participant = Participant::with('person')->findOrFail($id);
        $this->singleParticipantId = $id;
        $this->singleParticipantInfo = [
            'id' => $participant->id,
            'name' => $participant->person?->name_ar ?? $participant->person?->name ?? '-',
            'participant_number' => $participant->participant_number,
        ];

        $this->singleEnrollments = Enrollment::where('participant_id', $id)
            ->where('status', 'active')
            ->with(['group', 'program'])
            ->get()
            ->map(fn ($e) => [
                'id' => $e->id,
                'group_name' => $e->group?->name_ar ?? '-',
                'program_name' => $e->program?->name_ar ?? '-',
                'group_id' => $e->training_group_id,
                'program_id' => $e->training_program_id,
            ])->toArray();
    }

    public function selectSingleEnrollment(int $enrollmentId): void
    {
        $enrollment = Enrollment::with(['participant.person', 'group'])->findOrFail($enrollmentId);
        $this->singleEnrollmentId = $enrollmentId;
        $this->sourceGroupId = $enrollment->training_group_id;
        $this->sourceGroupInfo = [
            'id' => $enrollment->group?->id,
            'name_ar' => $enrollment->group?->name_ar,
            'program_name' => $enrollment->program?->name_ar ?? '-',
            'current_count' => $enrollment->group?->current_count,
            'max_capacity' => $enrollment->group?->max_capacity,
            'program_id' => $enrollment->training_program_id,
        ];

        $this->selectedParticipants = [
            $enrollmentId => [
                'enrollment_id' => $enrollmentId,
                'participant_id' => $enrollment->participant_id,
                'name' => $enrollment->participant?->person?->name_ar ?? $enrollment->participant?->person?->name ?? '-',
                'participant_number' => $enrollment->participant?->participant_number,
            ],
        ];
    }

    public function clearSingleParticipant(): void
    {
        $this->reset(['singleParticipantId', 'singleParticipantInfo', 'singleEnrollments', 'singleEnrollmentId', 'selectedParticipants', 'sourceGroupId', 'sourceGroupInfo']);
    }

    // --- Destination ---

    public function selectDestinationProgram(?int $id): void
    {
        $this->destinationProgramId = $id;
        $this->destinationGroupId = null;
        $this->destinationGroupInfo = null;
    }

    public function selectDestinationGroup(int $id): void
    {
        $group = TrainingGroup::with('program')->findOrFail($id);
        $this->destinationGroupId = $id;
        $this->destinationGroupInfo = [
            'id' => $group->id,
            'name_ar' => $group->name_ar,
            'program_name' => $group->program?->name_ar ?? '-',
            'current_count' => $group->current_count,
            'max_capacity' => $group->max_capacity,
        ];
    }

    // --- Navigation ---

    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;
        }
    }

    // --- Confirm ---

    public function confirm(): void
    {
        // confirm() is a wire:click like any other — reachable without walking
        // the steps. Re-run every step's rules where the work actually happens.
        $this->validate(
            $this->rulesForStep(1) + $this->rulesForStep(2) + $this->rulesForStep(3)
        );

        $enrollmentService = app(EnrollmentService::class);

        // Both lookups are branch-scoped: the destination group and each
        // enrolment must belong to the branch the user is standing in.
        $toGroup = TrainingGroup::findOrFail($this->destinationGroupId);
        $actor = auth()->user();

        $this->transferResults = [];
        $this->successCount = 0;
        $this->failCount = 0;

        foreach ($this->selectedParticipants as $enrollmentId => $info) {
            try {
                $enrollment = Enrollment::findOrFail($enrollmentId);
                $enrollmentService->transfer($enrollment, $toGroup, $actor);
                $this->transferResults[] = [
                    'name' => $info['name'],
                    'status' => 'success',
                    'message' => __('تم النقل بنجاح'),
                ];
                $this->successCount++;
            } catch (DomainException $e) {
                $this->transferResults[] = [
                    'name' => $info['name'],
                    'status' => 'error',
                    'message' => $e->getMessage(),
                ];
                $this->failCount++;
            } catch (\Throwable $e) {
                $this->transferResults[] = [
                    'name' => $info['name'],
                    'status' => 'error',
                    'message' => __('خطأ غير متوقع'),
                ];
                $this->failCount++;
            }
        }

        $this->completed = true;
    }

    private function rulesForStep(int $step): array
    {
        return match ($step) {
            1 => [
                'selectedParticipants' => 'required|array|min:1',
            ],
            2 => [
                // `exists:` is a raw query and never sees the branch scope.
                'destinationGroupId' => [
                    'required', 'integer', $this->inActiveBranch('training_groups'),
                ],
            ],
            3 => [
                'reason' => 'required|string|min:3|max:500',
            ],
            default => [],
        };
    }

    /**
     * An `exists` rule pinned to the active branch.
     *
     * Null branch means all-branches mode, where the user is entitled to every
     * branch and there is nothing to narrow to.
     */
    private function inActiveBranch(string $table): \Illuminate\Validation\Rules\Exists
    {
        $branchId = $this->getActiveBranchId();
        $rule = Rule::exists($table, 'id');

        return $branchId === null ? $rule : $rule->where('branch_id', $branchId);
    }

    public function messages(): array
    {
        return [
            'selectedParticipants.required' => 'يجب اختيار مشترك واحد على الأقل',
            'selectedParticipants.min' => 'يجب اختيار مشترك واحد على الأقل',
            'destinationGroupId.required' => 'يجب اختيار المجموعة الوجهة',
            'reason.required' => 'يجب كتابة سبب النقل',
            'reason.min' => 'سبب النقل قصير جدًا',
        ];
    }

    public function render()
    {
        // Every query below is filtered by the branch global scope on
        // Participant / TrainingGroup / Enrollment / TrainingProgram. The old
        // ->when($branchId, ...) clauses are gone: they were redundant on the
        // first query and outright dead on the second, where $branchId was
        // assigned inside the single-mode branch and never existed in bulk mode.

        // Search results for single mode
        $searchResults = collect();
        if ($this->mode === 'single' && strlen($this->participantSearch) >= 2 && !$this->singleParticipantInfo) {
            $searchResults = Participant::with('person')
                // One closure around both predicates. Un-grouped, the trailing
                // orWhere escaped every condition before it — the branch scope
                // and the academy scope included — so a search by participant
                // number returned other branches' and other tenants' players.
                ->where(function ($q) {
                    $q->whereHas('person', function ($pq) {
                        $pq->where('name_ar', 'ilike', "%{$this->participantSearch}%")
                           ->orWhere('name', 'ilike', "%{$this->participantSearch}%")
                           ->orWhere('phone', 'ilike', "%{$this->participantSearch}%");
                    })->orWhere('participant_number', 'ilike', "%{$this->participantSearch}%");
                })
                ->limit(10)
                ->get();
        }

        // Source groups for bulk mode
        $sourceGroups = collect();
        if ($this->mode === 'bulk' && !$this->sourceGroupId) {
            $sourceGroups = TrainingGroup::whereIn('status', ['active', 'forming'])
                ->where('current_count', '>', 0)
                ->with('program')
                ->orderBy('name_ar')
                ->get();
        }

        // Source group enrollments (for bulk selection)
        $sourceEnrollments = collect();
        if ($this->mode === 'bulk' && $this->sourceGroupId) {
            $query = Enrollment::where('training_group_id', $this->sourceGroupId)
                ->where('status', 'active')
                ->with('participant.person');

            if (strlen($this->participantSearch) >= 2) {
                $search = $this->participantSearch;
                $query->whereHas('participant.person', function ($q) use ($search) {
                    $q->where('name_ar', 'ilike', "%{$search}%")
                      ->orWhere('name', 'ilike', "%{$search}%")
                      ->orWhere('phone', 'ilike', "%{$search}%");
                });
            }

            $sourceEnrollments = $query->get();
        }

        // Destination options
        $availablePrograms = collect();
        $availableGroups = collect();
        if ($this->currentStep >= 2 && $this->sourceGroupId) {
            $availablePrograms = TrainingProgram::where('status', 'active')
                ->orderBy('name_ar')
                ->get();

            $targetProgramId = $this->destinationProgramId ?? ($this->sourceGroupInfo['program_id'] ?? null);

            if ($targetProgramId) {
                $availableGroups = TrainingGroup::where('training_program_id', $targetProgramId)
                    ->where('id', '!=', $this->sourceGroupId)
                    ->whereIn('status', ['forming', 'active'])
                    ->with('program')
                    ->orderBy('name_ar')
                    ->get();
            }
        }

        return view('livewire.enrollments.transfer-participant-wizard', [
            'searchResults' => $searchResults,
            'sourceGroups' => $sourceGroups,
            'sourceEnrollments' => $sourceEnrollments,
            'availablePrograms' => $availablePrograms,
            'availableGroups' => $availableGroups,
        ]);
    }
}
