<?php

namespace App\Services\Students;

use App\Models\ClassSection;
use App\Models\Enrollment;
use App\Models\Student;
use App\Models\StudentClass;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class StudentAssignmentService
{
    public function __construct(private StudentConfigService $configService)
    {
    }

    public function assignClasses(int $studentId, array $classSectionIds, bool $isEventOnly, ?int $userId = null): array
    {
        $context = $this->configService->context();
        $semester = (string) ($context['semester'] ?? '');
        $schoolYear = (string) ($context['school_year'] ?? '');

        $classSectionIds = $this->normalizeIds($classSectionIds);
        if ($studentId <= 0 || empty($classSectionIds) || $schoolYear === '') {
            return ['ok' => false, 'message' => 'Missing required data (student/class section).'];
        }

        $student = Student::query()->find($studentId);
        if (!$student) {
            return ['ok' => false, 'message' => 'Student not found.'];
        }

        $sections = ClassSection::query()
            ->whereIn('class_section_id', $classSectionIds)
            ->orWhereIn('id', $classSectionIds)
            ->get()
            ->toArray();

        if (empty($sections)) {
            return ['ok' => false, 'message' => 'Class/Section not found.'];
        }

        $sectionMap = [];
        foreach ($sections as $section) {
            $sectionMap[(int) ($section['class_section_id'] ?? 0)] = $section;
        }

        $missing = array_diff($classSectionIds, array_keys($sectionMap));
        if (!empty($missing)) {
            return ['ok' => false, 'message' => 'One or more selected classes do not exist.'];
        }

        $existing = StudentClass::query()
            ->where('student_id', $studentId)
            ->where('school_year', $schoolYear)
            ->get()
            ->keyBy(fn ($row) => (int) ($row->class_section_id ?? 0));

        $primarySectionId = $classSectionIds[0];
        $primarySection = $sectionMap[$primarySectionId] ?? reset($sectionMap);
        $parentClassId = (int) ($primarySection['class_id'] ?? 0);
        if ($parentClassId <= 0) {
            $parentClassId = (int) (ClassSection::getClassId($primarySectionId) ?? 0);
        }

        $attendanceStats = [];
        $scoreStats = [];

        DB::transaction(function () use (
            $studentId,
            $schoolYear,
            $classSectionIds,
            $isEventOnly,
            $userId,
            $existing,
            $semester,
            $primarySectionId,
            $parentClassId,
            &$attendanceStats,
            &$scoreStats
        ): void {
            foreach ($classSectionIds as $classSectionId) {
                $eventFlag = $isEventOnly ? 1 : 0;
                if ($existing->has($classSectionId)) {
                    $eventFlag = (int) ($existing[$classSectionId]->is_event_only ?? 0);
                }

                $payload = [
                    'student_id' => $studentId,
                    'class_section_id' => $classSectionId,
                    'school_year' => $schoolYear,
                    'semester' => $semester,
                    'is_event_only' => $eventFlag,
                    'updated_by' => $userId,
                    'updated_at' => now(),
                ];

                if ($existing->has($classSectionId)) {
                    $existing[$classSectionId]->fill($payload)->save();
                } else {
                    StudentClass::query()->create($payload + [
                        'created_at' => now(),
                    ]);
                }
            }

            if (!$isEventOnly) {
                $enrollment = Enrollment::query()
                    ->where('student_id', $studentId)
                    ->where('school_year', $schoolYear)
                    ->when($semester !== '', fn ($q) => $q->where('semester', $semester))
                    ->first();

                if ($enrollment) {
                    $enrollment->update([
                        'class_section_id' => $primarySectionId,
                        'enrollment_status' => 'payment pending',
                        'admission_status' => 'accepted',
                        'updated_at' => now(),
                    ]);
                }

                $attendanceStats = $this->updateAttendance(
                    $studentId,
                    $primarySectionId,
                    $parentClassId,
                    $semester,
                    $schoolYear,
                    $userId
                );

                $scoreStats = $this->updateScores(
                    $studentId,
                    $primarySectionId,
                    $semester,
                    $schoolYear,
                    $userId
                );
            }
        });

        $displayNames = [];
        foreach ($classSectionIds as $classSectionId) {
            $section = $sectionMap[$classSectionId] ?? null;
            if (!$section) {
                continue;
            }
            $name = (string) ($section['class_section_name'] ?? '');
            if ($name === '') {
                $name = 'Section #' . $classSectionId;
            }
            $displayNames[] = $name;
        }

        return [
            'ok' => true,
            'student_id' => $studentId,
            'class_section_id' => $primarySectionId,
            'class_section_ids' => $classSectionIds,
            'class_section_names' => $displayNames,
            'attendance_updates' => $attendanceStats,
            'score_updates' => $scoreStats,
        ];
    }

    public function removeClass(int $studentId, int $classSectionId, ?int $userId = null): array
    {
        $context = $this->configService->context();
        $semester = (string) ($context['semester'] ?? '');
        $schoolYear = (string) ($context['school_year'] ?? '');

        if ($studentId <= 0 || $classSectionId <= 0 || $schoolYear === '') {
            return ['ok' => false, 'message' => 'Missing required data (student/class section).'];
        }

        $row = StudentClass::query()
            ->where('student_id', $studentId)
            ->where('class_section_id', $classSectionId)
            ->where('school_year', $schoolYear)
            ->first();

        if (!$row) {
            return ['ok' => false, 'message' => 'Assignment not found for this student/class.'];
        }

        $remainingIds = [];
        $remainingNames = [];

        DB::transaction(function () use (
            $studentId,
            $classSectionId,
            $schoolYear,
            $semester,
            $userId,
            &$remainingIds,
            &$remainingNames
        ): void {
            StudentClass::query()
                ->where('student_id', $studentId)
                ->where('class_section_id', $classSectionId)
                ->where('school_year', $schoolYear)
                ->delete();

            $remainingIds = StudentClass::getClassSectionIdsByStudentId($studentId, $schoolYear);
            $remainingNames = StudentClass::getClassSectionsByStudentId($studentId, $schoolYear, true);

            $enrollment = Enrollment::query()
                ->where('student_id', $studentId)
                ->where('school_year', $schoolYear)
                ->when($semester !== '', fn ($q) => $q->where('semester', $semester))
                ->first();

            if ($enrollment) {
                $newClassId = !empty($remainingIds) ? $remainingIds[0] : null;
                if ((int) $enrollment->class_section_id === $classSectionId || $newClassId !== null) {
                    $enrollment->update([
                        'class_section_id' => $newClassId,
                        'updated_at' => now(),
                        'updated_by' => $userId,
                    ]);
                }
            }
        });

        return [
            'ok' => true,
            'student_id' => $studentId,
            'removed_class_id' => $classSectionId,
            'remaining_ids' => $remainingIds,
            'remaining_names' => $remainingNames,
        ];
    }

    private function updateAttendance(
        int $studentId,
        int $classSectionId,
        int $classId,
        string $semester,
        string $schoolYear,
        ?int $modifiedBy
    ): array {
        $now = now()->toDateTimeString();

        $attendanceUpdated = DB::table('attendance_data')
            ->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->update([
                'class_section_id' => $classSectionId,
                'class_id' => $classId,
                'updated_at' => $now,
                'modified_by' => $modifiedBy,
            ]);

        $recordUpdated = DB::table('attendance_record')
            ->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->update([
                'class_section_id' => $classSectionId,
                'updated_at' => $now,
                'modified_by' => $modifiedBy,
            ]);

        return [
            'attendance_data_updated' => $attendanceUpdated,
            'attendance_record_updated' => $recordUpdated,
        ];
    }

    private function updateScores(
        int $studentId,
        int $classSectionId,
        string $semester,
        string $schoolYear,
        ?int $modifiedBy
    ): array {
        $now = now()->toDateTimeString();
        $tables = [
            'homework',
            'quiz',
            'project',
            'participation',
            'midterm_exam',
            'final_exam',
            'final_score',
            'semester_scores',
        ];

        $results = [];
        foreach ($tables as $table) {
            $payload = [
                'class_section_id' => $classSectionId,
                'updated_at' => $now,
            ];
            if ($modifiedBy !== null && Schema::hasColumn($table, 'updated_by')) {
                $payload['updated_by'] = $modifiedBy;
            }

            $results[$table . '_updated'] = DB::table($table)
                ->where('student_id', $studentId)
                ->where('semester', $semester)
                ->where('school_year', $schoolYear)
                ->update($payload);
        }

        return $results;
    }

    private function normalizeIds(array $ids): array
    {
        $values = array_map('intval', $ids);
        $values = array_values(array_unique(array_filter($values, static fn ($v) => $v > 0)));
        return $values;
    }
}
