<?php

namespace App\Models;

use CodeIgniter\Model;
use CodeIgniter\I18n\Time;

class AttendanceTrackingModel extends Model
{
    protected $table            = 'attendance_tracking';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'array';
    protected $useSoftDeletes   = false;

    protected $allowedFields = [
        'student_id',
        'date',
        'is_reported',
        'reason',
        'is_notified',
        'notif_counter',
        'semester',
        'school_year',
        'note',
    ];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';
    protected $dateFormat    = 'datetime';

    /* ----------------------- Helpers ----------------------- */

    /** Normalize Y-m-d (or Y-m-d H:i:s) into a full 'Y-m-d H:i:s' at midnight if time missing. */
    private function normalizeDateTime(string $date): string
    {
        // If already has time, trust it; else append midnight
        if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $date)) {
            return $date;
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            return $date . ' 00:00:00';
        }
        // Best-effort parse
        try {
            $t = Time::parse($date, 'UTC');
        } catch (\Throwable $e) {
            $t = Time::today('UTC');
        }
        return $t->toDateTimeString(); // Y-m-d H:i:s
    }

    /** Apply semester/schoolYear filters if provided. */
    private function applyTermFilters($qb, ?string $semester, ?string $schoolYear)
    {
        if ($semester !== null && $semester !== '') {
            $qb->where('semester', $semester);
        }
        if ($schoolYear !== null && $schoolYear !== '') {
            $qb->where('school_year', $schoolYear);
        }
        return $qb;
    }

    /** Derive (semester, school_year) from a Y-m-d date (Fall=Aug–Dec; Spring=Jan–Jul). */
    private function deriveTermFromDate(string $ymd): array
    {
        try {
            $d = Time::createFromFormat('Y-m-d', substr($ymd, 0, 10), 'UTC') ?? Time::today('UTC');
        } catch (\Throwable $e) {
            $d = Time::today('UTC');
        }
        $m = (int)$d->format('n');
        $y = (int)$d->format('Y');

        $semester = ($m >= 8 && $m <= 12) ? 'Fall' : 'Spring';
        $startY   = ($m >= 8) ? $y : $y - 1;
        $endY     = $startY + 1;

        return [$semester, sprintf('%d-%d', $startY, $endY)];
    }

    /* ----------------------- CRUD-ish APIs ----------------------- */

    /**
     * Records a student absence (stores time at 00:00:00 if only a day is given).
     * NOTE: $semester/$schoolYear are optional; will be derived from $date if omitted.
     */
    public function recordAbsence(
        int $studentId,
        string $date,
        ?string $semester = null,
        ?string $schoolYear = null,
        bool $isExcused = false,
        ?string $reason = null
    ) {
        $dt = $this->normalizeDateTime($date);

        if ($semester === null || $schoolYear === null) {
            [$sem, $yr] = $this->deriveTermFromDate($dt);
            $semester    = $semester    ?? $sem;
            $schoolYear  = $schoolYear  ?? $yr;
        }

        return $this->insert([
            'student_id'    => $studentId,
            'date'          => $dt,
            'is_reported'    => $isExcused ? 1 : 0,
            'reason'        => $reason,
            'is_notified'   => 0,
            'notif_counter' => 0,
            'semester'      => $semester,
            'school_year'   => $schoolYear,
        ], true);
    }

    /**
     * All absences for a student; start/end accept Y-m-d or Y-m-d H:i:s.
     * For day-only ranges, we compare on DATE(date).
     */
    public function getStudentAbsences(
        int $studentId,
        ?string $startDate = null,
        ?string $endDate = null,
        ?string $semester = null,
        ?string $schoolYear = null
    ): array {
        $qb = $this->builder()->where('student_id', $studentId);

        if ($startDate && preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate)) {
            $qb->where('DATE(`date`) >=', $startDate);
        } elseif ($startDate) {
            $qb->where('date >=', $this->normalizeDateTime($startDate));
        }

        if ($endDate && preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
            $qb->where('DATE(`date`) <=', $endDate);
        } elseif ($endDate) {
            $qb->where('date <=', $this->normalizeDateTime($endDate));
        }

        $this->applyTermFilters($qb, $semester, $schoolYear);

        return $qb->orderBy('date', 'DESC')->get()->getResultArray();
    }

    /**
     * Unexcused + unnotified absences (optionally for a specific day).
     */
    public function getUnnotifiedUnexcusedAbsences(
        ?string $date = null,
        ?string $semester = null,
        ?string $schoolYear = null
    ): array {
        $qb = $this->builder()
            ->where('is_reported', 0)
            ->where('is_notified', 0);

        if ($date && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            $qb->where('DATE(`date`)', $date);
        } elseif ($date) {
            $qb->where('date', $this->normalizeDateTime($date));
        }

        $this->applyTermFilters($qb, $semester, $schoolYear);

        return $qb->orderBy('date', 'DESC')->get()->getResultArray();
    }

    /**
     * Has at least N absences in the last N days (rough heuristic);
     * for weekly checks, prefer checkConsecutiveAbsences().
     */
    public function hasConsecutiveAbsences(
        int $studentId,
        int $days,
        ?string $semester = null,
        ?string $schoolYear = null
    ): bool {
        $start = date('Y-m-d', strtotime("-{$days} days"));

        $qb = $this->builder()
            ->select('COUNT(*) AS cnt')
            ->where('student_id', $studentId)
            ->where('DATE(`date`) >=', $start);

        $this->applyTermFilters($qb, $semester, $schoolYear);

        $row = $qb->get()->getFirstRow('array');
        return (int)($row['cnt'] ?? 0) >= $days;
    }

    /**
     * Return TRUE if last $threshold absences are exactly weekly (7-day spacing).
     */
    public function checkConsecutiveAbsences(
        int $studentId,
        int $threshold,
        ?string $semester = null,
        ?string $schoolYear = null
    ): bool {
        $qb = $this->builder()
            ->select('date, is_reported, is_notified')
            ->where('student_id', $studentId)
            ->where('is_reported', 0)
            ->where('is_notified', 0);

        $this->applyTermFilters($qb, $semester, $schoolYear);

        $absences = $qb->orderBy('date', 'DESC')->limit($threshold)->get()->getResultArray();

        if (count($absences) < $threshold) return false;

        for ($i = 0; $i < $threshold - 1; $i++) {
            $d1 = new \DateTime($absences[$i]['date']);
            $d2 = new \DateTime($absences[$i + 1]['date']);
            $diffDays = (int)$d2->diff($d1)->format('%a');
            if ($diffDays !== 7) {
                return false;
            }
        }
        return true;
    }

    /**
     * TRUE if at least $absencesThreshold absences in the last $weeksStreak weeks.
     */
    public function checkAbsencesInStreak(
        int $studentId,
        int $absencesThreshold,
        int $weeksStreak,
        ?string $semester = null,
        ?string $schoolYear = null
    ): bool {
        $qb = $this->builder()
            ->select('date, is_reported, is_notified')
            ->where('student_id', $studentId)
            ->where('is_reported', 0)
            ->where('is_notified', 0);

        $this->applyTermFilters($qb, $semester, $schoolYear);

        $records = $qb->orderBy('date', 'DESC')->limit($weeksStreak)->get()->getResultArray();

        if (count($records) < $weeksStreak) return false;

        $count = 0;
        foreach ($records as $r) {
            if ((int)$r['is_reported'] === 0 && (int)$r['is_notified'] === 0) {
                $count++;
            }
        }
        return $count >= $absencesThreshold;
    }

    /**
     * Summarize a student's status this term.
     */
    public function getStudentAttendanceStats(
        int $studentId,
        ?string $semester = null,
        ?string $schoolYear = null
    ): array {
        // total_absences
        $qb1 = $this->builder()->where('student_id', $studentId);
        $this->applyTermFilters($qb1, $semester, $schoolYear);
        $total = (int)($qb1->select('COUNT(*) AS cnt')->get()->getFirstRow('array')['cnt'] ?? 0);

        // unexcused_absences
        $qb2 = $this->builder()->where('student_id', $studentId)->where('is_reported', 0);
        $this->applyTermFilters($qb2, $semester, $schoolYear);
        $unexcused = (int)($qb2->select('COUNT(*) AS cnt')->get()->getFirstRow('array')['cnt'] ?? 0);

        // last_absence
        $qb3 = $this->builder()->where('student_id', $studentId);
        $this->applyTermFilters($qb3, $semester, $schoolYear);
        $last = $qb3->orderBy('date', 'DESC')->get()->getFirstRow('array');

        return [
            'total_absences'      => $total,
            'unexcused_absences'  => $unexcused,
            'last_absence'        => $last,
        ];
    }

    /**
     * Fetch records by semester/schoolYear (with optional limit).
     */
    public function getAttendanceBySemester(
        ?string $semester = null,
        ?string $schoolYear = null,
        int $limit = 0
    ): array {
        $qb = $this->builder();
        $this->applyTermFilters($qb, $semester, $schoolYear);
        if ($limit > 0) $qb->limit($limit);
        return $qb->orderBy('date', 'DESC')->get()->getResultArray();
    }

    /**
     * Summary counts for a term.
     */
    public function getSemesterSummary(?string $semester = null, ?string $schoolYear = null): array
    {
        // total
        $qb1 = $this->builder();
        $this->applyTermFilters($qb1, $semester, $schoolYear);
        $total = (int)($qb1->select('COUNT(*) AS cnt')->get()->getFirstRow('array')['cnt'] ?? 0);

        // unexcused
        $qb2 = $this->builder()->where('is_reported', 0);
        $this->applyTermFilters($qb2, $semester, $schoolYear);
        $unexcused = (int)($qb2->select('COUNT(*) AS cnt')->get()->getFirstRow('array')['cnt'] ?? 0);

        // distinct students
        $qb3 = $this->builder();
        $this->applyTermFilters($qb3, $semester, $schoolYear);
        $students = (int)($qb3->select('COUNT(DISTINCT student_id) AS cnt')->get()->getFirstRow('array')['cnt'] ?? 0);

        return [
            'total_absences'        => $total,
            'unexcused_absences'    => $unexcused,
            'students_with_absences' => $students,
        ];
    }

    /**
     * Update/create violation rows (matching by student + DATE(date) + term).
     * Expects input rows like: ['id'=>..., 'violation'=>..., 'absences'=>[['date'=>'Y-m-d'], ...]]
     */
    public function updateAttendanceViolations(array $studentsWithViolations, string $semester, string $schoolYear): array
    {
        $results = ['added' => 0, 'updated' => 0, 'skipped' => 0, 'notified' => 0];

        foreach ($studentsWithViolations as $student) {
            if (empty($student['absences'])) {
                $results['skipped']++;
                continue;
            }

            foreach ($student['absences'] as $absence) {
                $ymd = substr((string)($absence['date'] ?? date('Y-m-d')), 0, 10);
                $dt  = $this->normalizeDateTime($ymd);

                $data = [
                    'student_id'    => (int)$student['id'],
                    'date'          => $dt,
                    'is_reported'    => 0,
                    'reason'        => (string)($student['violation'] ?? ''),
                    'is_notified'   => 0,
                    'notif_counter' => 0,
                    'semester'      => $semester,
                    'school_year'   => $schoolYear,
                ];

                try {
                    // Match by student + DATE(date) + term
                    $existing = $this->builder()
                        ->where('student_id', $data['student_id'])
                        ->where('DATE(`date`)', $ymd)
                        ->where('semester', $semester)
                        ->where('school_year', $schoolYear)
                        ->get()->getFirstRow('array');

                    if ($existing) {
                        if (($existing['reason'] ?? '') !== $data['reason']) {
                            $this->update((int)$existing['id'], $data);
                            $results['updated']++;

                            if (!empty($existing['is_notified'])) {
                                $this->resetNotification((int)$existing['id']);
                                $results['notified']++;
                            }
                        } else {
                            $results['skipped']++;
                        }
                    } else {
                        $newId = $this->insert($data, true);
                        $results['added']++;

                        // Only match a standalone "4" (final threshold)
                        if (preg_match('/\b4\b/', $data['reason'])) {
                            $this->markAsNotified((int)$newId);
                            $results['notified']++;
                        }
                    }
                } catch (\Throwable $e) {
                    log_message('error', sprintf(
                        'Failed to update attendance violation for student %d on %s: %s',
                        $data['student_id'],
                        $ymd,
                        $e->getMessage()
                    ));
                    $results['skipped']++;
                }
            }
        }

        return $results;
    }

    /* ----------------------- Notification ops ----------------------- */

    public function markAsNotified(int $id): bool
    {
        // atomic increment + set flag; touch updated_at by doing update()
        $now = utc_now();
        $this->builder()
            ->where('id', $id)
            ->set('notif_counter', 'COALESCE(notif_counter,0)+1', false)
            ->update();
        return $this->update($id, [
            'is_notified' => 1,
            'updated_at'  => $now,
        ]);
    }

    public function resetNotification(int $id): bool
    {
        return $this->update($id, ['is_notified' => 0, 'notif_counter' => 0]);
    }

    public function markRuleAsNotified(
        int $studentId,
        string $code,
        string $ymd,
        ?string $semester = null,
        ?string $schoolYear = null
    ): bool {
        $semester   = $semester   ?? (new \App\Models\ConfigurationModel())->getConfig('semester');
        $schoolYear = $schoolYear ?? (new \App\Models\ConfigurationModel())->getConfig('school_year');

        $day  = substr($ymd, 0, 10);
        $start = $day . ' 00:00:00';
        $end   = date('Y-m-d', strtotime($day . ' +1 day')) . ' 00:00:00';
        $now   = utc_now();

        // 1) Try match by code + day (preferred)
        $qb = $this->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->where('date >=', $start)
            ->where('date <',  $end)
            ->like('reason', $code, 'both')
            ->orderBy('date', 'DESC');

        $row = $qb->first();

        // 2) Fallback: match by day only (reason may hold a human-friendly title)
        if (!$row) {
            $row = $this->where('student_id', $studentId)
                ->where('semester', $semester)
                ->where('school_year', $schoolYear)
                ->where('date >=', $start)
                ->where('date <',  $end)
                ->orderBy('date', 'DESC')
                ->first();
        }

        if ($row && !empty($row['id'])) {
            $count = (int)($row['notif_counter'] ?? 0) + 1;
            return (bool) $this->update((int)$row['id'], [
                'is_notified'   => 1,
                'notif_counter' => $count,
                'is_reported'   => 0,
                'updated_at'    => $now,
            ]);
        }

        // No existing row -> insert one so future passes see it as notified
        return (bool) $this->insert([
            'student_id'    => $studentId,
            'date'          => $start,
            'is_reported'   => 0,
            'reason'        => $code,         // store the CODE, not a prose title
            'is_notified'   => 1,
            'notif_counter' => 1,
            'semester'      => $semester,
            'school_year'   => $schoolYear,
            'created_at'    => $now,
            'updated_at'    => $now,
        ]);
    }

     /**
     * Mark the latest matching tracking row as notified and increment counter.
     * $code is the rule code you stored in "reason" (e.g. ABS_2, LATE_3, MIX_L2A1).
     * Optionally bound by $lastDate if you want to avoid touching future rows.
     */
    public function markNotified(int $studentId, string $schoolYear, ?string $semester, string $code, ?string $lastDate = null): int
    {
        $qb = $this->builder();
        $qb->where('student_id', $studentId)
           ->where('school_year', $schoolYear);

        if (!empty($semester)) {
            $qb->where('semester', $semester);
        }

        if (!empty($lastDate)) {
            $qb->where('date <=', $lastDate); // keep it safe to the last violation date you emailed about
        }

        $qb->like('reason', $code, 'both')
           ->orderBy('date', 'DESC')
           ->limit(1);

        $row = $qb->get()->getRowArray();
        if (!$row) {
            return 0;
        }

        $id = (int)$row['id'];
        $counter = (int)($row['notif_counter'] ?? 0) + 1;

        return $this->update($id, [
            'is_notified'  => 1,
            'notif_counter'=> $counter,
            'updated_at'   => utc_now(),
        ]) ? 1 : 0;
    }
}
