<?php

namespace App\Services\AttendanceTracking;

use App\Models\AttendanceData;
use App\Models\Student;
use App\Models\StudentClass;
use Illuminate\Support\Facades\DB;

class AttendanceViolationStudentResolverService
{
    public function __construct(
        protected Student $studentModel,
        protected StudentClass $studentClassModel,
        protected AttendanceData $attendanceDataModel
    ) {
    }

    public function resolveForSchoolYear(string $schoolYear, ?string $semester = null): array
    {
        $debug = [
            'class_students' => 0,
            'student_ids'    => 0,
        ];

        $classStudentsQuery = $this->studentClassModel->query();
        if (method_exists($this->studentClassModel, 'scopeActive')) {
            $classStudentsQuery->active();
        }

        $classStudents = $classStudentsQuery
            ->where('student_class.school_year', $schoolYear)
            ->get()
            ->toArray();

        $debug['class_students'] = count($classStudents);

        if (empty($classStudents)) {
            $latestTerm = DB::table('student_class')
                ->select('school_year', 'semester')
                ->orderByDesc('id')
                ->first();

            if ($latestTerm && !empty($latestTerm->school_year)) {
                $schoolYear = (string) $latestTerm->school_year;
                if (!empty($latestTerm->semester)) {
                    $semester = (string) $latestTerm->semester;
                }

                $classStudentsQuery = $this->studentClassModel->query();
                if (method_exists($this->studentClassModel, 'scopeActive')) {
                    $classStudentsQuery->active();
                }

                $classStudents = $classStudentsQuery
                    ->where('student_class.school_year', $schoolYear)
                    ->get()
                    ->toArray();

                $debug['class_students'] = count($classStudents);
            }
        }

        [$studentIds, $studentCodes] = $this->extractStudentIdentifiers($classStudents);

        if (empty($studentIds) && empty($studentCodes)) {
            [$studentIds, $studentCodes] = $this->deriveIdentifiersFromAttendanceData($schoolYear);
        }

        [$studentIds, $studentCodes] = $this->filterInactiveIdentifiers($studentIds, $studentCodes);

        $debug['student_ids'] = count($studentIds) + count($studentCodes);

        if (empty($studentIds) && empty($studentCodes)) {
            return [
                'school_year'       => $schoolYear,
                'semester'          => $semester,
                'student_ids'       => [],
                'student_codes'     => [],
                'students'          => [],
                'student_code_to_id'=> [],
                'existing_ids'      => [],
                'debug'             => $debug,
            ];
        }

        $students = $this->loadActiveStudents($studentIds, $studentCodes);
        $students = $this->addPlaceholderStudentsForMissingCodes($students, $studentCodes);
        $students = $this->ensureEntriesForAllIdentifiers($students, $studentIds, $studentCodes);

        [$studentCodeToId, $existingIds] = $this->buildLookupMaps($students);

        return [
            'school_year'        => $schoolYear,
            'semester'           => $semester,
            'student_ids'        => $studentIds,
            'student_codes'      => $studentCodes,
            'students'           => $students,
            'student_code_to_id' => $studentCodeToId,
            'existing_ids'       => $existingIds,
            'debug'              => $debug,
        ];
    }

    public function ensureAttendanceStudentExists(
        array &$students,
        array &$studentCodeToId,
        array &$existingIds,
        mixed $rawStudentId
    ): ?int {
        if (is_numeric($rawStudentId)) {
            $sid = (int) $rawStudentId;
            if ($sid <= 0) {
                return null;
            }

            if (!isset($existingIds[$sid])) {
                $students[] = [
                    'id'        => $sid,
                    'school_id' => (string) $sid,
                    'firstname' => (string) $sid,
                    'lastname'  => '',
                    'parent_id' => 0,
                ];
                $existingIds[$sid] = true;
            }

            return $sid;
        }

        if (is_string($rawStudentId)) {
            $code = trim($rawStudentId);
            if ($code === '') {
                return null;
            }

            $sid = $studentCodeToId[$code] ?? null;
            if ($sid === null) {
                $sid = abs(crc32($code)) ?: random_int(3000000, 3999999);
                $studentCodeToId[$code] = $sid;

                if (!isset($existingIds[$sid])) {
                    $students[] = [
                        'id'        => $sid,
                        'school_id' => $code,
                        'firstname' => $code,
                        'lastname'  => '',
                        'parent_id' => 0,
                    ];
                    $existingIds[$sid] = true;
                }
            }

            return $sid;
        }

        return null;
    }

    protected function extractStudentIdentifiers(array $classStudents): array
    {
        $studentIds = [];
        $studentCodes = [];

        foreach ($classStudents as $row) {
            $raw = $row['student_id'] ?? null;

            if (is_numeric($raw)) {
                $studentIds[] = (int) $raw;
            } elseif (is_string($raw) && $raw !== '') {
                $studentCodes[] = trim($raw);
            }
        }

        $studentIds = array_values(array_unique(array_filter($studentIds, fn ($v) => $v > 0)));
        $studentCodes = array_values(array_unique(array_filter($studentCodes, fn ($v) => $v !== '')));

        return [$studentIds, $studentCodes];
    }

    protected function deriveIdentifiersFromAttendanceData(string $schoolYear): array
    {
        $studentIds = [];
        $studentCodes = [];

        $attendanceStudents = DB::table($this->attendanceDataModel->getTable())
            ->selectRaw('DISTINCT student_id')
            ->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
            ->orderBy('student_id')
            ->get();

        foreach ($attendanceStudents as $r) {
            $raw = $r->student_id ?? null;

            if (is_numeric($raw)) {
                $studentIds[] = (int) $raw;
            } elseif (is_string($raw) && $raw !== '') {
                $studentCodes[] = trim($raw);
            }
        }

        $studentIds = array_values(array_unique(array_filter($studentIds, fn ($v) => $v > 0)));
        $studentCodes = array_values(array_unique(array_filter($studentCodes, fn ($v) => $v !== '')));

        return [$studentIds, $studentCodes];
    }

    protected function filterInactiveIdentifiers(array $studentIds, array $studentCodes): array
    {
        if (!empty($studentIds)) {
            $inactiveRows = $this->studentModel->query()
                ->select('id')
                ->whereIn('id', $studentIds)
                ->where('is_active', 0)
                ->get()
                ->toArray();

            $inactiveIdSet = array_flip(array_map(
                static fn ($r) => (int) ($r['id'] ?? 0),
                $inactiveRows
            ));

            $studentIds = array_values(array_filter(
                $studentIds,
                static fn ($id) => !isset($inactiveIdSet[$id])
            ));
        }

        if (!empty($studentCodes)) {
            $inactiveCodeRows = $this->studentModel->query()
                ->select('school_id')
                ->whereIn('school_id', $studentCodes)
                ->where('is_active', 0)
                ->get()
                ->toArray();

            $inactiveCodeSet = array_flip(array_map(
                static fn ($r) => (string) ($r['school_id'] ?? ''),
                $inactiveCodeRows
            ));

            $studentCodes = array_values(array_filter(
                $studentCodes,
                static fn ($code) => $code !== '' && !isset($inactiveCodeSet[$code])
            ));
        }

        return [$studentIds, $studentCodes];
    }

    protected function loadActiveStudents(array $studentIds, array $studentCodes): array
    {
        $students = [];

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

        if (!empty($studentCodes)) {
            $byCode = $this->studentModel->query()
                ->whereIn('school_id', $studentCodes)
                ->where('is_active', 1)
                ->get()
                ->toArray();

            $seen = array_flip(array_map(fn ($s) => (int) ($s['id'] ?? 0), $students));

            foreach ($byCode as $s) {
                $sid = (int) ($s['id'] ?? 0);
                if ($sid > 0 && !isset($seen[$sid])) {
                    $students[] = $s;
                    $seen[$sid] = true;
                }
            }
        }

        return $students;
    }

    protected function addPlaceholderStudentsForMissingCodes(array $students, array $studentCodes): array
    {
        $knownCodes = [];

        foreach ($students as $s) {
            $code = trim((string) ($s['school_id'] ?? ''));
            if ($code !== '') {
                $knownCodes[$code] = true;
            }
        }

        foreach ($studentCodes as $code) {
            if (!isset($knownCodes[$code])) {
                $virtualId = abs(crc32($code)) ?: random_int(1000000, 1999999);
                $students[] = [
                    'id'         => $virtualId,
                    'school_id'  => $code,
                    'firstname'  => $code,
                    'lastname'   => '',
                    'parent_id'  => 0,
                    'class_name' => '',
                ];
            }
        }

        return $students;
    }

    protected function ensureEntriesForAllIdentifiers(array $students, array $studentIds, array $studentCodes): array
    {
        [$studentCodeToId, $existingIds] = $this->buildLookupMaps($students);

        foreach (array_merge($studentIds, $studentCodes) as $sidOrCode) {
            if (is_numeric($sidOrCode)) {
                $sid = (int) $sidOrCode;

                if ($sid > 0 && !isset($existingIds[$sid])) {
                    $students[] = [
                        'id'         => $sid,
                        'school_id'  => (string) $sid,
                        'firstname'  => (string) $sid,
                        'lastname'   => '',
                        'parent_id'  => 0,
                        'class_name' => '',
                    ];
                    $existingIds[$sid] = true;
                }
            } else {
                $code = (string) $sidOrCode;

                if ($code !== '' && !isset($studentCodeToId[$code])) {
                    $virtualId = abs(crc32($code)) ?: random_int(2000000, 2999999);
                    $students[] = [
                        'id'         => $virtualId,
                        'school_id'  => $code,
                        'firstname'  => $code,
                        'lastname'   => '',
                        'parent_id'  => 0,
                        'class_name' => '',
                    ];

                    $studentCodeToId[$code] = $virtualId;
                    $existingIds[$virtualId] = true;
                }
            }
        }

        return $students;
    }

    protected function buildLookupMaps(array $students): array
    {
        $studentCodeToId = [];
        foreach ($students as $stu) {
            $code = trim((string) ($stu['student_code'] ?? $stu['school_id'] ?? ''));
            if ($code !== '') {
                $studentCodeToId[$code] = (int) ($stu['id'] ?? 0);
            }
        }

        $existingIds = array_flip(array_map(
            fn ($s) => (int) ($s['id'] ?? 0),
            $students
        ));

        return [$studentCodeToId, $existingIds];
    }
}
