<?php

namespace App\Domain\Participant\Services;

use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Participant\Events\ParticipantFrozen;
use App\Domain\Participant\Events\ParticipantSuspended;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Events\ParticipantRegistered;
use App\Domain\Training\Events\ParticipantStatusChanged;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class ParticipantService
{
    private const VALID_TRANSITIONS = [
        'registered' => ['active', 'withdrawn'],
        'active' => ['frozen', 'suspended', 'inactive', 'graduated', 'transferred', 'withdrawn', 'blacklisted'],
        'frozen' => ['active', 'withdrawn', 'inactive'],
        'suspended' => ['active', 'withdrawn', 'blacklisted'],
        'inactive' => ['active', 'withdrawn'],
        'graduated' => ['active'],
        'withdrawn' => ['active'],
        'transferred' => [],
        'blacklisted' => [],
    ];

    public function __construct(
        private readonly PersonService $personService,
    ) {}

    public function register(array $data, User $actor): Participant
    {
        return DB::transaction(function () use ($data, $actor) {
            // Create or find the person
            $person = isset($data['person_id'])
                ? Person::findOrFail($data['person_id'])
                : $this->personService->create($data['person'], $actor);

            // Check if this person is already a participant in this academy
            $existing = Participant::withoutGlobalScopes()
                ->where('academy_id', app('current_academy')->id)
                ->where('person_id', $person->id)
                ->first();

            if ($existing) {
                throw new DomainException('هذا الشخص مسجل بالفعل كمشترك');
            }

            // Generate participant number
            $number = $this->generateParticipantNumber();

            $participant = Participant::create([
                'person_id' => $person->id,
                'branch_id' => $data['branch_id'] ?? null,
                'participant_number' => $number,
                'registration_date' => $data['registration_date'] ?? now()->toDateString(),
                'registration_source' => $data['registration_source'] ?? 'walk_in',
                'referred_by_id' => $data['referred_by_id'] ?? null,
                'primary_guardian_id' => $data['primary_guardian_id'] ?? null,
                'primary_activity_id' => $data['primary_activity_id'] ?? null,
                'skill_level' => $data['skill_level'] ?? null,
                'medical_clearance' => $data['medical_clearance'] ?? false,
                'medical_clearance_expires' => $data['medical_clearance_expires'] ?? null,
                'jersey_number' => $data['jersey_number'] ?? null,
                'jersey_size' => $data['jersey_size'] ?? null,
                'shoe_size' => $data['shoe_size'] ?? null,
                'dominant_hand' => $data['dominant_hand'] ?? null,
                'dominant_foot' => $data['dominant_foot'] ?? null,
                'height_cm' => $data['height_cm'] ?? null,
                'weight_kg' => $data['weight_kg'] ?? null,
                'status' => 'registered',
                'status_changed_at' => now(),
                'membership_type' => $data['membership_type'] ?? null,
                'membership_id' => $data['membership_id'] ?? null,
                'is_free' => $data['is_free'] ?? false,
                'notes' => $data['notes'] ?? null,
                'metadata' => $data['metadata'] ?? [],
                'created_by' => $actor->id,
                'updated_by' => $actor->id,
            ]);

            // A member with no branch is invisible everywhere: `participants` is
            // strict, so branch_id NULL means they appear on no branch's roster,
            // no branch KPI and no branch-filtered dropdown, while still
            // counting in the academy totals. The creating hook stamps the
            // branch on staff requests; a public self-registration has no staff
            // member and no branch to stamp, and that is the path that produced
            // the orphans. Fall back to the academy's main branch — the same
            // rule the branch backfill migration used.
            if (! $participant->branch_id) {
                $fallbackBranchId = $this->fallbackBranchId();

                if ($fallbackBranchId) {
                    $participant->forceFill(['branch_id' => $fallbackBranchId])->save();
                }
            }

            // Attach guardians if provided
            if (!empty($data['guardians'])) {
                foreach ($data['guardians'] as $guardianData) {
                    $participant->guardians()->attach($guardianData['guardian_id'], [
                        'relationship_type' => $guardianData['relationship_type'],
                        'is_primary' => $guardianData['is_primary'] ?? false,
                        'is_emergency_contact' => $guardianData['is_emergency_contact'] ?? false,
                        'can_pickup' => $guardianData['can_pickup'] ?? true,
                        'receives_notifications' => $guardianData['receives_notifications'] ?? true,
                        'can_authorize_payment' => $guardianData['can_authorize_payment'] ?? false,
                    ]);
                }
            }

            ParticipantRegistered::dispatch($participant, $actor);

            return $participant;
        });
    }

    public function update(Participant $participant, array $data, User $actor): Participant
    {
        return DB::transaction(function () use ($participant, $data, $actor) {
            $participant->update(array_merge($data, ['updated_by' => $actor->id]));
            return $participant->fresh();
        });
    }

    public function changeStatus(Participant $participant, string $newStatus, string $reason, ?User $actor = null): Participant
    {
        return DB::transaction(function () use ($participant, $newStatus, $reason, $actor) {
            $currentStatus = $participant->status->value ?? $participant->status;
            $allowed = self::VALID_TRANSITIONS[$currentStatus] ?? [];

            if (!in_array($newStatus, $allowed)) {
                throw new DomainException(
                    "لا يمكن تغيير الحالة من '{$currentStatus}' إلى '{$newStatus}'"
                );
            }

            // Blacklist requires super_admin
            if ($newStatus === 'blacklisted' && !$actor->is_super_admin) {
                throw new DomainException('فقط مدير النظام يمكنه إضافة مشترك للقائمة السوداء');
            }

            $participant->update([
                'status' => $newStatus,
                'status_changed_at' => now(),
                'status_reason' => $reason,
                'updated_by' => $actor?->id,
            ]);

            $fresh = $participant->fresh();

            if ($actor) {
                ParticipantStatusChanged::dispatch($fresh, $currentStatus, $newStatus, $actor);

                if ($newStatus === 'suspended') {
                    ParticipantSuspended::dispatch($fresh, $reason, $actor);
                } elseif ($newStatus === 'frozen') {
                    ParticipantFrozen::dispatch($fresh, $reason, $actor);
                }
            }

            return $fresh;
        });
    }

    public function freeze(Participant $participant, string $reason, User $actor): Participant
    {
        return $this->changeStatus($participant, 'frozen', $reason, $actor);
    }

    public function suspend(Participant $participant, string $reason, User $actor): Participant
    {
        return $this->changeStatus($participant, 'suspended', $reason, $actor);
    }

    public function activate(Participant $participant, string $reason, User $actor): Participant
    {
        return $this->changeStatus($participant, 'active', $reason, $actor);
    }

    public function withdraw(Participant $participant, string $reason, User $actor): Participant
    {
        return $this->changeStatus($participant, 'withdrawn', $reason, $actor);
    }

    public function graduate(Participant $participant, string $reason, User $actor): Participant
    {
        return $this->changeStatus($participant, 'graduated', $reason, $actor);
    }

    public function delete(Participant $participant, User $actor): void
    {
        DB::transaction(function () use ($participant, $actor) {
            if ($participant->status->value === 'active') {
                throw new DomainException('لا يمكن حذف مشترك نشط. يجب سحبه أولاً.');
            }
            $participant->update(['updated_by' => $actor->id]);
            $participant->delete();
        });
    }

    /**
     * The branch to file a member against when nothing else resolved one.
     *
     * `is_main DESC, id ASC` rather than `is_main` alone: several tenants have
     * is_main false on every branch, and without the id tiebreaker Postgres is
     * free to return a different one each time.
     */
    private function fallbackBranchId(): ?int
    {
        return Branch::query()
            ->where('is_active', true)
            ->orderByDesc('is_main')
            ->orderBy('id')
            ->value('id');
    }

    private function generateParticipantNumber(): string
    {
        $academyId = app('current_academy')->id;
        $prefix = 'P';
        $year = now()->format('y');

        $lastNumber = Participant::withoutGlobalScopes()
            ->where('academy_id', $academyId)
            ->where('participant_number', 'like', "{$prefix}{$year}%")
            ->orderByDesc('participant_number')
            ->value('participant_number');

        if ($lastNumber) {
            $sequence = (int) substr($lastNumber, strlen("{$prefix}{$year}")) + 1;
        } else {
            $sequence = 1;
        }

        return $prefix . $year . str_pad($sequence, 4, '0', STR_PAD_LEFT);
    }
}
