<?php

namespace App\Services\School;

use App\Models\DisciplinaryCase;
use App\Models\DisciplinaryCaseAction;
use App\Models\DisciplinaryCaseAttachment;
use App\Models\DisciplinaryRecord;
use App\Models\Guardian;
use App\Models\SchoolClass;
use App\Models\Staff;
use App\Models\Student;
use App\Models\User;
use App\Notifications\DisciplinaryCaseUpdatedNotification;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use InvalidArgumentException;

class DisciplinaryCaseService
{
    /** @var list<string> */
    public const STAGES = [
        'reported',
        'class_teacher_investigation',
        'headmaster_review',
        'hearing',
        'decision',
        'closed',
        'dismissed',
    ];

    /** @var list<string> */
    public const OUTCOMES = [
        'no_action',
        'verbal_warning',
        'written_warning',
        'detention',
        'community_service',
        'punishment',
        'suspension',
        'expulsion',
        'dismissal',
    ];

    /** @var list<string> */
    public const SEVERITIES = ['low', 'medium', 'high', 'critical'];

    /** @var list<string> */
    public const CATEGORIES = [
        'bullying',
        'fighting',
        'theft',
        'disrespect',
        'truancy',
        'cheating',
        'substance_abuse',
        'property_damage',
        'harassment',
        'other',
    ];

    /**
     * @param  array<string, mixed>  $data
     */
    public function submitReport(User $reporter, array $data): DisciplinaryCase
    {
        $student = Student::query()->findOrFail($reporter->student_id);
        $subject = Student::query()->findOrFail($data['subject_student_id']);

        $class = $this->resolveStudentClass($student);
        $headmaster = $this->resolveHeadmasterUser();

        return DB::transaction(function () use ($reporter, $student, $subject, $data, $class, $headmaster) {
            $isAnonymous = ! empty($data['is_anonymous']);

            $case = DisciplinaryCase::query()->create([
                'case_number' => $this->generateCaseNumber(),
                'reported_by_student_id' => $isAnonymous ? null : $student->id,
                'subject_student_id' => $subject->id,
                'class_id' => $class?->id,
                'class_teacher_id' => $class?->class_teacher_id,
                'headmaster_user_id' => $headmaster?->id,
                'category' => $data['category'] ?? null,
                'title' => $data['title'],
                'description' => $data['description'],
                'severity' => $data['severity'] ?? 'medium',
                'is_anonymous' => $isAnonymous,
                'stage' => 'reported',
                'created_by' => $reporter->id,
            ]);

            $this->logAction($case, null, 'reported', 'Case reported', $reporter);
            $this->notifyStakeholders($case, 'submitted');

            return $case->fresh([
                'reportedByStudent',
                'subjectStudent',
                'schoolClass',
                'classTeacher',
                'headmasterUser',
            ]);
        });
    }

    public function advance(DisciplinaryCase $case, User $user, string $toStage, ?string $notes = null, ?array $outcomeData = null): DisciplinaryCase
    {
        if ($case->isClosed()) {
            throw new InvalidArgumentException('This case is already closed.');
        }

        if (! $this->canAdvance($user, $case, $toStage)) {
            throw new InvalidArgumentException('You are not allowed to advance this case to that stage.');
        }

        return DB::transaction(function () use ($case, $user, $toStage, $notes, $outcomeData) {
            $fromStage = $case->stage;
            $updates = ['stage' => $toStage];

            if ($outcomeData !== null) {
                $updates = array_merge($updates, array_filter([
                    'outcome' => $outcomeData['outcome'] ?? null,
                    'outcome_details' => $outcomeData['outcome_details'] ?? null,
                    'parent_notified' => $outcomeData['parent_notified'] ?? $case->parent_notified,
                    'suspension_starts_on' => $outcomeData['suspension_starts_on'] ?? null,
                    'suspension_ends_on' => $outcomeData['suspension_ends_on'] ?? null,
                ], fn ($value) => $value !== null));
            }

            if (in_array($toStage, ['closed', 'dismissed'], true)) {
                $updates['finalized_by'] = $user->id;
                $updates['finalized_at'] = now();
            }

            $case->update($updates);

            $actionLabel = match ($toStage) {
                'class_teacher_investigation' => 'Class teacher investigation started',
                'headmaster_review' => 'Escalated to headmaster',
                'hearing' => 'Disciplinary hearing scheduled',
                'decision' => 'Decision recorded',
                'closed' => 'Case closed with outcome',
                'dismissed' => 'Case dismissed',
                default => 'Stage updated',
            };

            $this->logAction($case, $fromStage, $toStage, $actionLabel, $user, $notes);

            if ($toStage === 'closed' && $case->outcome) {
                $this->createDisciplinaryRecord($case, $user);
                $this->applyStudentStatus($case);
                $this->notifyParentsOfOutcome($case);
            }

            $this->notifyStakeholders($case->fresh(), 'updated');

            return $case->fresh([
                'reportedByStudent',
                'subjectStudent',
                'schoolClass',
                'classTeacher',
                'headmasterUser',
                'actions.performedBy',
            ]);
        });
    }

    public function canView(User $user, DisciplinaryCase $case): bool
    {
        if ($user->hasAnyRole(['Super Admin', 'Administrator', 'Registrar', 'Headmaster'])) {
            return true;
        }

        if ($user->hasRole('Teacher') && $user->staff_id && $case->class_teacher_id === $user->staff_id) {
            return true;
        }

        if ($user->hasRole('Department Head') && $user->staff_id) {
            return $this->caseInDepartment($user->staff_id, $case);
        }

        if ($user->hasRole('Parent')) {
            return $this->parentCanView($user, $case);
        }

        if ($user->student_id) {
            return in_array($user->student_id, [
                $case->reported_by_student_id,
                $case->subject_student_id,
            ], true) || $case->created_by === $user->id;
        }

        return false;
    }

    public function canAdvance(User $user, DisciplinaryCase $case, string $toStage): bool
    {
        if (! in_array($toStage, self::STAGES, true)) {
            return false;
        }

        if ($user->hasAnyRole(['Super Admin', 'Administrator', 'Headmaster'])) {
            return $this->isValidTransition($case->stage, $toStage, 'admin');
        }

        if ($user->hasRole('Teacher') && $user->staff_id && $case->class_teacher_id === $user->staff_id) {
            return $this->isValidTransition($case->stage, $toStage, 'class_teacher');
        }

        return false;
    }

    /**
     * @return list<string>
     */
    public function nextStagesFor(User $user, DisciplinaryCase $case): array
    {
        return collect(self::STAGES)
            ->filter(fn (string $stage) => $this->canAdvance($user, $case, $stage))
            ->values()
            ->all();
    }

    public function stageLabel(string $stage): string
    {
        return config("southend.disciplinary_stages.{$stage}", ucfirst(str_replace('_', ' ', $stage)));
    }

    public function outcomeLabel(?string $outcome): ?string
    {
        if (! $outcome) {
            return null;
        }

        return config("southend.disciplinary_outcomes.{$outcome}", ucfirst(str_replace('_', ' ', $outcome)));
    }

    /**
     * @param  list<UploadedFile>  $files
     */
    public function attachEvidence(DisciplinaryCase $case, User $user, array $files): void
    {
        foreach ($files as $file) {
            $path = $file->store('case-evidence/'.$case->id, 'public');
            DisciplinaryCaseAttachment::query()->create([
                'disciplinary_case_id' => $case->id,
                'file_path' => $path,
                'file_name' => $file->getClientOriginalName(),
                'mime_type' => $file->getMimeType(),
                'uploaded_by' => $user->id,
            ]);
        }
    }

    public function reporterDisplayName(DisciplinaryCase $case, ?User $viewer = null): string
    {
        if ($case->is_anonymous && ! $viewer?->hasAnyRole(['Super Admin', 'Administrator', 'Headmaster', 'Registrar'])) {
            return 'Anonymous';
        }

        return $case->reportedByStudent?->full_name ?? 'Staff/Admin';
    }

    private function caseInDepartment(int $staffId, DisciplinaryCase $case): bool
    {
        $staff = Staff::query()->find($staffId);
        if (! $staff?->department || ! $case->class_id) {
            return false;
        }

        return SchoolClass::query()
            ->whereKey($case->class_id)
            ->whereHas('classTeacher', fn ($q) => $q->where('department', $staff->department))
            ->exists();
    }

    private function parentCanView(User $user, DisciplinaryCase $case): bool
    {
        if (! in_array($case->stage, ['closed', 'dismissed'], true)) {
            return false;
        }

        $guardian = Guardian::query()->where('user_id', $user->id)->first();
        if (! $guardian) {
            return false;
        }

        return $guardian->students()->where('students.id', $case->subject_student_id)->exists();
    }

    private function notifyParentsOfOutcome(DisciplinaryCase $case): void
    {
        $case->load('subjectStudent.guardians.user');
        foreach ($case->subjectStudent?->guardians ?? [] as $guardian) {
            if ($guardian->user) {
                $guardian->user->notify(new DisciplinaryCaseUpdatedNotification($case, 'outcome'));
            }
        }
    }

    private function isValidTransition(string $from, string $to, string $role): bool
    {
        $transitions = match ($role) {
            'class_teacher' => [
                'reported' => ['class_teacher_investigation', 'headmaster_review', 'dismissed'],
                'class_teacher_investigation' => ['headmaster_review', 'dismissed'],
            ],
            'admin' => [
                'reported' => ['class_teacher_investigation', 'headmaster_review', 'dismissed'],
                'class_teacher_investigation' => ['headmaster_review', 'hearing', 'dismissed'],
                'headmaster_review' => ['hearing', 'decision', 'dismissed'],
                'hearing' => ['decision', 'dismissed'],
                'decision' => ['closed', 'dismissed'],
            ],
            default => [],
        };

        return in_array($to, $transitions[$from] ?? [], true);
    }

    private function resolveStudentClass(Student $student): ?SchoolClass
    {
        return $student->classes()->latest('class_student.created_at')->first();
    }

    private function resolveHeadmasterUser(): ?User
    {
        return User::role('Headmaster')->first()
            ?? User::role('Administrator')->first();
    }

    private function generateCaseNumber(): string
    {
        $year = now()->format('Y');
        $count = DisciplinaryCase::query()->whereYear('created_at', $year)->count() + 1;

        return sprintf('DC-%s-%04d', $year, $count);
    }

    private function logAction(
        DisciplinaryCase $case,
        ?string $fromStage,
        string $toStage,
        string $action,
        User $user,
        ?string $notes = null
    ): DisciplinaryCaseAction {
        return DisciplinaryCaseAction::query()->create([
            'disciplinary_case_id' => $case->id,
            'from_stage' => $fromStage,
            'to_stage' => $toStage,
            'action' => $action,
            'notes' => $notes,
            'performed_by' => $user->id,
        ]);
    }

    private function createDisciplinaryRecord(DisciplinaryCase $case, User $user): void
    {
        DisciplinaryRecord::query()->create([
            'student_id' => $case->subject_student_id,
            'incident_date' => $case->created_at->toDateString(),
            'category' => $case->category,
            'description' => $case->description,
            'action_taken' => $this->outcomeLabel($case->outcome).($case->outcome_details ? ': '.$case->outcome_details : ''),
            'recorded_by' => $user->staff_id,
            'disciplinary_case_id' => $case->id,
            'status' => 'resolved',
        ]);
    }

    private function applyStudentStatus(DisciplinaryCase $case): void
    {
        $student = $case->subjectStudent;

        if (! $student || ! $case->outcome) {
            return;
        }

        $status = match ($case->outcome) {
            'suspension' => 'suspended',
            'expulsion', 'dismissal' => 'withdrawn',
            default => $student->status,
        };

        if ($status !== $student->status) {
            $student->update(['status' => $status]);
        }
    }

    private function notifyStakeholders(DisciplinaryCase $case, string $event): void
    {
        $users = collect();

        if ($case->class_teacher_id) {
            $teacherUser = User::query()->where('staff_id', $case->class_teacher_id)->first();
            if ($teacherUser) {
                $users->push($teacherUser);
            }
        }

        if ($case->headmaster_user_id) {
            $users->push(User::query()->find($case->headmaster_user_id));
        }

        if ($case->reported_by_student_id) {
            $reporterUser = User::query()->where('student_id', $case->reported_by_student_id)->first();
            if ($reporterUser) {
                $users->push($reporterUser);
            }
        }

        User::role(['Super Admin', 'Administrator'])->get()->each(fn (User $user) => $users->push($user));

        $users->filter()->unique('id')->each(
            fn (User $user) => $user->notify(new DisciplinaryCaseUpdatedNotification($case, $event))
        );
    }
}
