<?php

namespace App\Models;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Model;

class StudentClassModel extends Model
{
    protected $table      = 'student_class';
    protected $primaryKey = 'id';
    protected $returnType = 'array';

    protected $useAutoIncrement = true;
    protected $protectFields    = true;

    protected $allowedFields = [
        'student_id',
        'class_section_id',
        'school_year',
        'is_event_only',
        'description',
        'updated_by',
        'created_at',
        'updated_at',
    ];

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

    protected $validationRules = [
        'student_id'      => 'required|integer',
        'class_section_id'=> 'permit_empty|integer',
        'school_year'     => 'required|max_length[20]',
        'is_event_only'   => 'permit_empty|in_list[0,1]',
        'updated_by'      => 'permit_empty|integer',
    ];

    protected $validationMessages = [];
    protected $skipValidation     = false;
    protected $cleanValidationRules = true;

    /**
     * Create a fresh builder for student_class.
     *
     * Using a fresh builder prevents WHERE conditions from a previous model
     * query from leaking into another query.
     */
    private function freshBuilder(): BaseBuilder
    {
        return $this->db->table($this->table);
    }

    /**
     * Create a fresh builder scoped to active students.
     */
    private function activeStudentsBuilder(): BaseBuilder
    {
        return $this->freshBuilder()
            ->select('student_class.*')
            ->join(
                'students',
                'students.id = student_class.student_id',
                'inner'
            )
            ->where('students.is_active', 1);
    }

    /**
     * Apply the active-student scope to the model.
     *
     * Prefer the dedicated retrieval methods below for new code because they
     * use fresh builders and cannot inherit stale model query state.
     */
    public function active(): self
    {
        return $this
            ->select('student_class.*')
            ->join(
                'students',
                'students.id = student_class.student_id',
                'inner'
            )
            ->where('students.is_active', 1);
    }

    /**
     * Return the most recent class section name assigned to a student.
     */
    public function getClassSectionNameByStudentId(
        int $studentId,
        ?string $schoolYear = null
    ): ?string {
        $builder = $this->freshBuilder()
            ->select('cs.class_section_name')
            ->join(
                'classSection cs',
                'cs.class_section_id = student_class.class_section_id',
                'inner'
            )
            ->where('student_class.student_id', $studentId)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            );

        if ($schoolYear !== null && trim($schoolYear) !== '') {
            $builder->where(
                'student_class.school_year',
                trim($schoolYear)
            );
        }

        $row = $builder
            ->orderBy('student_class.created_at', 'DESC')
            ->get()
            ->getRowArray();

        if (!$row) {
            return null;
        }

        $name = trim((string) ($row['class_section_name'] ?? ''));

        return $name !== '' ? $name : null;
    }

    /**
     * Get active students assigned to a class section.
     *
     * student_class is scoped by school year only. It has no semester column.
     */
    public function getClassStudents(
        int $classSectionId,
        ?string $schoolYear = null
    ): array {
        $builder = $this->activeStudentsBuilder()
            ->where(
                'student_class.class_section_id',
                $classSectionId
            )
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            );

        if ($schoolYear !== null && trim($schoolYear) !== '') {
            $builder->where(
                'student_class.school_year',
                trim($schoolYear)
            );
        }

        return $builder
            ->orderBy('students.lastname', 'ASC')
            ->orderBy('students.firstname', 'ASC')
            ->get()
            ->getResultArray();
    }

    /**
     * Return student IDs assigned to a class section for a school year.
     */
    public function getStudentIdsByClassSection(
        int $classSectionId,
        string $schoolYear
    ): array {
        $schoolYear = trim($schoolYear);

        $builder = $this->freshBuilder()
            ->select('student_class.student_id')
            ->join(
                'students',
                'students.id = student_class.student_id',
                'inner'
            )
            ->where(
                'student_class.class_section_id',
                $classSectionId
            )
            ->where('students.is_active', 1)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            );

        if ($schoolYear !== '') {
            $builder->where(
                'student_class.school_year',
                $schoolYear
            );
        }

        $rows = $builder
            ->groupBy('student_class.student_id')
            ->get()
            ->getResultArray();

        $studentIds = array_map(
            static fn(array $row): int =>
                (int) ($row['student_id'] ?? 0),
            $rows
        );

        return array_values(array_unique(array_filter(
            $studentIds,
            static fn(int $studentId): bool => $studentId > 0
        )));
    }

    /**
     * Return class section names assigned to a student for a school year.
     *
     * @return array|string
     */
    public function getClassSectionsByStudentId(
        int|string $studentId,
        string $schoolYear,
        bool $asArray = false
    ): array|string {
        $rows = $this->freshBuilder()
            ->select('cs.class_section_name')
            ->join(
                'classSection cs',
                'student_class.class_section_id = cs.class_section_id',
                'inner'
            )
            ->where('student_class.student_id', $studentId)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            )
            ->where(
                'student_class.school_year',
                trim($schoolYear)
            )
            ->orderBy('cs.class_section_name', 'ASC')
            ->get()
            ->getResultArray();

        $names = array_map(
            static fn(array $row): string =>
                trim((string) ($row['class_section_name'] ?? '')),
            $rows
        );

        $names = array_values(array_unique(array_filter(
            $names,
            static fn(string $name): bool => $name !== ''
        )));

        return $asArray ? $names : implode(', ', $names);
    }

    /**
     * Return class section names and indicate event-only assignments.
     *
     * @return array|string
     */
    public function getClassSectionsByStudentIdWithFlags(
        int|string $studentId,
        string $schoolYear,
        bool $asArray = false
    ): array|string {
        $rows = $this->freshBuilder()
            ->select(
                'cs.class_section_name, student_class.is_event_only'
            )
            ->join(
                'classSection cs',
                'student_class.class_section_id = cs.class_section_id',
                'inner'
            )
            ->where('student_class.student_id', $studentId)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            )
            ->where(
                'student_class.school_year',
                trim($schoolYear)
            )
            ->orderBy('cs.class_section_name', 'ASC')
            ->get()
            ->getResultArray();

        $names = [];

        foreach ($rows as $row) {
            $name = trim(
                (string) ($row['class_section_name'] ?? '')
            );

            if ($name === '') {
                continue;
            }

            if ((int) ($row['is_event_only'] ?? 0) === 1) {
                $name .= ' (Event)';
            }

            $names[] = $name;
        }

        $names = array_values(array_unique($names));

        return $asArray ? $names : implode(', ', $names);
    }

    /**
     * Return class-section IDs assigned to a student for a school year.
     *
     * @return int[]
     */
    public function getClassSectionIdsByStudentId(
        int|string $studentId,
        string $schoolYear
    ): array {
        $rows = $this->freshBuilder()
            ->select('student_class.class_section_id')
            ->where('student_class.student_id', $studentId)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            )
            ->where(
                'student_class.school_year',
                trim($schoolYear)
            )
            ->get()
            ->getResultArray();

        $ids = array_map(
            static fn(array $row): int =>
                (int) ($row['class_section_id'] ?? 0),
            $rows
        );

        return array_values(array_unique(array_filter(
            $ids,
            static fn(int $id): bool => $id > 0
        )));
    }

    /**
     * Return active students for a set of class-section IDs.
     */
    public function getStudentsByClassSectionIds(
        array $classSectionIds,
        ?string $schoolYear = null
    ): array {
        $classSectionIds = array_values(array_unique(array_filter(
            array_map('intval', $classSectionIds),
            static fn(int $id): bool => $id > 0
        )));

        if ($classSectionIds === []) {
            return [];
        }

        $builder = $this->freshBuilder()
            ->select([
                'students.id AS student_id',
                'students.firstname',
                'students.lastname',
                'students.school_id',
                'students.is_new',
                'students.photo_consent',
                'students.age',
                'student_class.school_year',
                'student_class.class_section_id',
                'student_class.is_event_only',
            ])
            ->join(
                'students',
                'students.id = student_class.student_id',
                'inner'
            )
            ->whereIn(
                'student_class.class_section_id',
                $classSectionIds
            )
            ->where('students.is_active', 1);

        if ($schoolYear !== null && trim($schoolYear) !== '') {
            $builder->where(
                'student_class.school_year',
                trim($schoolYear)
            );
        }

        return $builder
            ->orderBy('students.lastname', 'ASC')
            ->orderBy('students.firstname', 'ASC')
            ->get()
            ->getResultArray();
    }

    /**
     * Return the student's most recent non-event class grade.
     */
    public function getStudentGrade(
        int $studentId,
        ?string $schoolYear = null
    ): string {
        $builder = $this->freshBuilder()
            ->select('classSection.class_id')
            ->join(
                'classSection',
                'classSection.class_section_id = student_class.class_section_id',
                'inner'
            )
            ->where('student_class.student_id', $studentId)
            ->where('student_class.is_event_only', 0)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            );

        if ($schoolYear !== null && trim($schoolYear) !== '') {
            $builder->where(
                'student_class.school_year',
                trim($schoolYear)
            );
        }

        $row = $builder
            ->orderBy('student_class.created_at', 'DESC')
            ->get()
            ->getRowArray();

        $classId = trim((string) ($row['class_id'] ?? ''));

        if ($classId !== '') {
            return $classId;
        }

        log_message(
            'warning',
            'Student class or section not found for student ID: {studentId}',
            ['studentId' => $studentId]
        );

        return 'N/A';
    }

    /**
     * Determine whether a student has a non-event assignment for a year.
     */
    public function hasNonEventAssignment(
        int $studentId,
        string $schoolYear
    ): bool {
        return $this->freshBuilder()
            ->select('student_class.id')
            ->where('student_class.student_id', $studentId)
            ->where(
                'student_class.school_year',
                trim($schoolYear)
            )
            ->where('student_class.is_event_only', 0)
            ->limit(1)
            ->get()
            ->getRowArray() !== null;
    }

    /**
     * Return active student counts by class section.
     *
     * @return array<int, int>
     */
    public function getStudentCountsBySection(
        ?string $schoolYear = null
    ): array {
        $builder = $this->freshBuilder()
            ->select(
                'student_class.class_section_id, ' .
                'COUNT(DISTINCT student_class.student_id) AS total'
            )
            ->join(
                'students',
                'students.id = student_class.student_id',
                'inner'
            )
            ->where('students.is_active', 1)
            ->where(
                'student_class.class_section_id IS NOT NULL',
                null,
                false
            )
            ->groupBy('student_class.class_section_id');

        if ($schoolYear !== null && trim($schoolYear) !== '') {
            $builder->where(
                'student_class.school_year',
                trim($schoolYear)
            );
        }

        $rows = $builder
            ->get()
            ->getResultArray();

        $counts = [];

        foreach ($rows as $row) {
            $classSectionId = (int) (
                $row['class_section_id'] ?? 0
            );

            if ($classSectionId <= 0) {
                continue;
            }

            $counts[$classSectionId] = (int) (
                $row['total'] ?? 0
            );
        }

        return $counts;
    }
}