<?php

namespace App\Domain\Identity\Services;

use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Services\GuardianResolver;
use App\Domain\Participant\Models\Participant;
use App\Domain\Scheduling\Models\Assignment;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSession;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class PermissionService
{
    private array $cachedGroupIds = [];
    private array $cachedChildIds = [];

    public function can(User $user, string $permission, ?Model $record = null): bool
    {
        if ($user->is_super_admin) {
            return true;
        }

        $role = $user->primaryRole;
        if (!$role) {
            $role = $user->roles->first();
            if (!$role) {
                return false;
            }
            if (!$role->relationLoaded('permissions')) {
                $role->load('permissions');
            }
        }

        $rolePermission = $role->permissions->firstWhere('name', $permission);
        if (!$rolePermission) {
            return false;
        }

        if ($record === null) {
            return true;
        }

        $scope = $rolePermission->pivot->scope;

        return match ($scope) {
            'all' => true,
            'academy' => true,
            'branch' => $this->checkBranchScope($user, $record),
            'own' => $this->checkOwnScope($user, $record),
            'own_groups' => $this->checkOwnGroupsScope($user, $record),
            'own_children' => $this->checkOwnChildrenScope($user, $record),
            default => false,
        };
    }

    public function applyScope(Builder $query, User $user, string $permission): Builder
    {
        if ($user->is_super_admin) {
            return $query;
        }

        $role = $user->primaryRole;
        if (!$role) {
            $role = $user->roles->first();
            if (!$role) {
                return $query->whereRaw('1 = 0');
            }
            if (!$role->relationLoaded('permissions')) {
                $role->load('permissions');
            }
        }

        $rolePermission = $role->permissions->firstWhere('name', $permission);
        if (!$rolePermission) {
            return $query->whereRaw('1 = 0');
        }

        $scope = $rolePermission->pivot->scope;
        $table = $query->getModel()->getTable();

        return match ($scope) {
            'all', 'academy' => $query,
            'branch' => $query->where("{$table}.branch_id", $user->branch_id),
            'own' => $query->where("{$table}.created_by", $user->id),
            'own_groups' => $this->applyOwnGroupsScope($query, $user, $table),
            'own_children' => $this->applyOwnChildrenScope($query, $user, $table),
            default => $query,
        };
    }

    // --- Branch scope ---

    private function checkBranchScope(User $user, Model $record): bool
    {
        if (!isset($record->branch_id)) {
            return true;
        }
        return $record->branch_id === $user->branch_id;
    }

    // --- Own scope ---

    private function checkOwnScope(User $user, Model $record): bool
    {
        if (!isset($record->created_by)) {
            return true;
        }
        return $record->created_by === $user->id;
    }

    // --- Own Groups scope (trainers) ---

    private function getAssignedGroupIds(User $user): array
    {
        if (isset($this->cachedGroupIds[$user->id])) {
            return $this->cachedGroupIds[$user->id];
        }

        // Groups via Assignment records
        $assignmentIds = Assignment::active()
            ->forUser($user->id)
            ->where('assignable_type', TrainingGroup::class)
            ->pluck('assignable_id')
            ->toArray();

        // Groups where user is head_trainer_id
        $headTrainerIds = TrainingGroup::where('head_trainer_id', $user->id)
            ->pluck('id')
            ->toArray();

        $ids = array_unique(array_merge($assignmentIds, $headTrainerIds));

        $this->cachedGroupIds[$user->id] = $ids;
        return $ids;
    }

    private function checkOwnGroupsScope(User $user, Model $record): bool
    {
        $groupIds = $this->getAssignedGroupIds($user);

        if (empty($groupIds)) {
            return false;
        }

        if ($record instanceof TrainingGroup) {
            return in_array($record->id, $groupIds);
        }

        if ($record instanceof TrainingSession) {
            return in_array($record->training_group_id, $groupIds);
        }

        if (isset($record->training_group_id)) {
            return in_array($record->training_group_id, $groupIds);
        }

        if ($record instanceof Participant) {
            return Enrollment::where('participant_id', $record->id)
                ->whereIn('training_group_id', $groupIds)
                ->whereIn('status', ['active', 'pending'])
                ->exists();
        }

        if ($record instanceof AttendanceRecord) {
            $session = $record->session;
            return $session && in_array($session->training_group_id, $groupIds);
        }

        return false;
    }

    private function applyOwnGroupsScope(Builder $query, User $user, string $table): Builder
    {
        $groupIds = $this->getAssignedGroupIds($user);

        if (empty($groupIds)) {
            return $query->whereRaw('1 = 0');
        }

        return match ($table) {
            'training_groups' => $query->whereIn("{$table}.id", $groupIds),
            'training_sessions' => $query->whereIn("{$table}.training_group_id", $groupIds),
            'enrollments' => $query->whereIn("{$table}.training_group_id", $groupIds),
            'evaluations' => $query->whereIn("{$table}.training_group_id", $groupIds),
            'training_programs' => $query->whereIn("{$table}.id", function ($sub) use ($groupIds) {
                $sub->select('training_program_id')
                    ->from('training_groups')
                    ->whereIn('id', $groupIds);
            }),
            'participants' => $query->whereIn("{$table}.id", function ($sub) use ($groupIds) {
                $sub->select('participant_id')
                    ->from('enrollments')
                    ->whereIn('training_group_id', $groupIds)
                    ->whereIn('status', ['active', 'pending']);
            }),
            'attendance_records' => $query->whereIn("{$table}.training_session_id", function ($sub) use ($groupIds) {
                $sub->select('id')
                    ->from('training_sessions')
                    ->whereIn('training_group_id', $groupIds);
            }),
            'invoices' => $query->whereIn("{$table}.billable_id", function ($sub) use ($groupIds) {
                $sub->select('participant_id')
                    ->from('enrollments')
                    ->whereIn('training_group_id', $groupIds)
                    ->whereIn('status', ['active', 'pending']);
            })->where("{$table}.billable_type", Participant::class),
            default => $query->whereIn("{$table}.training_group_id", $groupIds),
        };
    }

    // --- Own Children scope (parents/guardians) ---

    private function getChildParticipantIds(User $user): array
    {
        if (isset($this->cachedChildIds[$user->id])) {
            return $this->cachedChildIds[$user->id];
        }

        // This was `->where('person_id', …)->orWhere('user_id', …)->first()`.
        // Two bugs in one line: the orWhere let the person_id branch escape the
        // tenant global scope, and ->first() on a non-unique column meant a
        // guardian with two rows saw one set of children and was 403'd on the
        // rest. GuardianResolver returns the union of every row, and includes
        // the account's own participant so a player is not permanently empty.
        $ids = app(GuardianResolver::class)->participantIdsFor($user);

        $this->cachedChildIds[$user->id] = $ids;

        return $ids;
    }

    private function checkOwnChildrenScope(User $user, Model $record): bool
    {
        $childIds = $this->getChildParticipantIds($user);

        if (empty($childIds)) {
            return false;
        }

        if ($record instanceof Participant) {
            return in_array($record->id, $childIds);
        }

        if (isset($record->participant_id)) {
            return in_array($record->participant_id, $childIds);
        }

        if ($record instanceof Invoice && $record->billable_type === Participant::class) {
            return in_array($record->billable_id, $childIds);
        }

        if ($record instanceof AttendanceRecord && $record->subject_type === Participant::class) {
            return in_array($record->subject_id, $childIds);
        }

        return false;
    }

    private function applyOwnChildrenScope(Builder $query, User $user, string $table): Builder
    {
        $childIds = $this->getChildParticipantIds($user);

        if (empty($childIds)) {
            return $query->whereRaw('1 = 0');
        }

        return match ($table) {
            'participants' => $query->whereIn("{$table}.id", $childIds),
            'enrollments' => $query->whereIn("{$table}.participant_id", $childIds),
            'evaluations' => $query->whereIn("{$table}.participant_id", $childIds),
            'invoices' => $query->where("{$table}.billable_type", Participant::class)
                ->whereIn("{$table}.billable_id", $childIds),
            'payments' => $query->whereIn("{$table}.invoice_id", function ($sub) use ($childIds) {
                $sub->select('id')
                    ->from('invoices')
                    ->where('billable_type', Participant::class)
                    ->whereIn('billable_id', $childIds);
            }),
            'attendance_records' => $query->where("{$table}.subject_type", Participant::class)
                ->whereIn("{$table}.subject_id", $childIds),
            default => $query->whereIn("{$table}.participant_id", $childIds),
        };
    }
}
