<?php

namespace App\Controllers;

use App\Models\ClassProgressReportModel;
use App\Models\ClassProgressAttachmentModel;
use App\Models\ConfigurationModel;
use App\Models\SubjectCurriculumModel;
use App\Models\TeacherClassModel;
use App\Services\SemesterRangeService;
use CodeIgniter\Exceptions\PageNotFoundException;

class ClassProgressController extends BaseController
{
    public const STATUS_OPTIONS = [
        'on_track' => 'On track',
        'slightly_behind' => 'Slightly behind',
        'behind' => 'Behind',
    ];
    private const DEFAULT_STATUS = 'on_track';
    public const SUBJECT_SECTIONS = [
        'islamic' => [
            'label' => 'Islamic Studies',
            'db_subject' => 'Islamic Studies',
        ],
        'quran' => [
            'label' => 'Quran/Arabic',
            'db_subject' => 'Quran/Arabic',
        ],
    ];

    /** Unit column for teacher custom Islamic / Quran rows; must match teacher form JS. Segment: "Custom / {text}". */
    public const CUSTOM_UNIT_ROW_LABEL = 'Custom';

    protected ClassProgressReportModel $reportModel;
    protected ClassProgressAttachmentModel $attachmentModel;
    protected TeacherClassModel $teacherClassModel;
    protected ConfigurationModel $configModel;
    protected string $attachmentStoragePath;
    protected string $attachmentPublicBase;
    protected SubjectCurriculumModel $curriculumModel;

    public function __construct()
    {
        helper(['form', 'url']);
        $this->reportModel = new ClassProgressReportModel();
        $this->attachmentModel = new ClassProgressAttachmentModel();
        $this->teacherClassModel = new TeacherClassModel();
        $this->configModel = new ConfigurationModel();
        $this->curriculumModel = new SubjectCurriculumModel();
        $this->attachmentStoragePath = WRITEPATH . 'uploads/class_material/';
        $this->attachmentPublicBase = 'writable/uploads/class_material/';
    }

    public function create()
    {
        $context = $this->resolveSchoolYearContext();
        $teacherId = (int) session()->get('user_id');
        $assignments = $this->loadTeacherSections($teacherId);
        $first = $assignments[0] ?? null;
        $classSectionId = $first['class_section_id'] ?? null;
        $classSectionName = $first['class_section_name'] ?? null;
        $classId = $first['class_id'] ?? null;
        $sundayOptions = $this->buildSundayOptions();
        $defaultWeekStart = $this->pickDefaultWeekStart($sundayOptions);
        $subjectCurriculum = [];
        if ($classId) {
            foreach (self::SUBJECT_SECTIONS as $slug => $section) {
                $subjectCurriculum[$slug] = $this->curriculumModel->getOptionsForClass((int) $classId, $slug);
            }
        }
        $data = [
            'subjectSections' => self::SUBJECT_SECTIONS,
            'subjectCurriculum' => $subjectCurriculum,
            'classSectionId' => $classSectionId,
            'classSectionName' => $classSectionName,
            'classId' => $classId,
            'sundayOptions' => $sundayOptions,
            'defaultWeekStart' => $defaultWeekStart,
            'isSchoolYearReadonly' => $context->isReadonly(),
        ];
        return view('teacher/class_progress_submit', $data);
    }

    public function store()
    {
        $this->assertSchoolYearWritable($this->resolveSchoolYearContext());
        [, $schoolYear] = $this->resolveCurrentTerm();
        $subjectSections = self::SUBJECT_SECTIONS;
        $rules = [
            'class_section_id' => 'required|integer',
            'week_start' => 'required|valid_date[Y-m-d]',
            'week_end' => 'required|valid_date[Y-m-d]',
            'support_needed' => 'permit_empty|string',
            'flags' => 'permit_empty',
        ];

        foreach ($subjectSections as $slug => $section) {
            $rules["covered_$slug"] = 'required|string';
            $rules["homework_$slug"] = 'permit_empty|string';
            $rules["unit_{$slug}.*"] = 'permit_empty|string|max_length[120]';
            $rules["chapter_{$slug}.*"] = 'permit_empty|string|max_length[120]';
        }

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        if (! $this->hasIslamicUnitSelection()) {
            return redirect()->back()->withInput()->with('error', 'Please select at least one Islamic Studies unit.');
        }

        $attachmentErrors = $this->validateAttachmentFiles($subjectSections);
        if (! empty($attachmentErrors)) {
            return redirect()->back()->withInput()->with('errors', $attachmentErrors);
        }

        $weekStart = (string) $this->request->getPost('week_start');
        $weekEnd = (string) $this->request->getPost('week_end');
        if ($weekStart && ! $weekEnd) {
            $weekEnd = $this->buildWeekEndFromStart($weekStart);
        }
        if ($weekStart && $weekEnd && strtotime($weekEnd) < strtotime($weekStart)) {
            return redirect()->back()->withInput()->with('error', 'Week end must be the same as or after the week start.');
        }

        $teacherId = (int) session()->get('user_id');
        $classSectionId = $this->request->getPost('class_section_id');
        $classSectionId = $classSectionId ? (int) $classSectionId : null;
        if (! $classSectionId) {
            return redirect()->back()->withInput()->with('error', 'No class assignment found for this report.');
        }

        $confirmOverwrite = (bool) $this->request->getPost('confirm_overwrite');
        $existingReports = $this->reportModel
            ->select('id')
            ->where('class_section_id', $classSectionId)
            ->where('week_start', $weekStart)
            ->where('teacher_id', $teacherId);
        $this->applyProgressSchoolYearScope($existingReports, $schoolYear);
        $existingReports = $existingReports->findAll();

        if (! $confirmOverwrite && ! empty($existingReports)) {
            return redirect()->back()
                ->withInput()
                ->with('warning', 'A progress report already exists for this week, are you sure you want to override it?')
                ->with('confirm_overwrite', true);
        }

        if ($confirmOverwrite && ! empty($existingReports)) {
            $existingIds = array_values(array_filter(array_map(
                static fn (array $row): int => (int) ($row['id'] ?? 0),
                $existingReports
            )));
            if (! empty($existingIds)) {
                $this->attachmentModel->whereIn('report_id', $existingIds)->delete();
                $this->reportModel->whereIn('id', $existingIds)->delete();
            }
        }

        $status = self::DEFAULT_STATUS;

        $reportsCreated = 0;
        foreach ($subjectSections as $slug => $section) {
            $covered = trim((string) $this->request->getPost("covered_$slug"));
            if ($covered === '') {
                continue;
            }
            $homework = trim((string) $this->request->getPost("homework_$slug"));
            $unitTitle = $this->buildUnitChapterSummary($slug);

            $data = [
                'teacher_id' => $teacherId,
                'class_section_id' => $classSectionId,
                'week_start' => $weekStart,
                'week_end' => $weekEnd,
                'subject' => $section['db_subject'] ?? $section['label'] ?? $slug,
                'unit_title' => $unitTitle,
                'covered' => $covered,
                'homework' => $homework ?: null,
                'status' => $status,
                'flags_json' => $this->normalizeFlags($this->request->getPost('flags')),
            ] + $this->progressReportSchoolYearData($schoolYear);

            $reportId = $this->reportModel->insert($data, true);
            $attachmentField = "attachment_$slug";
            $attachments = $this->request->getFileMultiple($attachmentField) ?? [];
            $storedAttachments = $this->storeAttachments($reportId, $attachments);
            if (! empty($storedAttachments)) {
                $this->attachmentModel->insertBatch($storedAttachments);
                $this->reportModel->update($reportId, ['attachment_path' => $storedAttachments[0]['file_path']]);
            }
            $reportsCreated++;
        }

        if ($reportsCreated === 0) {
            return redirect()->back()->withInput()->with('error', 'Please provide progress for at least one subject.');
        }

        return redirect()->to('teacher/progress/submit')->with('success', 'Progress reports saved.');
    }

    public function history()
    {
        $teacherId = (int) session()->get('user_id');
        [$semester, $schoolYear] = $this->resolveCurrentTerm();
        $assignments = $this->loadTeacherSections($teacherId);
        $selectedSectionId = (int) $this->request->getGet('class_section_id');
        $validSectionIds = array_column($assignments, 'class_section_id');
        if ($selectedSectionId === 0 && ! empty($validSectionIds)) {
            $selectedSectionId = $validSectionIds[0];
        }
        if ($selectedSectionId && ! in_array($selectedSectionId, $validSectionIds, true)) {
            $selectedSectionId = $validSectionIds[0] ?? null;
        }
        $allowedTeacherIds = $this->resolveAssignedTeacherIds($selectedSectionId, $semester, $schoolYear);
        if (empty($allowedTeacherIds)) {
            $allowedTeacherIds = [$teacherId];
        }
        $builder = $this->reportModel
            ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
            ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
            ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
            ->whereIn('teacher_id', $allowedTeacherIds);
        $this->applyProgressSchoolYearScope($builder, $schoolYear, true);
        if ($selectedSectionId) {
            $builder->where('class_progress_reports.class_section_id', $selectedSectionId);
        }
        $rows = $builder
            ->orderBy('week_start', 'DESC')
            ->findAll();

        $reportGroups = [];
        foreach ($rows as $row) {
            $row['status_label'] = self::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
            $key = $row['week_start'] ?? '';
            if ($key === '') {
                continue;
            }
            if (! isset($reportGroups[$key])) {
                $reportGroups[$key] = [
                    'week_start' => $row['week_start'],
                    'week_end' => $row['week_end'],
                    'class_section_name' => $row['class_section_name'] ?? '',
                    'reports' => [],
                ];
            }
            $reportGroups[$key]['reports'][$row['subject']] = $row;
        }

        $sectionOptions = [];
        foreach ($assignments as $assignment) {
            $sectionOptions[$assignment['class_section_id']] = $assignment['class_section_name'] ?? '';
        }
        return view('teacher/class_progress_history', [
            'reportGroups' => $reportGroups,
            'subjectSections' => self::SUBJECT_SECTIONS,
            'classSectionOptions' => $sectionOptions,
            'selectedSectionId' => $selectedSectionId,
            'schoolYear' => $schoolYear,
            'isSchoolYearReadonly' => $this->resolveSchoolYearContext()->isReadonly(),
        ]);
    }

    public function view($id)
    {
        $teacherId = (int) session()->get('user_id');
        [$semester, $schoolYear] = $this->resolveCurrentTerm();
        $rowQuery = $this->reportModel
            ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
            ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
            ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
            ->where('class_progress_reports.id', (int) $id);
        $this->applyProgressSchoolYearScope($rowQuery, $schoolYear, true);
        $row = $rowQuery->first();

        if (! $row) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $allowedTeacherIds = $this->resolveAssignedTeacherIds((int) $row['class_section_id'], $semester, $schoolYear);
        if (empty($allowedTeacherIds)) {
            if ($teacherId !== (int) $row['teacher_id']) {
                throw new PageNotFoundException('Progress report not found.');
            }
            $allowedTeacherIds = [(int) $row['teacher_id']];
        } elseif (! in_array($teacherId, $allowedTeacherIds, true)) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $row['status_label'] = self::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
        $weeklyReportsQuery = $this->reportModel
            ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
            ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
            ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
            ->whereIn('teacher_id', $allowedTeacherIds)
            ->where('class_progress_reports.class_section_id', $row['class_section_id'])
            ->where('week_start', $row['week_start']);
        $this->applyProgressSchoolYearScope($weeklyReportsQuery, $schoolYear);
        $weeklyReports = $weeklyReportsQuery
            ->orderBy('subject', 'ASC')
            ->findAll();

        $attachmentMap = $this->loadAttachmentsForReports(array_column($weeklyReports, 'id'));
        foreach ($weeklyReports as &$report) {
            $report['status_label'] = self::STATUS_OPTIONS[$report['status']] ?? 'Unknown';
            $report['attachments'] = $attachmentMap[$report['id']] ?? [];
            if (empty($report['attachments']) && ! empty($report['attachment_path'])) {
                $report['attachments'][] = [
                    'id' => $report['id'],
                    'name' => basename((string) $report['attachment_path']),
                    'legacy' => true,
                ];
            }
        }

        return view('teacher/class_progress_view', [
            'row' => $row,
            'weeklyReports' => $weeklyReports,
            'subjectSections' => self::SUBJECT_SECTIONS,
        ]);
    }

    public function edit($id)
    {
        $teacherId = (int) session()->get('user_id');
        [$semester, $schoolYear] = $this->resolveCurrentTerm();
        $rowQuery = $this->reportModel
            ->select('class_progress_reports.*, cs.class_section_name')
            ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
            ->where('class_progress_reports.id', (int) $id);
        $this->applyProgressSchoolYearScope($rowQuery, $schoolYear);
        $row = $rowQuery->first();

        if (! $row) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $allowedTeacherIds = $this->resolveAssignedTeacherIds((int) $row['class_section_id'], $semester, $schoolYear);
        if (empty($allowedTeacherIds)) {
            if ($teacherId !== (int) $row['teacher_id']) {
                throw new PageNotFoundException('Progress report not found.');
            }
            $allowedTeacherIds = [(int) $row['teacher_id']];
        } elseif (! in_array($teacherId, $allowedTeacherIds, true)) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $weeklyReportsQuery = $this->reportModel
            ->select('class_progress_reports.*')
            ->whereIn('teacher_id', $allowedTeacherIds)
            ->where('class_section_id', $row['class_section_id'])
            ->where('week_start', $row['week_start']);
        $this->applyProgressSchoolYearScope($weeklyReportsQuery, $schoolYear);
        $weeklyReports = $weeklyReportsQuery
            ->orderBy('subject', 'ASC')
            ->findAll();

        $reportMap = [];
        foreach ($weeklyReports as $report) {
            $subject = (string) ($report['subject'] ?? '');
            if ($subject === '') {
                continue;
            }
            $reportMap[$subject] = $report;
        }

        $subjectReports = [];
        foreach (self::SUBJECT_SECTIONS as $slug => $section) {
            $subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
            $report = $reportMap[$subjectName] ?? null;
            if (! $report) {
                continue;
            }
            $parsed = $this->parseUnitChapterSummary((string) ($report['unit_title'] ?? ''));
            $subjectReports[$slug] = [
                'report_id' => (int) ($report['id'] ?? 0),
                'covered' => $report['covered'] ?? '',
                'homework' => $report['homework'] ?? '',
                'unit_title' => $report['unit_title'] ?? '',
                'unit_values' => $parsed['units'],
                'chapter_values' => $parsed['chapters'],
            ];
        }

        $assignments = $this->loadTeacherSections($teacherId);
        $classId = null;
        $classSectionName = $row['class_section_name'] ?? '';
        foreach ($assignments as $assignment) {
            if ((int) ($assignment['class_section_id'] ?? 0) === (int) $row['class_section_id']) {
                $classId = $assignment['class_id'] ?? null;
                $classSectionName = $assignment['class_section_name'] ?? $classSectionName;
                break;
            }
        }

        $subjectCurriculum = [];
        if ($classId) {
            foreach (self::SUBJECT_SECTIONS as $slug => $section) {
                $subjectCurriculum[$slug] = $this->curriculumModel->getOptionsForClass((int) $classId, $slug);
            }
        }

        $sundayOptions = $this->buildSundayOptions();
        if (!in_array($row['week_start'], $sundayOptions, true)) {
            array_unshift($sundayOptions, $row['week_start']);
        }

        return view('teacher/class_progress_submit', [
            'subjectSections' => self::SUBJECT_SECTIONS,
            'subjectCurriculum' => $subjectCurriculum,
            'classSectionId' => $row['class_section_id'],
            'classSectionName' => $classSectionName,
            'classId' => $classId,
            'sundayOptions' => $sundayOptions,
            'defaultWeekStart' => $row['week_start'],
            'existingWeekEnd' => $row['week_end'],
            'existingReports' => $subjectReports,
            'isEdit' => true,
            'formAction' => base_url('teacher/progress/update/' . (int) $row['id']),
            'submitLabel' => 'Update Progress',
            'isSchoolYearReadonly' => $this->resolveSchoolYearContext()->isReadonly(),
        ]);
    }

    public function update($id)
    {
        $this->assertSchoolYearWritable($this->resolveSchoolYearContext());
        $teacherId = (int) session()->get('user_id');
        [$semester, $schoolYear] = $this->resolveCurrentTerm();
        $rowQuery = $this->reportModel
            ->where('class_progress_reports.id', (int) $id);
        $this->applyProgressSchoolYearScope($rowQuery, $schoolYear);
        $row = $rowQuery->first();
        if (! $row) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $allowedTeacherIds = $this->resolveAssignedTeacherIds((int) $row['class_section_id'], $semester, $schoolYear);
        if (empty($allowedTeacherIds)) {
            if ($teacherId !== (int) $row['teacher_id']) {
                throw new PageNotFoundException('Progress report not found.');
            }
            $allowedTeacherIds = [(int) $row['teacher_id']];
        } elseif (! in_array($teacherId, $allowedTeacherIds, true)) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $subjectSections = self::SUBJECT_SECTIONS;
        $rules = [
            'class_section_id' => 'required|integer',
            'week_start' => 'required|valid_date[Y-m-d]',
            'week_end' => 'required|valid_date[Y-m-d]',
        ];
        foreach ($subjectSections as $slug => $section) {
            $rules["covered_$slug"] = 'required|string';
            $rules["homework_$slug"] = 'permit_empty|string';
            $rules["unit_{$slug}.*"] = 'permit_empty|string|max_length[120]';
            $rules["chapter_{$slug}.*"] = 'permit_empty|string|max_length[120]';
        }

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        if (! $this->hasIslamicUnitSelection()) {
            return redirect()->back()->withInput()->with('error', 'Please select at least one Islamic Studies unit.');
        }

        $attachmentErrors = $this->validateAttachmentFiles($subjectSections);
        if (! empty($attachmentErrors)) {
            return redirect()->back()->withInput()->with('errors', $attachmentErrors);
        }

        $weekStart = (string) $this->request->getPost('week_start');
        $weekEnd = (string) $this->request->getPost('week_end');
        if ($weekStart && ! $weekEnd) {
            $weekEnd = $this->buildWeekEndFromStart($weekStart);
        }
        if ($weekStart && $weekEnd && strtotime($weekEnd) < strtotime($weekStart)) {
            return redirect()->back()->withInput()->with('error', 'Week end must be the same as or after the week start.');
        }

        $classSectionId = (int) ($row['class_section_id'] ?? 0);
        if ($classSectionId === 0) {
            return redirect()->back()->withInput()->with('error', 'No class assignment found for this report.');
        }

        $confirmOverwrite = (bool) $this->request->getPost('confirm_overwrite');
        if ($weekStart && $weekStart !== (string) ($row['week_start'] ?? '')) {
            $conflicts = $this->reportModel
                ->select('id')
                ->where('class_section_id', $classSectionId)
                ->where('week_start', $weekStart)
                ->where('teacher_id', $teacherId);
            $this->applyProgressSchoolYearScope($conflicts, $schoolYear);
            $conflicts = $conflicts->findAll();
            if (! $confirmOverwrite && ! empty($conflicts)) {
                return redirect()->back()
                    ->withInput()
                    ->with('warning', 'A progress report already exists for this week, are you sure you want to override it?')
                    ->with('confirm_overwrite', true);
            }
            if ($confirmOverwrite && ! empty($conflicts)) {
                $conflictIds = array_values(array_filter(array_map(
                    static fn (array $row): int => (int) ($row['id'] ?? 0),
                    $conflicts
                )));
                if (! empty($conflictIds)) {
                    $this->attachmentModel->whereIn('report_id', $conflictIds)->delete();
                    $this->reportModel->whereIn('id', $conflictIds)->delete();
                }
            }
        }

        $weeklyReportsQuery = $this->reportModel
            ->select('class_progress_reports.*')
            ->whereIn('teacher_id', $allowedTeacherIds)
            ->where('class_section_id', $classSectionId)
            ->where('week_start', $row['week_start']);
        $this->applyProgressSchoolYearScope($weeklyReportsQuery, $schoolYear);
        $weeklyReports = $weeklyReportsQuery
            ->orderBy('subject', 'ASC')
            ->findAll();

        $reportMap = [];
        foreach ($weeklyReports as $report) {
            $subject = (string) ($report['subject'] ?? '');
            if ($subject === '') {
                continue;
            }
            $reportMap[$subject] = $report;
        }

        $reportsUpdated = 0;
        $flagsInput = $this->request->getPost('flags');
        foreach ($subjectSections as $slug => $section) {
            $covered = trim((string) $this->request->getPost("covered_$slug"));
            if ($covered === '') {
                continue;
            }
            $homework = trim((string) $this->request->getPost("homework_$slug"));
            $unitTitle = $this->buildUnitChapterSummary($slug);
            $subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
            $existing = $reportMap[$subjectName] ?? null;
            if ($unitTitle === null && $existing) {
                $unitTitle = $existing['unit_title'] ?? null;
            }

            $data = [
                'class_section_id' => $classSectionId,
                'week_start' => $weekStart,
                'week_end' => $weekEnd,
                'subject' => $subjectName,
                'unit_title' => $unitTitle,
                'covered' => $covered,
                'homework' => $homework ?: null,
            ] + $this->progressReportSchoolYearData($schoolYear);
            if ($flagsInput !== null) {
                $data['flags_json'] = $this->normalizeFlags($flagsInput);
            }

            if ($existing) {
                $this->reportModel->update((int) $existing['id'], $data);
                $reportId = (int) $existing['id'];
            } else {
                $data['teacher_id'] = $teacherId;
                $data['status'] = self::DEFAULT_STATUS;
                $reportId = (int) $this->reportModel->insert($data, true);
            }

            $attachmentField = "attachment_$slug";
            $attachments = $this->request->getFileMultiple($attachmentField) ?? [];
            $storedAttachments = $this->storeAttachments($reportId, $attachments);
            if (! empty($storedAttachments)) {
                $this->attachmentModel->insertBatch($storedAttachments);
                if (empty($existing['attachment_path'] ?? '')) {
                    $this->reportModel->update($reportId, ['attachment_path' => $storedAttachments[0]['file_path']]);
                }
            }
            $reportsUpdated++;
        }

        if ($reportsUpdated === 0) {
            return redirect()->back()->withInput()->with('error', 'Please provide progress for at least one subject.');
        }

        return redirect()->to('teacher/progress/history')->with('success', 'Progress reports updated.');
    }

    public function attachment($id)
    {
        $row = $this->reportModel->find((int)$id);
        if (! $row || empty($row['attachment_path'])) {
            throw new PageNotFoundException('Attachment not found.');
        }

        $file = $this->resolveAttachmentFile($row);
        if (! $file) {
            throw new PageNotFoundException('Attachment missing.');
        }

        return $this->response->download($file, null)->setFileName(basename($file));
    }

    public function attachmentFile($id)
    {
        $attachment = $this->attachmentModel->find((int) $id);
        if (! $attachment || empty($attachment['file_path'])) {
            throw new PageNotFoundException('Attachment not found.');
        }

        $file = $this->resolveAttachmentPath($attachment['file_path']);
        if (! $file) {
            throw new PageNotFoundException('Attachment missing.');
        }

        $downloadName = $attachment['original_name'] ?: basename($file);
        return $this->response->download($file, null)->setFileName($downloadName);
    }

    protected function resolveAttachmentFile(array $row): ?string
    {
        $path = trim((string) ($row['attachment_path'] ?? ''));
        if ($path === '') {
            return null;
        }
        return $this->resolveAttachmentPath($path);
    }

    protected function resolveAttachmentPath(string $path): ?string
    {
        $relative = preg_replace('#^writable/uploads/#', '', $path);
        $absolute = WRITEPATH . 'uploads/' . ltrim($relative, '/');
        return is_file($absolute) ? $absolute : null;
    }

    protected function validateAttachmentFiles(array $subjectSections): array
    {
        $errors = [];
        foreach ($subjectSections as $slug => $section) {
            $label = $section['label'] ?? $slug;
            $field = "attachment_$slug";
            $files = $this->request->getFileMultiple($field) ?? [];
            foreach ($files as $file) {
                if (! $file || $file->getError() === UPLOAD_ERR_NO_FILE) {
                    continue;
                }
                if (! $file->isValid()) {
                    $errors[] = "Invalid attachment uploaded for {$label}.";
                    continue;
                }
                if ($file->getSize() > 5 * 1024 * 1024) {
                    $errors[] = "Each attachment for {$label} must be 5MB or smaller.";
                }
                $ext = strtolower((string) $file->getClientExtension());
                if ($ext === '' || ! in_array($ext, ['pdf', 'jpg', 'jpeg', 'png'], true)) {
                    $errors[] = "Only PDF, JPG, JPEG, or PNG files are allowed for {$label}.";
                }
            }
        }

        return $errors;
    }

    protected function storeAttachments(int $reportId, array $attachments): array
    {
        if (empty($attachments)) {
            return [];
        }

        $stored = [];
        $now = date('Y-m-d H:i:s');
        foreach ($attachments as $file) {
            if (! $file || $file->getError() === UPLOAD_ERR_NO_FILE) {
                continue;
            }
            if (! $file->isValid() || $file->hasMoved()) {
                continue;
            }
            $stored[] = [
                'report_id' => $reportId,
                'file_path' => $this->storeAttachment($file),
                'original_name' => $file->getClientName(),
                'mime_type' => $file->getClientMimeType(),
                'file_size' => $file->getSize(),
                'created_at' => $now,
            ];
        }

        return $stored;
    }

    protected function loadAttachmentsForReports(array $reportIds): array
    {
        $reportIds = array_values(array_filter(array_map('intval', $reportIds)));
        if (empty($reportIds)) {
            return [];
        }

        $rows = $this->attachmentModel
            ->whereIn('report_id', $reportIds)
            ->orderBy('id', 'ASC')
            ->findAll();

        $map = [];
        foreach ($rows as $row) {
            $reportId = (int) ($row['report_id'] ?? 0);
            if ($reportId === 0) {
                continue;
            }
            $map[$reportId][] = [
                'id' => (int) $row['id'],
                'name' => $row['original_name'] ?: basename((string) $row['file_path']),
            ];
        }

        return $map;
    }

    protected function loadTeacherSections(int $teacherId): array
    {
        [$semester, $schoolYear] = $this->resolveCurrentTerm();
        return $this->teacherClassModel->getClassAssignmentsByUserId($teacherId, $schoolYear, $semester);
    }

    protected function normalizeFlags($flags): ?string
    {
        $flags = array_values(array_filter((array) $flags, static fn ($item) => $item !== '' && $item !== null));
        return $flags ? json_encode($flags) : null;
    }

    /**
     * Split stored unit_title into curriculum lines vs custom teacher-typed subjects ("Custom / …").
     * Keep in sync with teacher form, {@see buildUnitChapterSummary()}, and admin views.
     *
     * @return array{curriculum: list<string>, custom: list<string>}
     */
    public static function splitUnitTitleForDisplay(string $unitTitle): array
    {
        $unitTitle = trim($unitTitle);
        if ($unitTitle === '') {
            return ['curriculum' => [], 'custom' => []];
        }

        $segments = preg_split('/\s*;\s*/', $unitTitle, -1, PREG_SPLIT_NO_EMPTY);
        $curriculum = [];
        $custom = [];
        foreach ($segments as $seg) {
            $seg = trim((string) $seg);
            if ($seg === '') {
                continue;
            }
            if (preg_match('/^Custom\s*\/\s*(.+)$/iu', $seg, $m)) {
                $custom[] = trim($m[1]);
            } else {
                $curriculum[] = $seg;
            }
        }

        return ['curriculum' => $curriculum, 'custom' => $custom];
    }

    protected function buildUnitChapterSummary(string $slug): ?string
    {
        $unitValues = array_map('trim', (array) $this->request->getPost("unit_$slug"));
        $chapterValues = array_map('trim', (array) $this->request->getPost("chapter_$slug"));
        $parts = [];
        $count = max(count($unitValues), count($chapterValues));
        for ($i = 0; $i < $count; $i++) {
            $unit = $unitValues[$i] ?? '';
            $chapter = $chapterValues[$i] ?? '';
            if ($unit === '' && $chapter === '') {
                continue;
            }
            if (strcasecmp($unit, self::CUSTOM_UNIT_ROW_LABEL) === 0 && $chapter !== '') {
                $unit = self::CUSTOM_UNIT_ROW_LABEL;
            }
            $segment = $unit;
            if ($chapter !== '') {
                $segment = $segment !== '' ? $unit . ' / ' . $chapter : $chapter;
            }
            if ($segment === '') {
                continue;
            }
            $parts[] = $segment;
        }
        if (! $parts) {
            return null;
        }
        $summary = implode(' ; ', $parts);

        return mb_strlen($summary) > 120 ? mb_substr($summary, 0, 120) : $summary;
    }

    protected function hasIslamicUnitSelection(): bool
    {
        $unitValues = array_map('trim', (array) $this->request->getPost('unit_islamic'));
        $chapterValues = array_map('trim', (array) $this->request->getPost('chapter_islamic'));
        $count = max(count($unitValues), count($chapterValues));
        for ($i = 0; $i < $count; $i++) {
            $unit = $unitValues[$i] ?? '';
            $chapter = $chapterValues[$i] ?? '';
            if ($unit !== '' || $chapter !== '') {
                return true;
            }
        }

        return false;
    }

    protected function parseUnitChapterSummary(string $summary): array
    {
        $summary = trim($summary);
        if ($summary === '') {
            return ['units' => [], 'chapters' => []];
        }

        $units = [];
        $chapters = [];
        $segments = preg_split('/\s*;\s*/', $summary, -1, PREG_SPLIT_NO_EMPTY);
        foreach ($segments as $segment) {
            $segment = trim($segment);
            if ($segment === '') {
                continue;
            }
            if (preg_match('/^Custom\s*\/\s*(.+)$/iu', $segment, $m)) {
                $units[] = self::CUSTOM_UNIT_ROW_LABEL;
                $chapters[] = trim($m[1]);

                continue;
            }
            $parts = preg_split('/\s*\/\s*/', $segment, 2);
            if (count($parts) === 2) {
                $u = trim($parts[0]);
                if (strcasecmp($u, self::CUSTOM_UNIT_ROW_LABEL) === 0) {
                    $u = self::CUSTOM_UNIT_ROW_LABEL;
                }
                $units[] = $u;
                $chapters[] = trim($parts[1]);
            } else {
                $units[] = $segment;
                $chapters[] = '';
            }
        }

        return ['units' => $units, 'chapters' => $chapters];
    }

    protected function buildSundayOptions(int $count = 12): array
    {
        $range = $this->resolveProgressDateRange();
        if ($range === null) {
            return $this->buildUpcomingSundayOptions($count);
        }
        [$rangeStart, $rangeEnd] = $range;

        try {
            $start = new \DateTime($rangeStart);
            $end = new \DateTime($rangeEnd);
        } catch (\Exception $e) {
            return $this->buildUpcomingSundayOptions($count);
        }

        if ($end < $start) {
            [$start, $end] = [$end, $start];
        }

        $weekday = (int) $start->format('w');
        if ($weekday !== 0) {
            $start->modify('next sunday');
        }

        $options = [];
        while ($start <= $end) {
            $options[] = $start->format('Y-m-d');
            $start->modify('+7 days');
        }

        return $options;
    }

    protected function buildUpcomingSundayOptions(int $count): array
    {
        $start = new \DateTime('today');
        $weekday = (int) $start->format('w');
        if ($weekday !== 0) {
            $start->modify('next sunday');
        }

        $options = [];
        for ($i = 0; $i < $count; $i++) {
            $options[] = $start->format('Y-m-d');
            $start->modify('+7 days');
        }

        return $options;
    }

    protected function resolveProgressDateRange(): ?array
    {
        $schoolYear = $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
        if ($schoolYear === '') {
            return null;
        }

        $semesterResolver = new SemesterRangeService($this->configModel);
        $semester = $semesterResolver->normalizeSemester((string) ($this->configModel->getConfig('semester') ?? ''));
        if ($semester === '') {
            $semester = $semesterResolver->getSemesterForDate();
        }

        if ($semester !== '') {
            $range = $semesterResolver->getSemesterRange($schoolYear, $semester);
            if ($range !== null) {
                return $range;
            }
        }

        return $semesterResolver->getSchoolYearRange($schoolYear);
    }

    protected function pickDefaultWeekStart(array $options): string
    {
        if (empty($options)) {
            return '';
        }

        $today = date('Y-m-d');
        $default = '';
        foreach ($options as $option) {
            if ($option <= $today) {
                $default = $option;
                continue;
            }
            break;
        }

        return $default !== '' ? $default : $options[0];
    }

    protected function buildWeekEndFromStart(string $weekStart): string
    {
        try {
            $dt = new \DateTime($weekStart);
            $dt->modify('+6 days');
            return $dt->format('Y-m-d');
        } catch (\Exception $e) {
            return $weekStart;
        }
    }

    protected function storeAttachment($file): string
    {
        $this->ensureAttachmentPath();
        $name = $file->getRandomName();
        $file->move($this->attachmentStoragePath, $name);
        return $this->attachmentPublicBase . $name;
    }

    protected function ensureAttachmentPath(): void
    {
        if (! is_dir($this->attachmentStoragePath)) {
            mkdir($this->attachmentStoragePath, 0755, true);
        }
    }

    protected function resolveCurrentTerm(): array
    {
        $schoolYear = $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
        $semester = (string) ($this->configModel->getConfig('semester') ?? '');
        return [$semester, $schoolYear];
    }

    protected function applyProgressSchoolYearScope($builder, string $schoolYear, bool $canScopeBySection = false): void
    {
        if ($schoolYear === '') {
            return;
        }

        $db = db_connect();
        $hasReportYear = $db->fieldExists('school_year', 'class_progress_reports');
        $hasSectionYear = $db->fieldExists('school_year', 'classSection');
        $semesterResolver = new SemesterRangeService($this->configModel);
        [$rangeStart, $rangeEnd] = $semesterResolver->getSchoolYearRange($schoolYear);

        if ($hasReportYear) {
            if ($rangeStart !== '' && $rangeEnd !== '') {
                $builder
                    ->groupStart()
                        ->where('class_progress_reports.school_year', $schoolYear)
                        ->orGroupStart()
                            ->groupStart()
                                ->where('class_progress_reports.school_year IS NULL', null, false)
                                ->orWhere('class_progress_reports.school_year', '')
                            ->groupEnd()
                            ->where('class_progress_reports.week_start >=', $rangeStart)
                            ->where('class_progress_reports.week_start <=', $rangeEnd)
                        ->groupEnd()
                    ->groupEnd();
                return;
            }

            $builder->where('class_progress_reports.school_year', $schoolYear);
            return;
        }

        if ($canScopeBySection && $hasSectionYear) {
            $builder->where('cs.school_year', $schoolYear);
            return;
        }

        if ($rangeStart !== '' && $rangeEnd !== '') {
            $builder
                ->where('class_progress_reports.week_start >=', $rangeStart)
                ->where('class_progress_reports.week_start <=', $rangeEnd);
        }
    }

    protected function progressReportSchoolYearData(string $schoolYear): array
    {
        if ($schoolYear === '' || ! db_connect()->fieldExists('school_year', 'class_progress_reports')) {
            return [];
        }

        return ['school_year' => $schoolYear];
    }

    protected function resolveAssignedTeacherIds(?int $classSectionId, string $semester, string $schoolYear): array
    {
        if (! $classSectionId || $schoolYear === '') {
            return [];
        }
        $rows = $this->teacherClassModel->assignedForSectionTerm($classSectionId, $semester, $schoolYear);
        $ids = [];
        foreach ($rows as $row) {
            $id = (int) ($row['teacher_id'] ?? 0);
            if ($id > 0) {
                $ids[$id] = true;
            }
        }
        return array_keys($ids);
    }
}
