<?php

namespace App\Services\Attendance;

use App\Models\AttendanceData;
use App\Models\AttendanceDay;
use App\Models\ClassSection;
use App\Models\Configuration;
use App\Models\UserRole;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class AttendanceService
{
    public function __construct(
        protected Configuration $configuration,
        protected AttendanceData $attendanceData,
        protected AttendanceDay $attendanceDay,
        protected ClassSection $classSection,
        protected UserRole $userRole,
        protected AttendancePolicyService $attendancePolicyService,
        protected StudentAttendanceWriterService $studentAttendanceWriterService,
        protected TeacherAttendanceSubmissionService $teacherAttendanceSubmissionService,
        protected AttendanceRecordSyncService $attendanceRecordSyncService,
    ) {}

    public function currentSemester(): string
    {
        return (string)($this->configuration->getConfig('semester') ?? 'Fall');
    }

    public function currentSchoolYear(): string
    {
        return (string)($this->configuration->getConfig('school_year') ?? now()->year . '-' . (now()->year + 1));
    }

    public function updateAttendanceManagement(Authenticatable $user, array $data): array
    {
        $classSectionId = (int)$data['class_section_id'];
        $studentId = (int)$data['student_id'];
        $status = strtolower((string)$data['status']);
        $date = (string)$data['date'];
        $reason = $data['reason'] ?? null;
        $classId = $data['class_id'] ?? null;
        $semester = (string)($data['semester'] ?? $this->currentSemester());
        $schoolYear = (string)($data['school_year'] ?? $this->currentSchoolYear());

        $dayRow = AttendanceDay::query()
            ->where('class_section_id', $classSectionId)
            ->whereDate('date', $date)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();

        $dayPolicyRow = $dayRow?->toArray() ?? [
            'status' => 'draft',
            'class_section_id' => $classSectionId,
            'date' => $date,
            'semester' => $semester,
            'school_year' => $schoolYear,
        ];

        $currentUser = [
            'id' => $user->id,
            'roles' => method_exists($user, 'roles') ? $user->roles->pluck('name')->all() : [],
            'permissions' => method_exists($user, 'permissions') ? $user->permissions->pluck('name')->all() : [],
        ];

        if (!$this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser)) {
            throw new RuntimeException('Attendance is locked for your role.');
        }

        $reportedRaw = strtolower((string)($data['is_reported'] ?? ''));
        $isReported = $status === 'present'
            ? 'no'
            : (in_array($reportedRaw, ['1', 'yes', 'y', 'true', 'on'], true) ? 'yes' : 'no');

        DB::transaction(function () use (
            $user,
            $classSectionId,
            $studentId,
            $status,
            $date,
            $reason,
            $classId,
            $semester,
            $schoolYear,
            $isReported
        ) {
            $this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([
                'class_section_id' => $classSectionId,
                'student_id' => $studentId,
                'school_id' => null,
                'status' => $status,
                'reason' => $reason,
                'is_reported' => $isReported,
                'semester' => $semester,
                'school_year' => $schoolYear,
                'date' => $date,
                'class_id' => (int)$classId,
                'user_id' => $user->id,
            ]);

            AttendanceDay::query()->firstOrCreate(
                [
                    'class_section_id' => $classSectionId,
                    'date' => $date,
                    'semester' => $semester,
                    'school_year' => $schoolYear,
                ],
                [
                    'status' => 'draft',
                    'created_at' => now(),
                    'updated_at' => now(),
                ]
            );
        });

        return [
            'ok' => true,
            'message' => 'Attendance updated successfully.',
        ];
    }

    public function submitTeacherAttendance(Authenticatable $user, array $data): array
    {
        return $this->teacherAttendanceSubmissionService->submit(
            $user,
            $data,
            fn (?array $row) => $this->detectExternalSubmission($row),
            fn () => $this->currentSemester(),
            fn () => $this->currentSchoolYear(),
        );
    }

    public function adminAddEntry(Authenticatable $user, array $data): array
    {
        $classSectionId = (int)$data['class_section_id'];
        $studentId = (int)$data['student_id'];
        $status = strtolower((string)$data['status']);
        $date = (string)($data['date'] ?? now()->toDateString());
        $reason = $data['reason'] ?? null;
        $isReported = in_array(strtolower((string)($data['is_reported'] ?? 'no')), ['yes', 'no'], true)
            ? strtolower((string)($data['is_reported'] ?? 'no'))
            : 'no';

        $section = $this->classSection
            ->query()
            ->select('class_id', 'id', 'class_section_id')
            ->where('id', $classSectionId)
            ->orWhere('class_section_id', $classSectionId)
            ->first();

        $resolvedClassId = (int)($section->class_id ?? 0);

        if ($resolvedClassId <= 0) {
            throw new RuntimeException('Cannot resolve class_id for this section.');
        }

        DB::transaction(function () use (
            $user,
            $classSectionId,
            $studentId,
            $status,
            $date,
            $reason,
            $resolvedClassId,
            $isReported
        ) {
            AttendanceDay::query()->firstOrCreate(
                [
                    'class_section_id' => $classSectionId,
                    'date' => $date,
                    'semester' => $this->currentSemester(),
                    'school_year' => $this->currentSchoolYear(),
                ],
                [
                    'status' => 'draft',
                    'created_at' => now(),
                    'updated_at' => now(),
                ]
            );

            $this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([
                'class_section_id' => $classSectionId,
                'student_id' => $studentId,
                'school_id' => $data['school_id'] ?? null,
                'status' => $status,
                'reason' => $reason,
                'is_reported' => $isReported,
                'semester' => $this->currentSemester(),
                'school_year' => $this->currentSchoolYear(),
                'date' => $date,
                'class_id' => $resolvedClassId,
                'user_id' => $user->id,
            ]);
        });

        return [
            'ok' => true,
            'message' => 'Entry saved successfully.',
        ];
    }

    public function normalizeAttendanceEntries(array $entries): array
    {
        foreach ($entries as &$row) {
            $row['status'] = $this->normalizeStatus($row['status'] ?? '');
            $reportedRaw = $row['is_reported'] ?? ($row['reported'] ?? '');
            $row['is_reported'] = $this->normalizeReportedFlag($reportedRaw);
        }
        return $entries;
    }

    public function normalizeStatus(?string $status): string
    {
        $status = strtolower(trim((string)$status));
        return in_array($status, ['present', 'absent', 'late'], true) ? $status : $status;
    }

    public function normalizeReportedFlag(mixed $flag): string
    {
        $v = strtolower(trim((string)$flag));
        return in_array($v, ['yes', '1', 'true', 'y', 'on'], true) ? 'yes' : 'no';
    }

    public function detectExternalSubmission(?array $row): array
    {
        if (empty($row) || !is_array($row)) {
            return ['locked' => false, 'source' => null];
        }

        $reportedRaw = strtolower((string)($row['is_reported'] ?? ''));
        $reason = strtolower((string)($row['reason'] ?? ''));

        $isParent = in_array($reportedRaw, ['yes', '1', 'true', 'y'], true)
            || str_contains($reason, 'parent-reported')
            || str_contains($reason, 'parent reported');

        if ($isParent) {
            return ['locked' => true, 'source' => 'parent'];
        }

        $modifierId = (int)($row['modified_by'] ?? 0);
        if ($modifierId > 0 && $this->isAdminLikeUserId($modifierId)) {
            return ['locked' => true, 'source' => 'admin'];
        }

        return ['locked' => false, 'source' => null];
    }

    public function isAdminLikeUserId(int $userId): bool
    {
        if ($userId <= 0) {
            return false;
        }

        $roles = $this->userRole->getRolesByUserId($userId);
        if (empty($roles)) {
            return false;
        }

        $excluded = ['parent', 'guest', 'teacher', 'teacher_assistant', 'assistant_teacher', 'ta'];

        foreach ($roles as $role) {
            $name = strtolower(str_replace([' ', '-'], '_', (string)($role['role_name'] ?? '')));
            if ($name !== '' && !in_array($name, $excluded, true)) {
                return true;
            }
        }

        return false;
    }
}