<?php

namespace App\Models;

use CodeIgniter\Model;
use Config\Services;
class StaffAttendanceModel extends Model
{
    protected $table            = 'staff_attendance';
    protected $primaryKey       = 'id';
    protected $returnType       = 'array';
    protected $useAutoIncrement = true;

    // ✅ Only columns that really exist in staff_attendance
    protected $allowedFields = [
        'user_id',
        'role_name',
        'date',
        'semester',
        'school_year',
        'status',       // enum('present','absent','late')
        'reason',
        'created_at',
        'updated_at',
        'created_by',
        'updated_by',
    ];

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

    // (Optional helper, keep if you like)
    public function currentEditorId(): int
    {
        return (int) (session()->get('user_id') ?? 0);
    }

    /**
     * Upsert by (user_id, date, semester, school_year).
     */
    public function upsertOne(
        int $userId,
        ?string $roleName,
        string $date,
        string $semester,
        string $schoolYear,
        string $status,
        ?string $reason,
        ?int $editorId = null
    ): bool {
        $existing = $this->where([
            'user_id'     => $userId,
            'date'        => $date,
            'semester'    => $semester,
            'school_year' => $schoolYear,
        ])->first();

        $row = [
            'user_id'     => $userId,
            'role_name'   => $roleName,
            'date'        => $date,
            'semester'    => $semester,
            'school_year' => $schoolYear,
            'status'      => $status,
            'reason'      => $reason ?: null,
            'updated_by'  => $editorId ?? $this->currentEditorId(),
        ];

        if ($existing) {
            return $this->update((int)$existing['id'], $row);
        }

        $row['created_by'] = $editorId ?? $this->currentEditorId();
        return $this->insert($row) !== false;
    }

    // Optional: helpers/constants
    public const STATUS_PRESENT = 'present';
    public const STATUS_ABSENT  = 'absent';
    public const STATUS_LATE    = 'late';

    public const POS_MAIN = 'main';
    public const POS_TA   = 'ta';

    /** Normalize/validate a status string; return null for empty/invalid (means delete). */
    protected function normStatus(?string $status): ?string
    {
        $s = strtolower((string) $status);
        return in_array($s, [self::STATUS_PRESENT, self::STATUS_ABSENT, self::STATUS_LATE], true) ? $s : null;
    }

    /** Quick session user id (safe). */
    protected function currentUserId(): ?int
    {
        $session = session();
        return $session ? ($session->get('user_id') ?: null) : null;
    }


    /**
     * Upsert a single cell by (user_id, date).
     * - If $status is empty/invalid => delete the record for that day (if exists).
     * - Otherwise upsert with provided fields.
     *
     * @param int         $userId
     * @param string      $date        Y-m-d
     * @param string|null $status      present|absent|late or ''/null to delete
     * @param string      $semester
     * @param string      $schoolYear
     * @param string|null $roleName
     * @param int|null    $classSectionId
     * @param string|null $position    main|ta (nullable for non-teachers)
     * @param string|null $reason
     */
    public function upsertCell(
        int $userId,
        string $date,
        ?string $status,
        string $semester,
        string $schoolYear,
        ?string $roleName = null,
        ?int $classSectionId = null,
        ?string $position = null,
        ?string $reason = null
    ): bool {
        if ($userId <= 0 || !$this->isValidDate($date) || $semester === '' || $schoolYear === '') {
            return false;
        }

        $existing = $this->where(['user_id' => $userId, 'date' => $date])->first();
        $norm = $this->normStatus($status);

        // DELETE if status cleared
        if ($norm === null) {
            if ($existing) {
                return (bool) $this->delete((int)$existing['id']);
            }
            return true; // nothing to delete
        }

        // UPSERT
        $row = [
            'user_id'         => $userId,
            'role_name'       => $roleName ?? ($existing['role_name'] ?? null),
            'class_section_id' => $classSectionId,
            'position'        => $position,
            'date'            => $date,
            'semester'        => $semester,
            'school_year'     => $schoolYear,
            'status'          => $norm,
            'reason'          => $reason,
            'updated_by'      => $this->currentUserId(),
        ];

        if ($existing) {
            $row['id'] = (int)$existing['id'];
            return (bool) $this->save($row);
        }

        $row['created_by'] = $this->currentUserId();
        return $this->insert($row) !== false;
    }

    /**
     * Return all staff (non Parent/Guest) with their status for a given day/term.
     * If a user has multiple roles, this picks a stable one via MIN(r.name).
     */
    public function staffWithStatusForDay(string $date, string $semester, string $schoolYear, array $excludeUserIds = []): array
    {
        $db = $this->db;

        // Subquery to pick one role per user (MIN by name for stability)
        $roleSub = $db->table('user_roles ur')
            ->select('ur.user_id, MIN(r.name) AS role_name')
            ->join('roles r', 'r.id = ur.role_id', 'left')
            ->groupBy('ur.user_id');

        $builder = $db->table('users u')
            ->select("
                u.id AS user_id,
                u.firstname,
                u.lastname,
                COALESCE(rs.role_name, '') AS role_name,
                sa.id AS satt_id,
                sa.status,
                sa.reason
            ")
            ->joinSubquery($roleSub, 'rs', 'rs.user_id = u.id', 'left')
            ->join('staff_attendance sa', "sa.user_id = u.id AND sa.date = {$db->escape($date)} AND sa.semester = {$db->escape($semester)} AND sa.school_year = {$db->escape($schoolYear)}", 'left')
            ->whereNotIn('LOWER(rs.role_name)', ['parent', 'guest'])
            ->orderBy('u.firstname', 'ASC')
            ->orderBy('u.lastname', 'ASC');

        if (!empty($excludeUserIds)) {
            $builder->whereNotIn('u.id', $excludeUserIds);
        }

        return $builder->get()->getResultArray();
    }

    // ---------- helpers ----------

    protected function isValidDate(string $d): bool
    {
        $dt = date_create_from_format('Y-m-d', $d);
        return $dt && $dt->format('Y-m-d') === $d;
    }

    public function getOne(int $userId, string $date): ?array
    {
        return $this->where(['user_id' => $userId, 'date' => $date])->first() ?: null;
    }

    public function deleteOne(int $userId, string $date): bool
    {
        $row = $this->getOne($userId, $date);
        return $row ? (bool)$this->delete((int)$row['id']) : true;
    }
    /* =========================
 * TEACHER/TA HELPERS (staff_attendance-backed)
 * ========================= */

    /** Return assigned teachers (main + TAs) for a section/term, with names. */
    public function getAssignedTeachersForSection(int $classSectionId, string $semester, string $schoolYear): array
    {
        return $this->db->table('teacher_class tc')
            ->select("
            tc.class_section_id,
            tc.teacher_id AS teacher_id,
            tc.position,
            u.firstname, u.lastname
        ", false)
            ->join('users u', 'u.id = tc.teacher_id', 'inner')
            ->where('tc.class_section_id', $classSectionId)
            ->where('tc.school_year', $schoolYear)
            ->orderBy("FIELD(tc.position,'main','ta')", '', false)
            ->orderBy('u.firstname', 'ASC')
            ->get()->getResultArray();
    }


    /** Get teacher/TA status for a given day/term in staff_attendance. */
    public function getStatusForDay(int $teacherId, int $classSectionId, string $date, string $semester, string $schoolYear): ?array
    {
        return $this->asArray()
            ->where('user_id', $teacherId)
            ->where('class_section_id', $classSectionId) // SECTION CODE
            ->where('date', $date)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first() ?: null;
    }

    public function assignedWithStatusForDay(int $classSectionId, string $date, string $semester, string $schoolYear): array
    {
        $db = $this->db;
        $join = "t.user_id = tc.teacher_id
             AND t.class_section_id = tc.class_section_id
             AND t.date = " . $db->escape($date) . "
             AND t.semester = " . $db->escape($semester) . "
             AND t.school_year = " . $db->escape($schoolYear);

        return $db->table('teacher_class tc')
            ->select("
            tc.class_section_id,
            tc.teacher_id AS teacher_id,
            tc.position,
            u.firstname, u.lastname,
            t.id AS satt_id, t.status, t.reason
        ", false)
            ->join('users u', 'u.id = tc.teacher_id', 'inner')
            ->join($this->table . ' t', $join, 'left') // staff_attendance
            ->where('tc.class_section_id', $classSectionId)
            ->where('tc.school_year', $schoolYear)
            ->orderBy("FIELD(tc.position,'main','ta')", '', false)
            ->orderBy('u.firstname', 'ASC')
            ->get()->getResultArray();
    }

    /** True if every assigned teacher has a status row for this day/term (in staff_attendance). */
    public function allAssignedStatusesPresent(int $classSectionId, string $date, string $semester, string $schoolYear): bool
    {
        // Count assigned teachers (from teacher_class)
        $assigned = $this->db->table('teacher_class')
            ->where('class_section_id', $classSectionId) // SECTION CODE
            ->where('school_year', $schoolYear)
            ->countAllResults();

        if ($assigned <= 0) return true;

        // Count distinct users saved in staff_attendance for that day/term/section
        $present = (int)($this->db->table($this->table)
            ->select('COUNT(DISTINCT user_id) AS c', false)
            ->where('class_section_id', $classSectionId)
            ->where('date', $date)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->get()->getRowArray()['c'] ?? 0);

        return $present === (int)$assigned;
    }

    /**
     * Assigned teachers for a section/term with attendance for a specific date.
     * Returns: class_section_id, teacher_id, position, firstname, lastname, satt_id, status, reason
     */

    /**
     * Group assignments by section (SECTION CODE) for a term.
     * Returns: [class_section_id => [ ['teacher_id'=>..,'position'=>'main|ta','firstname'=>..,'lastname'=>..], ... ]]
     */
    public function assignedByTerm(string $semester, string $schoolYear): array
    {
        $rows = $this->db->table('teacher_class tc')
            ->select('tc.class_section_id, tc.teacher_id AS teacher_id, tc.position, u.firstname, u.lastname')
            ->join('users u', 'u.id = tc.teacher_id', 'inner')
            ->where('tc.school_year', $schoolYear)
            ->orderBy("FIELD(tc.position,'main','ta')", '', false)
            ->orderBy('u.firstname', 'ASC')
            ->get()->getResultArray();

        $out = [];
        foreach ($rows as $r) {
            $sid = (int)$r['class_section_id'];
            $out[$sid][] = $r;
        }
        return $out;
    }


    /**
     * Resolve assignment bundles with a resolved section PK and label.
     * Shape: [$requestedCode] = ['resolved_id'=>int,'label'=>string,'teachers'=>[...]]
     */
    public function assignedByTermResolved(string $semester, string $schoolYear): array
    {
        $db = $this->db;

        // 1) PK matches (tc.class_section_id == cs.id)
        $pkRows = $db->table('teacher_class tc')
            // ...inside assignedByTermResolved
            ->select("
    tc.class_section_id AS requested_section_id,
    cs.id AS resolved_section_id,
    cs.class_section_name AS section_label,
    tc.teacher_id AS teacher_id,
    tc.position,
    u.firstname, u.lastname
", false)
            ->join('users u', 'u.id = tc.teacher_id', 'inner')
            ->join('classSection cs', 'cs.id = tc.class_section_id', 'inner')
            ->where('tc.school_year', $schoolYear)
            ->get()->getResultArray();

        $out = [];
        $pkTaken = [];
        foreach ($pkRows as $r) {
            $req = (int)($r['requested_section_id'] ?? 0);
            if (!isset($out[$req])) {
                $out[$req] = [
                    'resolved_id' => (int)$r['resolved_section_id'],
                    'label'       => trim((string)$r['section_label']),
                    'teachers'    => [],
                ];
                $pkTaken[$req] = true;
            }
            $out[$req]['teachers'][] = [
                'teacher_id' => (int)$r['teacher_id'],
                'position'   => (string)$r['position'],
                'firstname'  => (string)($r['firstname'] ?? ''),
                'lastname'   => (string)($r['lastname'] ?? ''),
            ];
        }

        // 2) CODE matches (tc.class_section_id == cs.class_section_id) if not matched by PK
        $codeRows = $db->table('teacher_class tc')
            ->select("
            tc.class_section_id AS requested_section_id,
            cs.id AS resolved_section_id,
            cs.class_section_name AS section_label,
            tc.user_id AS teacher_id,
            tc.position,
            u.firstname, u.lastname
        ", false)
            ->join('users u', 'u.id = tc.user_id', 'inner')
            ->join('classSection cs', 'cs.class_section_id = tc.class_section_id', 'inner')
            ->where('tc.school_year', $schoolYear)
            ->get()->getResultArray();

        foreach ($codeRows as $r) {
            $req = (int)($r['requested_section_id'] ?? 0);
            if (isset($pkTaken[$req])) continue;

            if (!isset($out[$req])) {
                $out[$req] = [
                    'resolved_id' => (int)$r['resolved_section_id'],
                    'label'       => trim((string)$r['section_label']),
                    'teachers'    => [],
                ];
            }
            $out[$req]['teachers'][] = [
                'teacher_id' => (int)$r['teacher_id'],
                'position'   => (string)$r['position'],
                'firstname'  => (string)($r['firstname'] ?? ''),
                'lastname'   => (string)($r['lastname'] ?? ''),
            ];
        }

        foreach ($out as $req => &$bundle) {
            if (empty($bundle['resolved_id'])) $bundle['resolved_id'] = (int)$req;
            if ($bundle['label'] === '')       $bundle['label']       = 'Section #' . (int)$req;
        }
        unset($bundle);

        return $out;
    }

    /** Group assignments by SECTION CODE (uses tc.class_section_id). */
    public function assignedByTermByCode(string $semester, string $schoolYear): array
    {
        $rows = $this->db->table('teacher_class tc')
            ->select('tc.class_section_id, tc.teacher_id AS teacher_id, tc.position, u.firstname, u.lastname')
            ->join('users u', 'u.id = tc.teacher_id', 'inner')
            ->where('tc.school_year', $schoolYear)
            ->orderBy('tc.class_section_id', 'ASC')
            ->orderBy("FIELD(tc.position,'main','ta')", '', false)
            ->orderBy('u.firstname', 'ASC')
            ->get()->getResultArray();

        $out = [];
        foreach ($rows as $r) {
            $code = (int)$r['class_section_id'];
            $out[$code][] = [
                'teacher_id' => (int)$r['teacher_id'],
                'position'   => (string)$r['position'],
                'firstname'  => (string)($r['firstname'] ?? ''),
                'lastname'   => (string)($r['lastname'] ?? ''),
            ];
        }
        return $out;
    }


    /** Assigned + status for a single day by SECTION CODE (joins staff_attendance). */
    public function assignedWithStatusForDayByCode(
        int $sectionCode,
        string $date,
        string $semester,
        string $schoolYear
    ): array {
        $db = $this->db;
        $join = "t.user_id = tc.teacher_id
             AND t.class_section_id = tc.class_section_id
             AND t.date = " . $db->escape($date) . "
             AND t.semester = " . $db->escape($semester) . "
             AND t.school_year = " . $db->escape($schoolYear);

        return $db->table('teacher_class tc')
            ->select("
            tc.class_section_id,
            tc.teacher_id AS teacher_id,
            tc.position,
            u.firstname, u.lastname,
            t.id AS satt_id, t.status, t.reason
        ", false)
            ->join('users u', 'u.id = tc.teacher_id', 'inner')
            ->join($this->table . ' t', $join, 'left') // staff_attendance
            ->where('tc.class_section_id', $sectionCode)
            ->where('tc.school_year', $schoolYear)
            ->orderBy("FIELD(tc.position,'main','ta')", '', false)
            ->orderBy('u.firstname', 'ASC')
            ->get()->getResultArray();
    }


    /**
     * Statuses for a range (by SECTION CODE) from staff_attendance.
     * Returns: [sectionCode][teacherId][Y-m-d] = ['status'=>..,'reason'=>..]
     */
    public function statusesByRange(array $sectionCodes, string $startDate, string $endDate, string $semester, string $schoolYear): array
    {
        if (empty($sectionCodes)) return [];

        $rows = $this->db->table($this->table) // staff_attendance
            ->select('class_section_id, user_id, DATE_FORMAT(date,"%Y-%m-%d") AS d, status, reason', false)
            ->whereIn('class_section_id', $sectionCodes) // SECTION CODE
            ->where('date >=', $startDate)
            ->where('date <=', $endDate)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->get()->getResultArray();

        $map = [];
        foreach ($rows as $r) {
            $sid = (int)$r['class_section_id'];
            $tid = (int)$r['user_id'];
            $day = (string)$r['d'];
            $map[$sid][$tid][$day] = [
                'status' => strtolower((string)$r['status']),
                'reason' => $r['reason'],
            ];
        }
        return $map;
    }
}
