<?php

namespace App\Services;

use App\Enums\UserRole;
use App\Exceptions\DomainException;
use App\Models\Appointment;
use App\Models\AssistantAssignment;
use App\Models\DoctorLocationAssignment;
use App\Models\Patient;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;

class AccessService
{
    public function isAdmin(User $user): bool
    {
        return $user->role === UserRole::ADMIN;
    }

    public function assertOrganization(User $user, object $record): void
    {
        if ((int) ($record->organization_id ?? 0) !== (int) $user->organization_id) {
            throw new DomainException('FORBIDDEN', 'This record belongs to another organization.', 403);
        }
    }

    public function assistantAssignment(
        User $user,
        int $doctorId,
        int $locationId,
        ?string $requiredPermission = null
    ): ?AssistantAssignment {
        if ($user->role !== UserRole::ASSISTANT || ! $user->assistant) {
            return null;
        }

        $query = AssistantAssignment::query()
            ->where('organization_id', $user->organization_id)
            ->where('assistant_id', $user->assistant->id)
            ->where('doctor_id', $doctorId)
            ->where('location_id', $locationId)
            ->where('is_active', true);

        if ($requiredPermission !== null) {
            $query->where($requiredPermission, true);
        }

        return $query->first();
    }

    public function canUseDoctorLocation(User $user, int $doctorId, int $locationId, ?string $assistantPermission = null): bool
    {
        if ($this->isAdmin($user)) {
            return true;
        }

        if ($user->role === UserRole::DOCTOR) {
            if ((int) $user->doctor?->id !== $doctorId) {
                return false;
            }

            return DoctorLocationAssignment::query()
                ->where('organization_id', $user->organization_id)
                ->where('doctor_id', $doctorId)
                ->where('location_id', $locationId)
                ->where('is_active', true)
                ->exists();
        }

        if ($user->role === UserRole::ASSISTANT) {
            return (bool) $this->assistantAssignment($user, $doctorId, $locationId, $assistantPermission);
        }

        return false;
    }

    public function canCreatePatient(User $user): bool
    {
        if ($this->isAdmin($user)) {
            return true;
        }

        if ($user->role === UserRole::ASSISTANT && $user->assistant) {
            return AssistantAssignment::query()
                ->where('assistant_id', $user->assistant->id)
                ->where('organization_id', $user->organization_id)
                ->where('is_active', true)
                ->where('can_create_patient', true)
                ->exists();
        }

        return false;
    }

    public function canEditPatient(User $user, Patient $patient): bool
    {
        $this->assertOrganization($user, $patient);

        if ($this->isAdmin($user)) {
            return true;
        }

        if ($user->role === UserRole::PATIENT) {
            return (int) $patient->user_id === (int) $user->id;
        }

        if ($user->role === UserRole::ASSISTANT && $user->assistant) {
            if ((int) $patient->created_by === (int) $user->id) {
                return AssistantAssignment::query()
                    ->where('organization_id', $user->organization_id)
                    ->where('assistant_id', $user->assistant->id)
                    ->where('is_active', true)
                    ->where('can_edit_patient', true)
                    ->exists();
            }

            return Appointment::query()
                ->where('patient_id', $patient->id)
                ->whereExists(function ($sub) use ($user) {
                    $sub->selectRaw('1')
                        ->from('assistant_assignments as aa')
                        ->whereColumn('aa.doctor_id', 'appointments.doctor_id')
                        ->whereColumn('aa.location_id', 'appointments.location_id')
                        ->where('aa.assistant_id', $user->assistant->id)
                        ->where('aa.is_active', true)
                        ->where('aa.can_edit_patient', true);
                })
                ->exists();
        }

        if ($user->role === UserRole::DOCTOR && $user->doctor) {
            return Appointment::query()
                ->where('patient_id', $patient->id)
                ->where('doctor_id', $user->doctor->id)
                ->exists();
        }

        return false;
    }

    public function canViewPatient(User $user, Patient $patient): bool
    {
        $this->assertOrganization($user, $patient);

        if ($this->isAdmin($user)) {
            return true;
        }

        if ($user->role === UserRole::PATIENT) {
            return (int) $patient->user_id === (int) $user->id;
        }

        if ($user->role === UserRole::DOCTOR && $user->doctor) {
            return Appointment::query()
                ->where('patient_id', $patient->id)
                ->where('doctor_id', $user->doctor->id)
                ->exists();
        }

        if ($user->role === UserRole::ASSISTANT && $user->assistant) {
            if ((int) $patient->created_by === (int) $user->id) {
                return true;
            }

            return Appointment::query()
                ->where('patient_id', $patient->id)
                ->whereExists(function ($sub) use ($user) {
                    $sub->selectRaw('1')
                        ->from('assistant_assignments as aa')
                        ->whereColumn('aa.doctor_id', 'appointments.doctor_id')
                        ->whereColumn('aa.location_id', 'appointments.location_id')
                        ->where('aa.assistant_id', $user->assistant->id)
                        ->where('aa.is_active', true);
                })
                ->exists();
        }

        return false;
    }

    public function scopePatients(Builder $query, User $user): Builder
    {
        $query->where('organization_id', $user->organization_id);

        if ($this->isAdmin($user)) {
            return $query;
        }

        if ($user->role === UserRole::PATIENT) {
            return $query->where('user_id', $user->id);
        }

        if ($user->role === UserRole::DOCTOR && $user->doctor) {
            return $query->whereHas('appointments', fn (Builder $q) => $q->where('doctor_id', $user->doctor->id));
        }

        if ($user->role === UserRole::ASSISTANT && $user->assistant) {
            return $query->where(function (Builder $q) use ($user) {
                $q->where('created_by', $user->id)
                    ->orWhereHas('appointments', function (Builder $appointments) use ($user) {
                        $appointments->whereExists(function ($sub) use ($user) {
                            $sub->selectRaw('1')
                                ->from('assistant_assignments as aa')
                                ->whereColumn('aa.doctor_id', 'appointments.doctor_id')
                                ->whereColumn('aa.location_id', 'appointments.location_id')
                                ->where('aa.assistant_id', $user->assistant->id)
                                ->where('aa.is_active', true);
                        });
                    });
            });
        }

        return $query->whereRaw('1 = 0');
    }

    public function scopeAppointments(Builder $query, User $user): Builder
    {
        $query->where('appointments.organization_id', $user->organization_id);

        if ($this->isAdmin($user)) {
            return $query;
        }

        if ($user->role === UserRole::DOCTOR && $user->doctor) {
            return $query->where('doctor_id', $user->doctor->id);
        }

        if ($user->role === UserRole::ASSISTANT && $user->assistant) {
            return $query->whereExists(function ($sub) use ($user) {
                $sub->selectRaw('1')
                    ->from('assistant_assignments as aa')
                    ->whereColumn('aa.doctor_id', 'appointments.doctor_id')
                    ->whereColumn('aa.location_id', 'appointments.location_id')
                    ->where('aa.assistant_id', $user->assistant->id)
                    ->where('aa.is_active', true);
            });
        }

        if ($user->role === UserRole::PATIENT) {
            return $query->whereHas('patient', fn (Builder $q) => $q->where('user_id', $user->id));
        }

        return $query->whereRaw('1 = 0');
    }

    public function scopeQueue(Builder $query, User $user): Builder
    {
        $query->where('queue_entries.organization_id', $user->organization_id);

        if ($this->isAdmin($user)) {
            return $query;
        }

        if ($user->role === UserRole::DOCTOR && $user->doctor) {
            return $query->where('doctor_id', $user->doctor->id);
        }

        if ($user->role === UserRole::ASSISTANT && $user->assistant) {
            return $query->whereExists(function ($sub) use ($user) {
                $sub->selectRaw('1')
                    ->from('assistant_assignments as aa')
                    ->whereColumn('aa.doctor_id', 'queue_entries.doctor_id')
                    ->whereColumn('aa.location_id', 'queue_entries.location_id')
                    ->where('aa.assistant_id', $user->assistant->id)
                    ->where('aa.is_active', true);
            });
        }

        return $query->whereRaw('1 = 0');
    }

    public function canCollectPayment(User $user, Appointment $appointment): bool
    {
        if ($this->isAdmin($user)) {
            return true;
        }

        return $user->role === UserRole::ASSISTANT
            && (bool) $this->assistantAssignment(
                $user,
                $appointment->doctor_id,
                $appointment->location_id,
                'can_collect_payment'
            );
    }

    public function canCheckIn(User $user, Appointment $appointment): bool
    {
        return $this->isAdmin($user)
            || $this->canUseDoctorLocation($user, $appointment->doctor_id, $appointment->location_id, 'can_check_in');
    }

    public function canReschedule(User $user, Appointment $appointment): bool
    {
        if ($this->isAdmin($user)) {
            return true;
        }

        if ($user->role === UserRole::PATIENT) {
            return (int) $appointment->patient?->user_id === (int) $user->id;
        }

        return $this->canUseDoctorLocation($user, $appointment->doctor_id, $appointment->location_id, 'can_reschedule');
    }

    public function canCancel(User $user, Appointment $appointment): bool
    {
        if ($this->isAdmin($user)) {
            return true;
        }

        if ($user->role === UserRole::PATIENT) {
            return (int) $appointment->patient?->user_id === (int) $user->id;
        }

        return $this->canUseDoctorLocation($user, $appointment->doctor_id, $appointment->location_id, 'can_cancel');
    }
}
