<?php

namespace App\Services\AttendanceTracking;

use App\Models\AttendanceData;
use App\Models\AttendanceTracking;
use App\Models\Student;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Throwable;

class AttendanceCaseQueryService
{
    public function __construct(
        protected Student $studentModel,
        protected AttendanceData $attendanceDataModel,
        protected AttendanceTracking $attendanceTrackingModel,
        protected ViolationRuleEngineService $violationRuleEngine,
        protected AttendanceParentLookupService $parentLookupService
    ) {
    }

    public function getStudentCase(
        int $studentId,
        ?string $codeParam = null,
        ?string $incidentDate = null,
        bool $includeNotified = false,
        bool $pendingOnly = false,
        ?string $schoolYearFilter = null,
        ?string $semesterFilter = null,
        ?string $defaultSchoolYear = null,
        ?string $defaultSemester = null
    ): array {
        $student = $this->studentModel->query()->find($studentId);
        if (!$student) {
            return [
                'success' => false,
                'message' => 'Student not found',
                'status'  => 404,
            ];
        }

        $student = $student->toArray();
        $student['name'] = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''));

        $codeParam = strtoupper((string) ($codeParam ?? ''));
        $incidentDate = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $incidentDate) ? (string) $incidentDate : '';

        $statusFilter = ['absent', 'late'];
        if (str_starts_with($codeParam, 'ABS')) {
            $statusFilter = ['absent'];
        } elseif (str_starts_with($codeParam, 'LATE')) {
            $statusFilter = ['late'];
        } elseif (str_starts_with($codeParam, 'MIX')) {
            $statusFilter = ['absent', 'late'];
        }

        $schoolYear = $schoolYearFilter
            ?? ($incidentDate !== ''
                ? $this->violationRuleEngine->schoolYearForDate($incidentDate, $defaultSchoolYear)
                : (string) $defaultSchoolYear);

        $semester = $semesterFilter;
        if ($semester === null && $codeParam === '' && $incidentDate === '') {
            $semester = (string) $defaultSemester;
        }

        [$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear ?: date('Y'));
        $today    = Carbon::today();
        $start    = Carbon::parse($syStart)->startOfDay();
        $rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay();

        $windowWeeks = $this->violationRuleEngine->windowWeeksForViolationCode($codeParam);
        $endYmd      = $incidentDate !== '' ? $incidentDate : date('Y-m-d');
        $startYmd    = $this->violationRuleEngine->activeWeekWindowStartYmd($endYmd, $windowWeeks, $schoolYear, $semester);

        $studentIdValues = [(int) $studentId];
        $studentCodeRaw = trim((string) ($student['student_code'] ?? $student['school_id'] ?? ''));
        $studentCodeValues = [];

        if ($studentCodeRaw !== '') {
            if (is_numeric($studentCodeRaw)) {
                $studentCodeNum = (int) $studentCodeRaw;
                if ($studentCodeNum > 0 && $studentCodeNum !== (int) $studentId) {
                    $studentIdValues[] = $studentCodeNum;
                }
            } else {
                $studentCodeValues[] = $studentCodeRaw;
            }
        }

        $studentIdValues = array_values(array_unique(array_filter($studentIdValues, fn ($v) => $v > 0)));
        $studentCodeValues = array_values(array_unique(array_filter($studentCodeValues, fn ($v) => $v !== '')));

        $qb = $this->attendanceDataModel->query()
            ->select([
                'id',
                'student_id',
                'class_id',
                'class_section_id',
                'date',
                'status',
                'reason',
                'school_year',
                'semester',
                'is_reported',
                'is_notified',
            ])
            ->where(function ($q) use ($studentIdValues, $studentCodeValues) {
                $q->whereIn('student_id', $studentIdValues);
                if (!empty($studentCodeValues)) {
                    $q->orWhereIn('student_id', $studentCodeValues);
                }
            })
            ->where('date', '>=', max($start->format('Y-m-d'), $startYmd))
            ->where('date', '<', $this->violationRuleEngine->dayBounds($endYmd)[1]);

        if ($statusFilter === ['absent']) {
            $qb->whereRaw("(LOWER(TRIM(status)) LIKE 'abs%' OR LOWER(TRIM(status)) = 'a')");
        } elseif ($statusFilter === ['late']) {
            $qb->whereRaw("(LOWER(TRIM(status)) LIKE 'late%' OR LOWER(TRIM(status)) = 'l')");
        } else {
            $qb->whereRaw("(LOWER(TRIM(status)) LIKE 'abs%' OR LOWER(TRIM(status)) LIKE 'late%' OR LOWER(TRIM(status)) IN ('a','l'))");
        }

        if ($pendingOnly) {
            $this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable());

            if (!$includeNotified) {
                $qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))");
            }
        }

        if ($schoolYearFilter !== null) {
            $qb->where('school_year', $schoolYearFilter);
        }

        if ($semesterFilter !== null) {
            $qb->where('semester', $semesterFilter);
        }

        $attendanceRows = $qb->orderByDesc('date')->get()->toArray();

        $data = [
            'student'                => $student,
            'attendance'             => $attendanceRows,
            'violation_code'         => $codeParam !== '' ? $codeParam : null,
            'violation_date'         => $incidentDate,
            'violation_notified'     => null,
            'show_violation_summary' => true,
            'school_year'            => $schoolYear,
            'semester'               => $semester,
        ];

        if ($data['violation_date'] === '' && !empty($data['attendance'][0]['date'])) {
            $data['violation_date'] = substr((string) $data['attendance'][0]['date'], 0, 10);
        }

        if (!empty($data['attendance'][0]['is_notified'])) {
            $data['violation_notified'] = $data['attendance'][0]['is_notified'];
        }

        if ($data['violation_date'] !== '') {
            foreach ($attendanceRows as $row) {
                $ymd = substr((string) ($row['date'] ?? ''), 0, 10);
                if ($ymd === $data['violation_date']) {
                    $data['show_violation_summary'] = false;
                    break;
                }
            }
        }

        return [
            'success' => true,
            'data'    => $data,
        ];
    }

    public function getNotifiedViolations(
        ?string $schoolYearParam = null,
        ?string $semesterParam = null,
        ?string $defaultSchoolYear = null,
        ?string $defaultSemester = null
    ): array {
        $schoolYear = filled($schoolYearParam) ? (string) $schoolYearParam : (string) $defaultSchoolYear;
        $semester   = filled($semesterParam) ? (string) $semesterParam : (string) $defaultSemester;

        $trackingData = $this->attendanceTrackingModel->query()
            ->where('school_year', $schoolYear)
            ->where('semester', $semester)
            ->where(function ($q) {
                $q->where('is_notified', 1)
                    ->orWhere('is_notified', '1')
                    ->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'");
            })
            ->orderByDesc('date')
            ->get()
            ->toArray();

        if (empty($trackingData)) {
            $latest = $this->attendanceTrackingModel->query()
                ->select(['school_year', 'semester'])
                ->where(function ($q) {
                    $q->where('is_notified', 1)
                        ->orWhere('is_notified', '1')
                        ->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'");
                })
                ->orderByDesc('date')
                ->orderByDesc('id')
                ->first();

            if ($latest) {
                $schoolYear = (string) ($latest->school_year ?? $schoolYear);
                $semester   = (string) ($latest->semester ?? $semester);

                $trackingData = $this->attendanceTrackingModel->query()
                    ->where('school_year', $schoolYear)
                    ->where('semester', $semester)
                    ->where(function ($q) {
                        $q->where('is_notified', 1)
                            ->orWhere('is_notified', '1')
                            ->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'");
                    })
                    ->orderByDesc('date')
                    ->get()
                    ->toArray();
            }
        }

        if (empty($trackingData)) {
            return [
                'students'     => [],
                'school_year'  => $schoolYear,
                'semester'     => $semester,
                'show_actions' => false,
            ];
        }

        $studentIds = array_unique(array_map(fn ($r) => (int) ($r['student_id'] ?? 0), $trackingData));

        $students = $this->studentModel->query()
            ->whereIn('id', $studentIds)
            ->where('is_active', 1)
            ->get()
            ->toArray();

        $activeIdSet = array_flip(array_map(
            static fn ($row) => (int) ($row['id'] ?? 0),
            $students
        ));

        $trackingData = array_values(array_filter(
            $trackingData,
            static fn ($row) => isset($activeIdSet[(int) ($row['student_id'] ?? 0)])
        ));

        $studentIds = array_keys($activeIdSet);

        if (empty($trackingData)) {
            return [
                'students'     => [],
                'school_year'  => $schoolYear,
                'semester'     => $semester,
                'show_actions' => false,
            ];
        }

        $studentMap = [];
        foreach ($students as $s) {
            $studentMap[$s['id']] = $s;
        }

        $classByStudent = [];
        try {
            $rows = DB::table('student_class as sc')
                ->select([
                    'sc.student_id',
                    'sc.class_section_id',
                    'cs.class_section_name',
                    'cs.class_id',
                    'c.class_name',
                ])
                ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
                ->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
                ->whereIn('sc.student_id', $studentIds)
                ->where('sc.school_year', $schoolYear)
                ->orderByDesc('sc.id')
                ->get();

            foreach ($rows as $row) {
                $sid = (int) ($row->student_id ?? 0);
                if ($sid > 0 && !isset($classByStudent[$sid])) {
                    $classByStudent[$sid] = [
                        'class_section_id'   => $row->class_section_id ?? null,
                        'class_section_name' => (string) ($row->class_section_name ?? ''),
                        'class_id'           => $row->class_id ?? null,
                        'class_name'         => (string) ($row->class_name ?? ''),
                    ];
                }
            }
        } catch (Throwable) {
        }

        $mergedData = [];
        foreach ($trackingData as $row) {
            $sid = (int) ($row['student_id'] ?? 0);
            if (!isset($studentMap[$sid])) {
                continue;
            }

            $merged = array_merge($studentMap[$sid], $row);

            if (isset($classByStudent[$sid])) {
                $merged['class_section_name'] = (string) ($classByStudent[$sid]['class_section_name'] ?? '');
                $merged['class_name']         = (string) ($classByStudent[$sid]['class_name'] ?? '');
            } elseif (!empty($merged['registration_grade'])) {
                $merged['grade'] = (string) $merged['registration_grade'];
            }

            try {
                $p = $this->parentLookupService->getPrimaryParentForStudent($sid);
                if ($p) {
                    $merged['parent_name']  = $p['parent_name'] ?? '';
                    $merged['parent_email'] = $p['email'] ?? '';
                    $merged['parent_phone'] = $p['phone'] ?? '';
                }
            } catch (Throwable) {
            }

            $mergedData[] = $merged;
        }

        return [
            'students'    => $mergedData,
            'school_year' => $schoolYear,
            'semester'    => $semester,
        ];
    }
}