<?php

namespace App\Services\Promotions;

use App\Models\ClassSection;
use App\Models\LevelProgression;
use App\Models\SchoolClass;
use Illuminate\Support\Collection;

/**
 * Resolves current → next level using the configurable
 * `level_progressions` table (plan section 10).
 *
 * Falls back to `class_id + 1` only if no mapping is found, matching
 * the legacy behaviour in {@see \App\Services\School\AccountEventService}.
 */
class LevelProgressionService
{
    /**
     * Default mapping shipped with the plan doc; used to seed the table
     * if no rows exist when the service is first asked for a list.
     */
    public const DEFAULT_MAP = [
        ['current' => 'KG1',    'next' => 'KG2',     'order' => 10,  'terminal' => false],
        ['current' => 'KG2',    'next' => 'Grade 1', 'order' => 20,  'terminal' => false],
        ['current' => 'Grade 1','next' => 'Grade 2', 'order' => 30,  'terminal' => false],
        ['current' => 'Grade 2','next' => 'Grade 3', 'order' => 40,  'terminal' => false],
        ['current' => 'Grade 3','next' => 'Grade 4', 'order' => 50,  'terminal' => false],
        ['current' => 'Grade 4','next' => 'Grade 5', 'order' => 60,  'terminal' => false],
        ['current' => 'Grade 5','next' => 'Grade 6', 'order' => 70,  'terminal' => false],
        ['current' => 'Grade 6','next' => 'Grade 7', 'order' => 80,  'terminal' => false],
        ['current' => 'Grade 7','next' => 'Grade 8', 'order' => 90,  'terminal' => false],
        ['current' => 'Grade 8','next' => 'Grade 9', 'order' => 100, 'terminal' => false],
        ['current' => 'Grade 9','next' => 'Youth',   'order' => 110, 'terminal' => false],
        ['current' => 'Youth',  'next' => null,      'order' => 120, 'terminal' => true],
    ];

    /**
     * Returns all configured level progressions, seeding defaults if the
     * table is empty.
     *
     * @return Collection<int,LevelProgression>
     */
    public function list(bool $activeOnly = true): Collection
    {
        $query = LevelProgression::query()->ordered();
        if ($activeOnly) {
            $query->active();
        }
        $rows = $query->get();

        if ($rows->isEmpty() && $activeOnly) {
            $this->seedDefaults();
            $rows = LevelProgression::query()->ordered()->active()->get();
        }

        return $rows;
    }

    /**
     * Resolve next-level information for a student based on their current
     * `class_id` (i.e. classes.id). Returns null if no progression rule
     * matches and no fallback can be derived.
     *
     * @return array{
     *   current_level_id:?int,
     *   current_level_name:?string,
     *   next_level_id:?int,
     *   next_level_name:?string,
     *   is_terminal:bool
     * }|null
     */
    public function resolveByCurrentClassId(?int $currentClassId): ?array
    {
        if ($currentClassId === null || $currentClassId <= 0) {
            return null;
        }

        $current = SchoolClass::query()->find($currentClassId);
        if (!$current) {
            return null;
        }
        $currentName = (string) ($current->class_name ?? '');

        $progression = LevelProgression::findByCurrentLevelId($currentClassId)
            ?: ($currentName !== '' ? LevelProgression::findByCurrentLevelName($currentName) : null);

        if ($progression) {
            return [
                'current_level_id' => $currentClassId,
                'current_level_name' => $currentName !== '' ? $currentName : $progression->current_level_name,
                'next_level_id' => $progression->next_level_id !== null ? (int) $progression->next_level_id : null,
                'next_level_name' => $progression->next_level_name,
                'is_terminal' => (bool) $progression->is_terminal,
            ];
        }

        // Legacy fallback: numeric next class id (matches AccountEventService::preparePromotionQueue).
        $nextId = $currentClassId + 1;
        $next = SchoolClass::query()->find($nextId);

        return [
            'current_level_id' => $currentClassId,
            'current_level_name' => $currentName !== '' ? $currentName : null,
            'next_level_id' => $next ? (int) $next->id : null,
            'next_level_name' => $next ? (string) $next->class_name : null,
            'is_terminal' => false,
        ];
    }

    /**
     * Resolve next-level info from a `classSection` row id (the
     * "from_class_section_id" used elsewhere).
     */
    public function resolveByCurrentClassSectionId(?int $classSectionId): ?array
    {
        if ($classSectionId === null || $classSectionId <= 0) {
            return null;
        }
        $classId = ClassSection::getClassId($classSectionId);
        if (!$classId) {
            return null;
        }
        return $this->resolveByCurrentClassId((int) $classId);
    }

    /**
     * Seed the table with the default mapping from {@see DEFAULT_MAP}.
     * Idempotent – relies on the unique key on `current_level_name`.
     */
    public function seedDefaults(): int
    {
        $created = 0;
        foreach (self::DEFAULT_MAP as $row) {
            $existing = LevelProgression::query()
                ->whereRaw('LOWER(current_level_name) = ?', [strtolower($row['current'])])
                ->first();
            if ($existing) {
                continue;
            }

            $currentClassId = $this->resolveClassIdByName($row['current']);
            $nextClassId = isset($row['next']) && $row['next'] !== null
                ? $this->resolveClassIdByName($row['next'])
                : null;

            LevelProgression::query()->create([
                'current_level_id' => $currentClassId,
                'current_level_name' => $row['current'],
                'next_level_id' => $nextClassId,
                'next_level_name' => $row['next'] ?? null,
                'order_index' => (int) ($row['order'] ?? 0),
                'is_terminal' => (bool) ($row['terminal'] ?? false),
                'is_active' => true,
            ]);
            $created++;
        }

        return $created;
    }

    public function upsertMapping(array $payload): LevelProgression
    {
        $currentName = (string) ($payload['current_level_name'] ?? '');
        if ($currentName === '') {
            throw new \InvalidArgumentException('current_level_name is required');
        }

        $existing = LevelProgression::findByCurrentLevelName($currentName);
        $data = [
            'current_level_id' => isset($payload['current_level_id']) ? (int) $payload['current_level_id'] : ($existing->current_level_id ?? null),
            'current_level_name' => $currentName,
            'next_level_id' => isset($payload['next_level_id']) ? (int) $payload['next_level_id'] : null,
            'next_level_name' => $payload['next_level_name'] ?? null,
            'order_index' => isset($payload['order_index']) ? (int) $payload['order_index'] : ($existing->order_index ?? 0),
            'is_terminal' => (bool) ($payload['is_terminal'] ?? ($existing->is_terminal ?? false)),
            'is_active' => array_key_exists('is_active', $payload) ? (bool) $payload['is_active'] : ($existing->is_active ?? true),
            'notes' => $payload['notes'] ?? ($existing->notes ?? null),
        ];

        if ($existing) {
            $existing->fill($data);
            $existing->save();
            return $existing;
        }

        return LevelProgression::query()->create($data);
    }

    private function resolveClassIdByName(?string $name): ?int
    {
        if ($name === null || $name === '') {
            return null;
        }
        $row = SchoolClass::query()
            ->whereRaw('LOWER(class_name) = ?', [strtolower(trim($name))])
            ->orderByDesc('id')
            ->first();

        return $row ? (int) $row->id : null;
    }
}
