<?php

namespace App\Services\Promotions\Placement;

use App\Models\Configuration;
use App\Models\SectionPlacementBatch;
use App\Models\SectionPlacementBatchStudent;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class SectionPlacementPreviewService
{
    public function __construct(
        private PromotionSectionCapacityService $capacityService,
        private PlacementPoolBuilder $poolBuilder,
        private BalancedSectionPlacementService $placementService
    ) {
    }

    public function createDraft(
        string $fromSchoolYear,
        string $toSchoolYear,
        ?int $fromGradeLevelId = null,
        ?int $toGradeLevelId = null,
        ?int $createdBy = null
    ): SectionPlacementBatch {
        $capacityInfo = $this->capacityService->capacity();
        $poolResult = $this->poolBuilder->build($fromSchoolYear, $toSchoolYear, $toGradeLevelId);
        $placement = $this->placementService->assign($poolResult['pool'], (int) $capacityInfo['value']);

        $snapshot = [
            'capacity' => $capacityInfo,
            'target_section_sizes' => $placement['target_sizes'],
            'sections' => $placement['sections'],
            'excluded_students' => $poolResult['exceptions'],
            'warnings' => $capacityInfo['warnings'],
            'new_students_defaulted_to_d_band' => array_values(array_map(
                fn ($s) => $s['student_id'],
                array_filter($placement['assignments'], fn ($s) => ($s['placement_band_reason'] ?? null) === 'new_student_default_band')
            )),
        ];

        return DB::transaction(function () use ($fromSchoolYear, $toSchoolYear, $fromGradeLevelId, $toGradeLevelId, $createdBy, $capacityInfo, $placement, $snapshot) {
            $batch = SectionPlacementBatch::query()->create([
                'from_school_year' => $fromSchoolYear,
                'to_school_year' => $toSchoolYear,
                'from_grade_level_id' => $fromGradeLevelId,
                'to_grade_level_id' => $toGradeLevelId,
                'section_capacity_used' => (int) $capacityInfo['value'],
                'total_students' => count($placement['assignments']),
                'section_count' => (int) $placement['section_count'],
                'algorithm' => BalancedSectionPlacementService::ALGORITHM,
                'configuration_snapshot_json' => json_encode([
                    PromotionSectionCapacityService::CONFIG_KEY => (int) $capacityInfo['value'],
                    'placement.algorithm' => BalancedSectionPlacementService::ALGORITHM,
                ]),
                'preview_snapshot_json' => json_encode($snapshot),
                'created_by' => $createdBy,
                'status' => SectionPlacementBatch::STATUS_DRAFT,
            ]);

            foreach ($placement['assignments'] as $assignment) {
                SectionPlacementBatchStudent::query()->create([
                    'batch_id' => $batch->getKey(),
                    'student_id' => $assignment['student_id'],
                    'student_type' => $assignment['student_type'],
                    'source_decision_id' => $assignment['source_decision_id'],
                    'source_enrollment_id' => $assignment['source_enrollment_id'],
                    'final_score' => $assignment['final_score'],
                    'placement_score' => $assignment['placement_score'],
                    'score_band' => $assignment['score_band'],
                    'placement_band_reason' => $assignment['placement_band_reason'],
                    'planned_section_index' => $assignment['planned_section_index'],
                    'assignment_order' => $assignment['assignment_order'],
                    'was_override' => false,
                    'created_at' => now(),
                ]);
            }

            return $batch->refresh();
        });
    }

    public function previewPayload(SectionPlacementBatch $batch): array
    {
        return [
            'batch' => $batch->toArray(),
            'preview' => $batch->preview_snapshot_json ? json_decode((string) $batch->preview_snapshot_json, true) : null,
            'students' => $batch->students()->orderBy('planned_section_index')->orderBy('assignment_order')->get()->toArray(),
        ];
    }

    public function finalize(int $batchId, ?int $userId = null): SectionPlacementBatch
    {
        $batch = SectionPlacementBatch::query()->findOrFail($batchId);
        if ($batch->status !== SectionPlacementBatch::STATUS_DRAFT) {
            throw new RuntimeException('Only draft placement batches can be finalized.');
        }
        if ((int) $batch->total_students !== $batch->students()->count()) {
            throw new RuntimeException('Draft batch student count does not match batch total.');
        }

        return DB::transaction(function () use ($batch, $userId) {
            $semester = Configuration::getConfigValueByKey('semester') ?: 'Fall';
            $sectionMap = $this->ensureSections($batch, $semester);

            $seen = [];
            $students = $batch->students()->orderBy('assignment_order')->get();
            foreach ($students as $student) {
                $studentId = (int) $student->student_id;
                if (isset($seen[$studentId])) {
                    throw new RuntimeException('Student appears more than once in placement batch.');
                }
                $seen[$studentId] = true;

                $duplicate = DB::table('student_class')
                    ->where('student_id', $studentId)
                    ->where('school_year', $batch->to_school_year)
                    ->where('is_event_only', 0)
                    ->exists();
                if ($duplicate) {
                    throw new RuntimeException('Student ' . $studentId . ' already has a target-year class placement.');
                }

                $sectionId = $sectionMap[(int) $student->planned_section_index] ?? null;
                if ($sectionId === null) {
                    throw new RuntimeException('Missing target section for planned section ' . $student->planned_section_index . '.');
                }

                DB::table('student_class')->insert([
                    'student_id' => $studentId,
                    'class_section_id' => $sectionId,
                    'is_event_only' => 0,
                    'semester' => $semester,
                    'school_year' => $batch->to_school_year,
                    'description' => 'Finalized from section placement batch #' . $batch->getKey(),
                    'created_at' => now(),
                    'updated_at' => now(),
                    'updated_by' => $userId,
                ]);

                $student->assigned_section_id = $sectionId;
                $student->save();
            }

            $batch->status = SectionPlacementBatch::STATUS_FINALIZED;
            $batch->finalized_by = $userId;
            $batch->finalized_at = now();
            $batch->save();

            return $batch->refresh();
        });
    }

    private function ensureSections(SectionPlacementBatch $batch, string $semester): array
    {
        if (!$batch->to_grade_level_id) {
            throw new RuntimeException('to_grade_level_id is required to finalize placement sections.');
        }

        $map = [];
        $classId = (int) $batch->to_grade_level_id;
        $sectionCount = (int) $batch->section_count;
        $maxSectionId = (int) DB::table('classSection')->max('class_section_id');
        $letters = range('A', 'Z');

        for ($i = 1; $i <= $sectionCount; $i++) {
            $name = $this->sectionName($classId, $letters[$i - 1] ?? (string) $i, $batch->to_school_year);
            $existing = DB::table('classSection')
                ->where('class_id', $classId)
                ->where('class_section_name', $name)
                ->where(function ($q) use ($batch) {
                    $q->where('school_year', $batch->to_school_year)->orWhereNull('school_year');
                })
                ->value('class_section_id');

            if ($existing) {
                $map[$i] = (int) $existing;
                continue;
            }

            $sectionId = ++$maxSectionId;
            DB::table('classSection')->insert([
                'class_id' => $classId,
                'class_section_id' => $sectionId,
                'class_section_name' => $name,
                'semester' => $semester,
                'school_year' => $batch->to_school_year,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
            $map[$i] = $sectionId;
        }

        return $map;
    }

    private function sectionName(int $classId, string $suffix, string $schoolYear): string
    {
        $className = DB::table('classes')->where('id', $classId)->value('class_name');
        $base = $className ? (string) $className : ('Class ' . $classId);
        return $base . '-' . $suffix . ' ' . $schoolYear;
    }
}
