<?php

namespace App\Controllers;

use App\Controllers\ClassProgressController;
use App\Models\ClassProgressAttachmentModel;
use App\Models\ClassProgressReportModel;
use App\Models\ConfigurationModel;
use App\Models\EnrollmentModel;
use App\Services\SemesterRangeService;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Exceptions\PageNotFoundException;
use Config\Database;

class ParentProgressController extends BaseController
{
    protected ClassProgressReportModel $reportModel;
    protected ClassProgressAttachmentModel $attachmentModel;
    protected EnrollmentModel $enrollmentModel;
    protected ConfigurationModel $configModel;
    protected BaseConnection $db;
    private ?array $parentSectionIds = null;
    private array $parentSectionIdsByYear = [];

    public function __construct()
    {
        helper(['url', 'form']);
        $this->reportModel = new ClassProgressReportModel();
        $this->attachmentModel = new ClassProgressAttachmentModel();
        $this->enrollmentModel = new EnrollmentModel();
        $this->configModel = new ConfigurationModel();
        $this->db = Database::connect();
    }

    public function index()
    {
        $schoolYear = $this->currentSchoolYearName();
        $students = $this->getParentStudents();
        $sectionIds = array_values(array_unique(array_filter(array_map(
            static fn (array $student): int => (int) ($student['class_section_id'] ?? 0),
            $students
        ))));
        $subjectSections = ClassProgressController::SUBJECT_SECTIONS;

        $rows = [];
        if (! empty($sectionIds)) {
            $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('class_progress_reports.class_section_id', $sectionIds);
            $this->applyProgressSchoolYearScope($builder, $schoolYear);

            $rows = $builder
                ->orderBy('week_start', 'DESC')
                ->findAll();
        }

        $studentReportGroups = [];
        foreach ($students as $student) {
            $studentId = (int) ($student['student_id'] ?? 0);
            if ($studentId === 0) {
                continue;
            }
            $classSectionId = (int) ($student['class_section_id'] ?? 0);
            $studentRows = $classSectionId
                ? array_values(array_filter(
                    $rows,
                    static fn (array $row): bool => (int) ($row['class_section_id'] ?? 0) === $classSectionId
                ))
                : [];
            $studentReportGroups[$studentId] = $this->groupReportsByWeek($studentRows);
        }

        return view('parent/class_progress_list', [
            'students' => $students,
            'studentReportGroups' => $studentReportGroups,
            'subjectSections' => $subjectSections,
            'hasStudents' => ! empty($students),
        ]);
    }

    public function view($id)
    {
        $schoolYear = $this->currentSchoolYearName();
        $row = $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)
            ->first();

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

        $reportSchoolYear = trim((string) ($row['school_year'] ?? ''));
        $effectiveSchoolYear = $reportSchoolYear !== '' ? $reportSchoolYear : $schoolYear;

        if (! $this->isSectionAccessible($row['class_section_id'] ?? null, $effectiveSchoolYear)) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
        $row['flags'] = $this->decodeFlags($row['flags_json']);

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

        $attachmentMap = $this->loadAttachmentsForReports(array_column($weeklyReports, 'id'));
        foreach ($weeklyReports as &$report) {
            $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('parent/class_progress_view', [
            'row' => $row,
            'weeklyReports' => $weeklyReports,
            'subjectSections' => ClassProgressController::SUBJECT_SECTIONS,
        ]);
    }

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

        $reportSchoolYear = trim((string) ($row['school_year'] ?? ''));
        $effectiveSchoolYear = $reportSchoolYear !== '' ? $reportSchoolYear : $this->currentSchoolYearName();

        if (! $this->isSectionAccessible($row['class_section_id'] ?? null, $effectiveSchoolYear) || 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.');
        }

        $report = $this->reportModel->find((int) ($attachment['report_id'] ?? 0));
        if (! $report) {
            throw new PageNotFoundException('Attachment not found.');
        }

        $reportSchoolYear = trim((string) ($report['school_year'] ?? ''));
        $effectiveSchoolYear = $reportSchoolYear !== '' ? $reportSchoolYear : $this->currentSchoolYearName();

        if (! $this->isSectionAccessible($report['class_section_id'] ?? null, $effectiveSchoolYear)) {
            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 getParentSectionIds(?string $schoolYear = null): array
    {
        $schoolYear = trim((string) ($schoolYear ?? $this->currentSchoolYearName()));
        if ($schoolYear === $this->currentSchoolYearName() && $this->parentSectionIds !== null) {
            return $this->parentSectionIds;
        }

        if (isset($this->parentSectionIdsByYear[$schoolYear])) {
            return $this->parentSectionIdsByYear[$schoolYear];
        }

        $parentId = (int) session()->get('user_id');
        if ($parentId === 0) {
            $this->parentSectionIdsByYear[$schoolYear] = [];
            if ($schoolYear === $this->currentSchoolYearName()) {
                $this->parentSectionIds = [];
            }
            return [];
        }

        $rows = $this->enrollmentModel
            ->select('class_section_id')
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->where('is_withdrawn', 0)
            ->groupBy('class_section_id')
            ->findAll();

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

        $ids = array_values(array_unique($ids));
        $this->parentSectionIdsByYear[$schoolYear] = $ids;
        if ($schoolYear === $this->currentSchoolYearName()) {
            $this->parentSectionIds = $ids;
        }
        return $ids;
    }

    protected function buildSectionOptions(array $sectionIds): array
    {
        if (empty($sectionIds)) {
            return [];
        }

        $rows = $this->db->table('classSection')
            ->select('class_section_id, class_section_name')
            ->whereIn('class_section_id', $sectionIds)
            ->orderBy('class_section_name', 'ASC')
            ->get()
            ->getResultArray();

        $options = [];
        foreach ($rows as $row) {
            $options[(int) ($row['class_section_id'] ?? 0)] = $row['class_section_name'] ?? 'Unknown section';
        }
        return $options;
    }

    protected function getParentStudents(): array
    {
        $parentId = (int) session()->get('user_id');
        if ($parentId === 0) {
            return [];
        }

        $rows = $this->db->table('enrollments e')
            ->select('e.student_id, e.class_section_id, e.updated_at, e.created_at, s.firstname, s.lastname, cs.class_section_name')
            ->join('students s', 's.id = e.student_id')
            ->join('classSection cs', 'cs.class_section_id = e.class_section_id', 'left')
            ->where('e.parent_id', $parentId)
            ->where('e.school_year', $this->currentSchoolYearName())
            ->where('e.is_withdrawn', 0)
            ->orderBy('e.updated_at', 'DESC')
            ->orderBy('e.created_at', 'DESC')
            ->get()
            ->getResultArray();

        $students = [];
        foreach ($rows as $row) {
            $studentId = (int) ($row['student_id'] ?? 0);
            if ($studentId === 0 || isset($students[$studentId])) {
                continue;
            }
            $students[$studentId] = $row;
        }

        return array_values($students);
    }

    protected function groupReportsByWeek(array $rows): array
    {
        $reportGroups = [];
        foreach ($rows as $row) {
            $row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
            $weekStart = $row['week_start'] ?? '';
            $sectionId = (int) ($row['class_section_id'] ?? 0);
            if ($weekStart === '' || $sectionId === 0) {
                continue;
            }
            $key = $weekStart . ':' . $sectionId;
            if (! isset($reportGroups[$key])) {
                $reportGroups[$key] = [
                    'week_start' => $row['week_start'] ?? '',
                    'week_end' => $row['week_end'] ?? '',
                    'class_section_name' => $row['class_section_name'] ?? '',
                    'class_section_id' => $sectionId,
                    'reports' => [],
                ];
            }
            $reportGroups[$key]['reports'][$row['subject']] = $row;
        }
        return $reportGroups;
    }

    protected function isSectionAccessible(?int $classSectionId, ?string $schoolYear = null): bool
    {
        if (! $classSectionId) {
            return false;
        }
        return in_array((int) $classSectionId, $this->getParentSectionIds($schoolYear), true);
    }

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

        $hasReportYear = $this->db->fieldExists('school_year', 'class_progress_reports');
        $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 ($rangeStart !== '' && $rangeEnd !== '') {
            $builder
                ->where('class_progress_reports.week_start >=', $rangeStart)
                ->where('class_progress_reports.week_start <=', $rangeEnd);
        }
    }

    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 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 decodeFlags(?string $json): array
    {
        if (! $json) {
            return [];
        }

        $flags = json_decode($json, true);
        return is_array($flags) ? $flags : [];
    }
}
