<?php

namespace App\Services\Grading;

use App\Models\ClassSection;
use App\Models\PlacementBatch;
use App\Models\PlacementLevel;
use App\Models\PlacementScore;
use App\Models\Student;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class GradingPlacementService
{
    public function placementContext(?int $classSectionId, string $schoolYear, ?string $placementTest, ?string $open): array
    {
        if (!$classSectionId) {
            $showStudents = ($placementTest !== '' && $open === '1');
            $students = $showStudents ? $this->fetchActiveStudentsWithSection($schoolYear) : [];
            $batches = $this->fetchPlacementBatches($schoolYear);
            $batchDetails = $this->fetchPlacementBatchDetails($batches, $schoolYear);

            return [
                'school_year' => $schoolYear,
                'students' => $students,
                'batches' => $batches,
                'batch_details' => $batchDetails,
                'placement_test' => $placementTest,
                'show_students' => $showStudents,
            ];
        }

        $sectionName = ClassSection::query()->where('class_section_id', $classSectionId)->value('class_section_name');
        $classId = ClassSection::query()->where('class_section_id', $classSectionId)->value('class_id');

        $students = Student::getStudentInfoByClassSectionId($classSectionId, null, $schoolYear);
        $studentIds = array_values(array_filter(array_map(
            static fn ($row) => (int) ($row['student_id'] ?? 0),
            $students
        ), static fn ($id) => $id > 0));

        $levels = [];
        if (!empty($studentIds)) {
            $rows = PlacementLevel::query()
                ->whereIn('student_id', $studentIds)
                ->where('school_year', $schoolYear)
                ->get();
            foreach ($rows as $row) {
                $levels[(int) $row->student_id] = $row->level ?? null;
            }
        }

        return [
            'class_section_id' => $classSectionId,
            'class_section_name' => $sectionName ?? '',
            'class_id' => $classId,
            'school_year' => $schoolYear,
            'students' => $students,
            'levels' => $levels,
        ];
    }

    public function updatePlacementLevel(int $studentId, string $schoolYear, $level, ?int $userId): void
    {
        if ($studentId <= 0 || $schoolYear === '') {
            throw new RuntimeException('Missing student or school year.');
        }

        $level = $level === '' || $level === null ? null : (int) $level;
        if ($level !== null && !in_array($level, [1, 2, 3], true)) {
            throw new RuntimeException('Invalid placement level.');
        }

        $existing = PlacementLevel::query()
            ->where('student_id', $studentId)
            ->where('school_year', $schoolYear)
            ->first();

        if ($level === null) {
            if ($existing) {
                $existing->delete();
            }
            return;
        }

        $payload = [
            'student_id' => $studentId,
            'school_year' => $schoolYear,
            'level' => $level,
            'updated_by' => $userId,
        ];

        if ($existing) {
            $existing->update($payload);
        } else {
            $payload['created_by'] = $userId;
            PlacementLevel::query()->create($payload);
        }
    }

    public function updatePlacementLevels(int $classSectionId, string $schoolYear, array $levels, ?int $userId): void
    {
        if ($classSectionId <= 0 || $schoolYear === '') {
            throw new RuntimeException('Missing class section or school year.');
        }

        $students = Student::getStudentInfoByClassSectionId($classSectionId, null, $schoolYear);
        $validIds = array_values(array_filter(array_map(
            static fn ($row) => (int) ($row['student_id'] ?? 0),
            $students
        ), static fn ($id) => $id > 0));

        $validSet = array_flip($validIds);
        $existingRows = [];
        if (!empty($validIds)) {
            $rows = PlacementLevel::query()
                ->whereIn('student_id', $validIds)
                ->where('school_year', $schoolYear)
                ->get();
            foreach ($rows as $row) {
                $existingRows[(int) $row->student_id] = $row;
            }
        }

        foreach ($levels as $studentIdRaw => $levelRaw) {
            $studentId = (int) $studentIdRaw;
            if (!isset($validSet[$studentId])) {
                continue;
            }
            $levelRaw = trim((string) $levelRaw);
            $level = $levelRaw === '' ? null : (int) $levelRaw;
            if ($level !== null && !in_array($level, [1, 2, 3], true)) {
                continue;
            }

            if ($level === null) {
                if (isset($existingRows[$studentId])) {
                    $existingRows[$studentId]->delete();
                }
                continue;
            }

            $payload = [
                'student_id' => $studentId,
                'school_year' => $schoolYear,
                'level' => $level,
                'updated_by' => $userId,
            ];

            if (isset($existingRows[$studentId])) {
                $existingRows[$studentId]->update($payload);
            } else {
                $payload['created_by'] = $userId;
                PlacementLevel::query()->create($payload);
            }
        }
    }

    public function createPlacementBatch(string $schoolYear, string $placementTest, array $levels, ?int $userId): int
    {
        if ($schoolYear === '' || $placementTest === '') {
            throw new RuntimeException('Missing placement test or school year.');
        }

        $students = $this->fetchActiveStudentsWithSection($schoolYear);
        $validIds = array_values(array_filter(array_map(
            static fn ($row) => (int) ($row['student_id'] ?? 0),
            $students
        ), static fn ($id) => $id > 0));
        $validSet = array_flip($validIds);

        $batch = PlacementBatch::query()->create([
            'placement_test' => $placementTest,
            'school_year' => $schoolYear,
            'created_by' => $userId,
            'updated_by' => $userId,
        ]);

        $savedCount = 0;
        foreach ($levels as $studentIdRaw => $levelRaw) {
            $studentId = (int) $studentIdRaw;
            if (!isset($validSet[$studentId])) {
                continue;
            }
            $levelRaw = trim((string) $levelRaw);
            if ($levelRaw === '') {
                continue;
            }
            $score = (int) $levelRaw;
            if ($score < 0 || $score > 100) {
                continue;
            }

            PlacementScore::query()->create([
                'batch_id' => (int) $batch->id,
                'student_id' => $studentId,
                'score' => $score,
                'created_by' => $userId,
                'updated_by' => $userId,
            ]);
            $savedCount++;
        }

        if ($savedCount === 0) {
            $batch->delete();
            throw new RuntimeException('No scores entered. Batch not saved.');
        }

        return (int) $batch->id;
    }

    public function getPlacementBatch(int $batchId): array
    {
        $batch = PlacementBatch::query()->find($batchId);
        if (!$batch) {
            throw new RuntimeException('Placement batch not found.');
        }

        $schoolYear = (string) ($batch->school_year ?? '');
        $students = $this->fetchActiveStudentsWithSection($schoolYear);
        $scores = $this->fetchPlacementScoresForBatch($batchId);

        return [
            'batch' => $batch,
            'students' => $students,
            'scores' => $scores,
        ];
    }

    public function updatePlacementBatch(int $batchId, string $schoolYear, array $levels, ?int $userId): void
    {
        $batch = PlacementBatch::query()->find($batchId);
        if (!$batch) {
            throw new RuntimeException('Placement batch not found.');
        }

        $students = $this->fetchActiveStudentsWithSection($schoolYear);
        $validIds = array_values(array_filter(array_map(
            static fn ($row) => (int) ($row['student_id'] ?? 0),
            $students
        ), static fn ($id) => $id > 0));

        $validSet = array_flip($validIds);
        $existing = $this->fetchPlacementScoresForBatch($batchId);

        foreach ($levels as $studentIdRaw => $scoreRaw) {
            $studentId = (int) $studentIdRaw;
            if (!isset($validSet[$studentId])) {
                continue;
            }
            $scoreRaw = trim((string) $scoreRaw);
            if ($scoreRaw === '') {
                if (isset($existing[$studentId])) {
                    PlacementScore::query()->whereKey($existing[$studentId]['id'])->delete();
                }
                continue;
            }
            $score = (int) $scoreRaw;
            if ($score < 0 || $score > 100) {
                continue;
            }

            if (isset($existing[$studentId])) {
                PlacementScore::query()->whereKey($existing[$studentId]['id'])->update([
                    'score' => $score,
                    'updated_by' => $userId,
                ]);
            } else {
                PlacementScore::query()->create([
                    'batch_id' => $batchId,
                    'student_id' => $studentId,
                    'score' => $score,
                    'created_by' => $userId,
                    'updated_by' => $userId,
                ]);
            }
        }

        $batch->update([
            'updated_by' => $userId,
        ]);
    }

    private function fetchActiveStudentsWithSection(string $schoolYear): array
    {
        return DB::table('students as s')
            ->select('s.id as student_id', 's.school_id', 's.firstname', 's.lastname', 'sc.class_section_id', 'cs.class_section_name', 'c.class_name')
            ->leftJoin('student_class as sc', function ($join) use ($schoolYear) {
                $join->on('sc.student_id', '=', 's.id')
                    ->where('sc.school_year', '=', $schoolYear);
            })
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
            ->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
            ->where('s.is_active', 1)
            ->orderBy('s.lastname', 'ASC')
            ->orderBy('s.firstname', 'ASC')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();
    }

    private function fetchPlacementBatches(string $schoolYear): array
    {
        return PlacementBatch::query()
            ->where('school_year', $schoolYear)
            ->orderBy('created_at', 'DESC')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();
    }

    private function fetchPlacementBatchDetails(array $batches, string $schoolYear): array
    {
        if (empty($batches)) {
            return [];
        }

        $batchIds = array_values(array_filter(array_map(
            static fn ($row) => (int) ($row['id'] ?? 0),
            $batches
        ), static fn ($id) => $id > 0));

        if (empty($batchIds)) {
            return [];
        }

        $rows = DB::table('placement_scores as ps')
            ->select('ps.batch_id', 'ps.score', 's.school_id', 's.firstname', 's.lastname', 'cs.class_section_name', 'c.class_name')
            ->join('students as s', 's.id', '=', 'ps.student_id')
            ->leftJoin('student_class as sc', function ($join) use ($schoolYear) {
                $join->on('sc.student_id', '=', 's.id')
                    ->where('sc.school_year', '=', $schoolYear);
            })
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
            ->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
            ->whereIn('ps.batch_id', $batchIds)
            ->where('s.is_active', 1)
            ->orderBy('ps.batch_id', 'ASC')
            ->orderBy('s.lastname', 'ASC')
            ->orderBy('s.firstname', 'ASC')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        $details = [];
        foreach ($rows as $row) {
            $bid = (int) ($row['batch_id'] ?? 0);
            if ($bid <= 0) {
                continue;
            }
            $details[$bid][] = $row;
        }

        return $details;
    }

    private function fetchPlacementScoresForBatch(int $batchId): array
    {
        $rows = PlacementScore::query()
            ->where('batch_id', $batchId)
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        $scores = [];
        foreach ($rows as $row) {
            $scores[(int) $row['student_id']] = $row;
        }

        return $scores;
    }
}
