<?php

namespace App\Modules\Attendance\Services;

use App\Modules\Auth\Entities\Role;
use App\Modules\Attendance\Entities\AttendanceSession;
use App\Modules\Academic\Models\ClassModel;
use App\Modules\Academic\Models\ScheduleModel;
use App\Modules\Academic\Models\StudentModel;
use App\Modules\Academic\Models\SubjectModel;
use App\Modules\Attendance\Models\AttendanceSessionModel;

/**
 * Attendance Report Service
 *
 * On-the-fly computation of schedule attendance report (expected, present, late, absent).
 * No DB insert for ABSENT. RBAC applied when userContext provided.
 */
class AttendanceReportService
{
    protected ScheduleModel $scheduleModel;
    protected StudentModel $studentModel;
    protected SubjectModel $subjectModel;
    protected ClassModel $classModel;
    protected AttendanceSessionModel $attendanceModel;

    public function __construct()
    {
        $this->scheduleModel   = new ScheduleModel();
        $this->studentModel   = new StudentModel();
        $this->subjectModel   = new SubjectModel();
        $this->classModel     = new ClassModel();
        $this->attendanceModel = new AttendanceSessionModel();
    }

    /**
     * Get schedule attendance report for a given date.
     * Absent = expected students minus those with PRESENT/LATE record (no insert).
     *
     * @param int $scheduleId
     * @param string $dateYmd Y-m-d
     * @param array|null $userContext { id, name, email, roles: [ { role_code } ] } for RBAC
     * @return array|null Report array or null if schedule not found / no access
     */
    public function getScheduleAttendanceReport(int $scheduleId, string $dateYmd, ?array $userContext = null): ?array
    {
        $schedule = $this->scheduleModel->getScheduleWithSlot($scheduleId);
        if ($schedule === null) {
            return null;
        }

        if ($userContext !== null && !$this->canAccessSchedule($schedule, $userContext)) {
            return null;
        }

        $classId = (int) $schedule['class_id'];

        $expectedStudents = $this->studentModel->where('class_id', $classId)->findAll();
        $expectedIds      = array_map(fn ($s) => (int) $s->id, $expectedStudents);
        $expectedTotal    = count($expectedIds);

        $db     = \Config\Database::connect();
        $builder = $db->table('attendance_sessions AS a');
        $builder->select('a.student_id, s.nisn, s.name, a.status, a.checkin_at');
        $builder->join('students AS s', 's.id = a.student_id', 'inner');
        $builder->where('a.schedule_id', $scheduleId);
        $builder->where('a.attendance_date', $dateYmd);
        $builder->whereIn('a.status', [AttendanceSession::STATUS_PRESENT, AttendanceSession::STATUS_LATE]);
        $builder->orderBy('s.name', 'ASC');
        $rows = $builder->get()->getResultArray();

        $presentList = [];
        $presentIds  = [];
        $lateTotal   = 0;
        foreach ($rows as $row) {
            $sid = (int) $row['student_id'];
            $presentIds[] = $sid;
            $presentList[] = [
                'student_id' => $sid,
                'nisn'       => (string) ($row['nisn'] ?? ''),
                'name'       => (string) ($row['name'] ?? ''),
                'status'     => (string) $row['status'],
                'checkin_at' => (string) $row['checkin_at'],
            ];
            if ($row['status'] === AttendanceSession::STATUS_LATE) {
                $lateTotal++;
            }
        }
        $presentTotal = count($presentList);

        $absentIds = array_diff($expectedIds, $presentIds);
        $absentList = [];
        foreach ($expectedStudents as $s) {
            if (in_array((int) $s->id, $absentIds, true)) {
                $absentList[] = [
                    'student_id' => (int) $s->id,
                    'nisn'       => (string) ($s->nisn ?? ''),
                    'name'       => (string) ($s->name ?? ''),
                ];
            }
        }
        usort($absentList, fn ($a, $b) => strcmp($a['name'], $b['name']));
        $absentTotal = count($absentList);

        $subject    = $this->subjectModel->find($schedule['subject_id']);
        $subjectName = $subject ? (string) $subject->name : '-';
        $classEntity = $this->classModel->find($classId);
        $className   = $classEntity ? (string) $classEntity->name : '-';

        $schedulePayload = [
            'id'          => (int) $schedule['id'],
            'class_id'    => (int) $schedule['class_id'],
            'class_name'  => $className,
            'subject'     => $subjectName,
            'teacher'     => (string) ($schedule['teacher_name'] ?? ''),
            'start_time'  => (string) $schedule['start_time'],
            'end_time'    => (string) $schedule['end_time'],
            'day_of_week' => (int) $schedule['day_of_week'],
        ];

        return [
            'schedule' => $schedulePayload,
            'summary'  => [
                'expected_total' => $expectedTotal,
                'present_total'  => $presentTotal,
                'late_total'     => $lateTotal,
                'absent_total'   => $absentTotal,
            ],
            'present' => $presentList,
            'absent'  => $absentList,
        ];
    }

    /**
     * RBAC: whether user can access this schedule.
     * @param array|object $schedule Schedule row (array from getScheduleWithSlot) or entity with class_id, teacher_user_id
     */
    protected function canAccessSchedule(array|object $schedule, array $user): bool
    {
        $roles     = $user['roles'] ?? [];
        $roleCodes = array_column($roles, 'role_code');

        if (in_array(Role::CODE_ADMIN, $roleCodes, true) || in_array(Role::CODE_GURU_BK, $roleCodes, true)) {
            return true;
        }

        $classId = (int) (is_array($schedule) ? $schedule['class_id'] : $schedule->class_id);

        if (in_array(Role::CODE_WALI_KELAS, $roleCodes, true)) {
            $db  = \Config\Database::connect();
            $row = $db->table('classes')->select('id')->where('id', $classId)->where('wali_user_id', $user['id'])->get()->getRow();
            return $row !== null;
        }

        if (in_array(Role::CODE_GURU_MAPEL, $roleCodes, true)) {
            $teacherUserId = is_array($schedule)
                ? (isset($schedule['teacher_user_id']) ? (int) $schedule['teacher_user_id'] : 0)
                : (isset($schedule->teacher_user_id) ? (int) $schedule->teacher_user_id : 0);
            return $teacherUserId === (int) $user['id'];
        }

        if (in_array(Role::CODE_ORANG_TUA, $roleCodes, true)) {
            $studentIds = $this->getStudentIdsForParent((int) $user['id']);
            if ($studentIds === []) {
                return false;
            }
            $db = \Config\Database::connect();
            $overlap = $db->table('students')
                ->where('class_id', $classId)
                ->whereIn('id', $studentIds)
                ->countAllResults();
            return $overlap > 0;
        }

        return false;
    }

    protected function getStudentIdsForParent(int $userId): array
    {
        $db   = \Config\Database::connect();
        $rows = $db->table('student_parents AS sp')
            ->select('sp.student_id')
            ->join('parents AS p', 'p.id = sp.parent_id', 'inner')
            ->where('p.user_id', $userId)
            ->get()
            ->getResultArray();
        return array_map('intval', array_column($rows, 'student_id'));
    }
}
