<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\PrintRequestModel;
use App\Models\UserModel;
use App\Models\ClassModel;
use App\Models\ClassSectionModel;
use App\Models\ConfigurationModel;
use App\Models\TeacherClassModel;
use App\Models\AdminNotificationSubjectModel;
use App\Models\NotificationModel;
use App\Models\UserNotificationModel;
use App\Services\SemesterRangeService;
use CodeIgniter\Exceptions\PageNotFoundException;

class PrintRequests extends BaseController
{
    protected $printRequestModel;
    protected $userModel;
    protected $classModel;
    protected $teacherClassModel;
    protected $configModel;
    protected $classSectionModel;
    protected $adminNotificationSubjectModel;
    protected $notificationModel;
    protected $userNotificationModel;
    
    // Define the upload path as a constant or property for easy maintenance
    protected $uploadPath = WRITEPATH . 'uploads/print_requests/';

    public function __construct()
    {
        $this->printRequestModel = new PrintRequestModel();
        $this->userModel = new UserModel();
        $this->classModel = new ClassModel();
        $this->teacherClassModel = new TeacherClassModel();
        $this->configModel = new ConfigurationModel();
        $this->classSectionModel = new ClassSectionModel();
        $this->adminNotificationSubjectModel = new AdminNotificationSubjectModel();
        $this->notificationModel = new NotificationModel();
        $this->userNotificationModel = new UserNotificationModel();
        helper(['form', 'url']);
    }

    public function teacher_index()
    {
        $teacher_id = session()->get('user_id');
        $context = $this->resolveSchoolYearContext();
        $schoolYear = $context->yearName();
        
        $printRequestsQuery = $this->printRequestModel
            ->select('print_requests.*, admins.firstname as admin_firstname, admins.lastname as admin_lastname')
            ->join('users as admins', 'admins.id = print_requests.admin_id', 'left')
            ->join('classSection cs', 'cs.class_section_id = print_requests.class_id', 'left')
            ->where('print_requests.teacher_id', $teacher_id);
        $this->applyPrintRequestSchoolYearScope($printRequestsQuery, $schoolYear);
        $data['print_requests'] = $printRequestsQuery
            ->orderBy('print_requests.required_by', 'DESC')
            ->orderBy('print_requests.id', 'DESC')
            ->findAll();

        $teacher_classes = $this->teacherClassModel->getClassAssignmentsByUserId((int) $teacher_id, $schoolYear);
        $data['class_id'] = !empty($teacher_classes) ? $teacher_classes[0]['class_section_id'] : null;
        $dateOptions = $this->buildRequiredByOptions();
        $data['sundays'] = $dateOptions['sundays'];
        $data['times'] = $dateOptions['times'];
        $data['isSchoolYearReadonly'] = $context->isReadonly();

        return view('print_requests/teacher_index', $data);
    }

    public function admin_index()
    {
        $context = $this->resolveSchoolYearContext();
        $schoolYear = $context->yearName();

        $printRequestsQuery = $this->printRequestModel
            ->select('
                print_requests.*,
                u.firstname,
                u.lastname,
                cs.class_section_name,
                admins.firstname AS admin_firstname,
                admins.lastname AS admin_lastname
            ')
            ->join('users u', 'u.id = print_requests.teacher_id', 'left')
            ->join('classSection cs', 'cs.class_section_id = print_requests.class_id', 'left')
            ->join('users admins', 'admins.id = print_requests.admin_id', 'left');

        $this->applyPrintRequestSchoolYearScope($printRequestsQuery, $schoolYear);

        $data['print_requests'] = $printRequestsQuery
            ->orderBy("
                CASE
                    WHEN print_requests.status = 'not_assigned' THEN 1
                    WHEN print_requests.status = 'assigned' THEN 2
                    WHEN print_requests.status = 'done' THEN 3
                    WHEN print_requests.status = 'delivered' THEN 4
                    ELSE 5
                END
            ", 'ASC', false)
            ->orderBy('print_requests.required_by', 'ASC')
            ->findAll();
        $data['isSchoolYearReadonly'] = $context->isReadonly();

        return view('print_requests/admin_index', $data);
    }

    public function create()
    {
        $context = $this->resolveSchoolYearContext();
        $this->assertSchoolYearWritable($context);
        $schoolYear = $context->yearName();
        $validationRules = [
            'file' => 'uploaded[file]|max_size[file,5120]|ext_in[file,pdf,jpg,png,jpeg,doc,docx,txt]',
            'page_selection' => 'permit_empty|regex_match[/^\\s*\\d+(?:\\s*-\\s*\\d+)?(?:\\s*,\\s*\\d+(?:\\s*-\\s*\\d+)?)*\\s*$/]',
            'num_copies' => 'required|integer|greater_than[0]',
            'required_by' => 'required|valid_date',
            'pickup_method' => 'required'
        ];

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

        $teacher_id = session()->get('user_id');

        $file = $this->request->getFile('file');
        $newName = '';
        if ($file->isValid() && !$file->hasMoved()) {
            $newName = $file->getRandomName();
            // Moved to WRITEPATH
            $file->move($this->uploadPath, $newName);
        }

        $data = [
            'teacher_id' => $teacher_id,
            'class_id' => $this->request->getPost('class_id'),
            'file_path' => $newName,
            'page_selection' => trim((string) $this->request->getPost('page_selection')),
            'num_copies' => $this->request->getPost('num_copies'),
            'required_by' => $this->request->getPost('required_by'),
            'pickup_method' => $this->request->getPost('pickup_method'),
            'status' => 'not_assigned',
        ] + $this->printRequestSchoolYearData($schoolYear);

        $printRequestId = (int) $this->printRequestModel->insert($data, true);
        if ($printRequestId > 0) {
            $this->notifyAdminsForPrintRequest($printRequestId, $data, 'created');
        }

        return redirect()->to('teacher/print-requests')->with('success', 'Print request created successfully.');
    }

    public function update($id)
    {
        $request = $this->printRequestModel->find($id);
        if (!$request) {
            return redirect()->back()->with('error', 'Print request not found.');
        }

        // Case 1: Admin status update
        if ($this->request->getPost('status')) {
            $this->assertSchoolYearWritable($this->resolveSchoolYearContext());
            $user_id = session()->get('user_id');
            $current_status = $request['status'];
            $new_status = $this->request->getPost('status');

            $allowed_transitions = [
                'not_assigned' => ['assigned'],
                'assigned' => ['not_assigned', 'done'],
                'done' => ['delivered'],
                'delivered' => []
            ];

            if (!isset($allowed_transitions[$current_status]) || !in_array($new_status, $allowed_transitions[$current_status])) {
                if ($current_status == $new_status) {
                    return redirect()->to('admin/print-requests');
                }
                return redirect()->to('admin/print-requests')->with('error', 'Invalid status transition.');
            }

            $data = ['status' => $new_status];
            if ($new_status == 'assigned') {
                $data['admin_id'] = $user_id;
            } elseif ($new_status == 'not_assigned') {
                $data['admin_id'] = null;
            }

            $this->printRequestModel->update($id, $data);
            return redirect()->to('admin/print-requests')->with('success', 'Status updated successfully.');
        }

        // Case 2: Teacher edit
        if ($this->request->getPost('num_copies')) {
            $this->assertSchoolYearWritable($this->resolveSchoolYearContext());
            $user_id = session()->get('user_id');
            if ($request['teacher_id'] != $user_id) {
                return redirect()->to('teacher/print-requests')->with('error', 'You are not authorized to edit this request.');
            }
            if (!in_array($request['status'], ['not_assigned', 'assigned'])) {
                return redirect()->to('teacher/print-requests')->with('error', 'Cannot edit a request that is already being processed.');
            }

            $validationRules = [
                'num_copies' => 'required|integer|greater_than[0]',
                'required_by' => 'required|valid_date',
                'pickup_method' => 'required|in_list[Self Pickup,Delivered to class]'
            ];

            $pageSelectionRule = 'regex_match[/^\\s*\\d+(?:\\s*-\\s*\\d+)?(?:\\s*,\\s*\\d+(?:\\s*-\\s*\\d+)?)*\\s*$/]';
            $validationRules['page_selection'] = 'permit_empty|' . $pageSelectionRule;
            
            $hasNewFile = $this->request->getFile('file') && $this->request->getFile('file')->isValid();
            if ($hasNewFile) {
                $validationRules['file'] = 'uploaded[file]|max_size[file,5120]|ext_in[file,pdf,jpg,png,jpeg,doc,docx,txt]';
            }
    
            if (!$this->validate($validationRules)) {
                return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
            }
    
            $data = [
                'num_copies' => $this->request->getPost('num_copies'),
                'required_by' => $this->request->getPost('required_by'),
                'pickup_method' => $this->request->getPost('pickup_method'),
                'page_selection' => trim((string) $this->request->getPost('page_selection')),
            ];

            $file = $this->request->getFile('file');
            if ($file && $file->isValid() && !$file->hasMoved()) {
                // Remove old file from WRITEPATH
                if ($request['file_path'] && file_exists($this->uploadPath . $request['file_path'])) {
                    @unlink($this->uploadPath . $request['file_path']);
                }
                $newName = $file->getRandomName();
                $file->move($this->uploadPath, $newName);
                $data['file_path'] = $newName;
            }

            $this->printRequestModel->update($id, $data);
            $notifyPayload = array_merge($request, $data, ['id' => $id]);
            $this->notifyAdminsForPrintRequest($id, $notifyPayload, 'updated');
            return redirect()->to('teacher/print-requests')->with('success', 'Print request updated successfully.');
        }

        return redirect()->back()->with('error', 'Invalid request data.');
    }
    
    public function delete($id)
    {
        $this->assertSchoolYearWritable($this->resolveSchoolYearContext());
        $teacher_id = session()->get('user_id');
        $request = $this->printRequestModel->find($id);

        if (!$request) {
            return redirect()->to('teacher/print-requests')->with('error', 'Print request not found.');
        }

        if ($request['teacher_id'] != $teacher_id) {
            return redirect()->to('teacher/print-requests')->with('error', 'You are not authorized to delete this request.');
        }

        if (!in_array($request['status'], ['not_assigned', 'assigned'])) {
            return redirect()->to('teacher/print-requests')->with('error', 'Cannot delete a request that is already being processed.');
        }

        $this->notifyAdminsForPrintRequest($id, $request, 'deleted');

        // Delete file from WRITEPATH
        if ($request['file_path'] && file_exists($this->uploadPath . $request['file_path'])) {
            @unlink($this->uploadPath . $request['file_path']);
        }

        $this->printRequestModel->delete($id);

        return redirect()->to('teacher/print-requests')->with('success', 'Print request deleted successfully.');
    }

    public function copy($id)
    {
        $teacher_id = session()->get('user_id');
        $context = $this->resolveSchoolYearContext();
        $this->assertSchoolYearWritable($context);
        $schoolYear = $context->yearName();
        $request = $this->printRequestModel->find($id);

        if (!$request) {
            return redirect()->to('teacher/print-requests')->with('error', 'Print request not found.');
        }

        if ($request['teacher_id'] != $teacher_id) {
            return redirect()->to('teacher/print-requests')->with('error', 'You are not authorized to copy this request.');
        }

        $filePath = trim((string) ($request['file_path'] ?? ''));
        $candidates = [
            $this->uploadPath . $filePath,
            FCPATH . 'uploads/print_requests/' . $filePath,
        ];
        $fileExists = false;
        foreach ($candidates as $candidate) {
            if ($filePath !== '' && file_exists($candidate)) {
                $fileExists = true;
                break;
            }
        }

        if (!$fileExists) {
            return redirect()->to('teacher/print-requests')->with('error', 'The original file is unavailable for copying.');
        }

        $data = [
            'teacher_id' => $teacher_id,
            'class_id' => $request['class_id'],
            'file_path' => $filePath,
            'page_selection' => $request['page_selection'] ?? null,
            'num_copies' => $request['num_copies'],
            'required_by' => $request['required_by'],
            'pickup_method' => $request['pickup_method'],
            'status' => 'not_assigned',
        ] + $this->printRequestSchoolYearData($schoolYear);

        $copiedId = (int) $this->printRequestModel->insert($data, true);
        if ($copiedId > 0) {
            $this->notifyAdminsForPrintRequest($copiedId, $data, 'created');
        }

        return redirect()->to('teacher/print-requests')->with('success', 'Print request copied successfully.');
    }

    public function createCopy()
    {
        $context = $this->resolveSchoolYearContext();
        $this->assertSchoolYearWritable($context);
        $schoolYear = $context->yearName();
        $validationRules = [
            'num_copies' => 'required|integer|greater_than[0]',
            'required_by' => 'required|valid_date',
            'pickup_method' => 'required'
        ];

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

        $teacher_id = session()->get('user_id');

        $data = [
            'teacher_id' => $teacher_id,
            'class_id' => $this->request->getPost('class_id'),
            'file_path' => '',
            'page_selection' => null,
            'num_copies' => $this->request->getPost('num_copies'),
            'required_by' => $this->request->getPost('required_by'),
            'pickup_method' => $this->request->getPost('pickup_method'),
            'status' => 'not_assigned',
        ] + $this->printRequestSchoolYearData($schoolYear);

        $printRequestId = (int) $this->printRequestModel->insert($data, true);
        if ($printRequestId > 0) {
            $this->notifyAdminsForPrintRequest($printRequestId, $data, 'created');
        }

        return redirect()->to('teacher/print-requests')->with('success', 'Copy request submitted. Please hand the original document to the copy center.');
    }

    private function buildRequiredByOptions(): array
    {
        $tz = new \DateTimeZone('America/Chicago');
        $currentDate = new \DateTime('now', $tz);
        $currentYear = (int)$currentDate->format('Y');
        $endYear = ($currentDate->format('n') > 6) ? $currentYear + 1 : $currentYear;
        $endDate = new \DateTime("first Sunday of June {$endYear}", $tz);
        $endDate->modify('+1 week');

        $sundays = [];
        $date = new \DateTime('now', $tz);
        $date->setTime(0, 0, 0);
        if ($date->format('w') !== '0') {
            $date->modify('next Sunday');
        }
        while ($date < $endDate) {
            $sundays[] = $date->format('Y-m-d');
            $date->modify('+1 week');
        }

        $times = [];
        $start = new \DateTime('10:00', $tz);
        $end = new \DateTime('13:00', $tz);
        $interval = new \DateInterval('PT15M');
        $period = new \DatePeriod($start, $interval, $end);
        foreach ($period as $time) {
            $times[] = $time->format('H:i');
        }

        return [
            'sundays' => $sundays,
            'times' => $times,
        ];
    }

    private function applyPrintRequestSchoolYearScope($query, string $schoolYear): void
    {
        if ($schoolYear === '') {
            return;
        }

        $hasPrintRequestYear = $this->printRequestsHaveSchoolYearColumn();
        $hasClassSectionYear = db_connect()->fieldExists('school_year', 'classSection');
        $range = (new SemesterRangeService($this->configModel))->getSchoolYearRange($schoolYear);
        [$rangeStart, $rangeEnd] = $range;

        if ($hasPrintRequestYear && $hasClassSectionYear) {
            $query->groupStart()
                ->where('print_requests.school_year', $schoolYear)
                ->orGroupStart()
                    ->groupStart()
                        ->where('print_requests.school_year IS NULL', null, false)
                        ->orWhere('print_requests.school_year', '')
                    ->groupEnd()
                    ->where('cs.school_year', $schoolYear)
                ->groupEnd();

            if ($rangeStart !== '' && $rangeEnd !== '') {
                $query
                    ->orGroupStart()
                        ->groupStart()
                            ->where('print_requests.school_year IS NULL', null, false)
                            ->orWhere('print_requests.school_year', '')
                        ->groupEnd()
                        ->groupStart()
                            ->where('cs.school_year IS NULL', null, false)
                            ->orWhere('cs.school_year', '')
                        ->groupEnd()
                        ->where('print_requests.required_by >=', $rangeStart . ' 00:00:00')
                        ->where('print_requests.required_by <=', $rangeEnd . ' 23:59:59')
                    ->groupEnd();
            }

            $query->groupEnd();
            return;
        }

        if (! $hasPrintRequestYear && $hasClassSectionYear) {
            $query->where('cs.school_year', $schoolYear);
            return;
        }

        if ($rangeStart !== '' && $rangeEnd !== '') {
            if ($hasPrintRequestYear) {
                $query
                    ->groupStart()
                        ->where('print_requests.school_year', $schoolYear)
                        ->orGroupStart()
                            ->groupStart()
                                ->where('print_requests.school_year IS NULL', null, false)
                                ->orWhere('print_requests.school_year', '')
                            ->groupEnd()
                            ->where('print_requests.required_by >=', $rangeStart . ' 00:00:00')
                            ->where('print_requests.required_by <=', $rangeEnd . ' 23:59:59')
                        ->groupEnd()
                    ->groupEnd();
                return;
            }

            $query
                ->where('print_requests.required_by >=', $rangeStart . ' 00:00:00')
                ->where('print_requests.required_by <=', $rangeEnd . ' 23:59:59');
            return;
        }

        if ($hasPrintRequestYear) {
            $query->where('print_requests.school_year', $schoolYear);
        }
    }

    private function printRequestSchoolYearData(string $schoolYear): array
    {
        if ($schoolYear === '' || ! $this->printRequestsHaveSchoolYearColumn()) {
            return [];
        }

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

    private function printRequestsHaveSchoolYearColumn(): bool
    {
        return db_connect()->fieldExists('school_year', 'print_requests');
    }

    public function serveFile(string $filename, string $mode = 'inline')
    {
        $safeName = basename(trim($filename));
        if ($safeName === '') {
            throw new PageNotFoundException('File not specified.');
        }

        $candidates = [
            WRITEPATH . 'uploads/print_requests/' . $safeName,
            FCPATH . 'uploads/print_requests/' . $safeName,
        ];

        $path = null;
        foreach ($candidates as $candidate) {
            if (is_file($candidate)) {
                $path = $candidate;
                break;
            }
        }

        if (!$path) {
            throw new PageNotFoundException('File not found.');
        }

        $mime = mime_content_type($path) ?: 'application/octet-stream';
        $disposition = strtolower(trim($mode)) === 'download' ? 'attachment' : 'inline';

        return $this->response
            ->download($path, null)
            ->setFileName($safeName)
            ->setHeader('Content-Type', $mime)
            ->setHeader('Content-Disposition', sprintf('%s; filename="%s"', $disposition, $safeName));
    }

    private function notifyAdminsForPrintRequest(int $printRequestId, array $requestData, string $event = 'created'): void
    {
        try {
            $db = \Config\Database::connect();
        } catch (\Throwable $e) {
            return;
        }

        if (
            !$db->tableExists('admin_notification_subjects') ||
            !$db->tableExists('notifications') ||
            !$db->tableExists('user_notifications')
        ) {
            log_message('error', 'Print request notifications skipped: required tables missing.');
            return;
        }

        $rows = $this->adminNotificationSubjectModel
            ->select('admin_id')
            ->where('subject', 'print_requests')
            ->findAll();

        $adminIds = array_values(array_unique(array_map('intval', array_column($rows, 'admin_id'))));
        if (empty($adminIds)) {
            log_message('info', 'Print request notifications skipped: no admins subscribed to print_requests.');
            return;
        }

        $teacherId = (int) ($requestData['teacher_id'] ?? 0);
        $teacherName = '';
        if ($teacherId > 0) {
            $teacher = $this->userModel->find($teacherId);
            $teacherName = trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? ''));
        }
        if ($teacherName === '') {
            $teacherName = $teacherId > 0 ? ('Teacher #' . $teacherId) : 'Teacher';
        }

        $className = '';
        $classId = $requestData['class_id'] ?? null;
        if ($classId !== null && $classId !== '') {
            $className = (string) ($this->classSectionModel->getClassSectionNameBySectionId($classId) ?? '');
        }

        $event = strtolower(trim($event));
        $eventMap = [
            'created' => [
                'title' => 'New Print Request',
                'intro' => 'A new print request has been submitted.',
                'lead' => 'New print request from ',
            ],
            'updated' => [
                'title' => 'Print Request Updated',
                'intro' => 'A print request has been updated.',
                'lead' => 'Updated print request from ',
            ],
            'deleted' => [
                'title' => 'Print Request Removed',
                'intro' => 'A print request has been deleted.',
                'lead' => 'Print request removed for ',
            ],
        ];
        $eventConfig = $eventMap[$event] ?? $eventMap['created'];

        $filePath = trim((string) ($requestData['file_path'] ?? ''));
        $isCopyRequest = $filePath === '';
        $messageParts = [$eventConfig['lead'] . $teacherName];
        if ($className !== '') {
            $messageParts[] = 'Class: ' . $className;
        }
        if (!empty($requestData['num_copies'])) {
            $messageParts[] = 'Copies: ' . (int) $requestData['num_copies'];
        }
        if (!empty($requestData['required_by'])) {
            $messageParts[] = 'Needed by: ' . $requestData['required_by'];
        }
        if (!empty($requestData['page_selection'])) {
            $messageParts[] = 'Pages: ' . $requestData['page_selection'];
        }
        if (!empty($requestData['pickup_method'])) {
            $messageParts[] = 'Pickup: ' . $requestData['pickup_method'];
        }
        $message = implode(' | ', $messageParts);
        $actionUrl = site_url('admin/print-requests');

        $payload = [
            'title' => $eventConfig['title'],
            'message' => $message,
            'target_group' => 'admin',
            'delivery_channels' => 'in_app,email',
            'priority' => 'normal',
            'status' => 'pending',
            'action_url' => $isCopyRequest ? '' : $actionUrl,
            'scheduled_at' => utc_now(),
            'school_year' => $this->configModel->getConfig('school_year'),
            'semester' => $this->configModel->getConfig('semester'),
        ];

        $notificationFields = $db->getFieldNames('notifications');
        if (is_array($notificationFields) && !empty($notificationFields)) {
            $payload = array_intersect_key($payload, array_flip($notificationFields));
        }

        $notificationId = (int) $this->notificationModel->insert($payload, true);
        if ($notificationId <= 0) {
            return;
        }

        $userNotificationFields = $db->getFieldNames('user_notifications');
        $userFieldMap = is_array($userNotificationFields) ? array_flip($userNotificationFields) : [];

        $batch = [];
        foreach ($adminIds as $adminId) {
            if ($adminId <= 0) {
                continue;
            }
            $row = [
                'notification_id' => $notificationId,
                'user_id' => $adminId,
                'is_read' => 0,
                'delivered' => 0,
            ];
            if (!empty($userFieldMap)) {
                $row = array_intersect_key($row, $userFieldMap);
            }
            $batch[] = $row;
        }

        if (!empty($batch)) {
            $this->userNotificationModel->insertBatch($batch);
        }

        $adminRows = $this->userModel
            ->select('id, firstname, lastname, email')
            ->whereIn('id', $adminIds)
            ->findAll();

        if (empty($adminRows)) {
            return;
        }

        $mailer = new \App\Controllers\View\EmailController();
        $subject = $eventConfig['title'];
        $body = '<p>' . esc($eventConfig['intro']) . '</p>'
            . '<p><strong>From:</strong> ' . esc($teacherName) . '</p>'
            . ($className !== '' ? '<p><strong>Class:</strong> ' . esc($className) . '</p>' : '')
            . (!empty($requestData['num_copies']) ? '<p><strong>Copies:</strong> ' . (int) $requestData['num_copies'] . '</p>' : '')
            . (!empty($requestData['required_by']) ? '<p><strong>Needed by:</strong> ' . esc($requestData['required_by']) . '</p>' : '')
            . (!empty($requestData['page_selection']) ? '<p><strong>Pages:</strong> ' . esc($requestData['page_selection']) . '</p>' : '')
            . (!empty($requestData['pickup_method']) ? '<p><strong>Pickup Method:</strong> ' . esc($requestData['pickup_method']) . '</p>' : '')
            . ($isCopyRequest ? '' : '<p><a href="' . esc($actionUrl) . '">View print requests</a></p>');

        foreach ($adminRows as $adminRow) {
            $email = trim((string) ($adminRow['email'] ?? ''));
            if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                continue;
            }
            $mailer->sendEmail($email, $subject, $body, 'notifications');
        }
    }
}
