<?php

namespace App\Services;

use App\Exceptions\SchoolYear\InvalidSchoolYearSelectionException;
use App\Exceptions\SchoolYear\SchoolYearConfigurationException;
use App\Exceptions\SchoolYear\SchoolYearNotFoundException;
use App\Models\SchoolYearModel;
use App\Support\SchoolYear\SchoolYearStatus;
use App\Support\SchoolYear\SchoolYearContext;
use CodeIgniter\HTTP\IncomingRequest;

final class SchoolYearContextService
{
    public function __construct(
        private readonly SchoolYearModel $schoolYearModel,
        private readonly SchoolYearAccessPolicy $accessPolicy = new SchoolYearAccessPolicy(),
    ) {
    }

    public function resolve(
        IncomingRequest $request,
        ?int $routeSchoolYearId = null
    ): SchoolYearContext {
        $requestedId = $routeSchoolYearId ?? $this->normalizeInt($request->getGet('school_year_id'));
        $requestedName = $this->legacyYearName($request);
        $userId = (int) (session()->get('user_id') ?? 0);

        if ($requestedId !== null && $requestedName !== '') {
            $row = $this->schoolYearModel->find($requestedId);

            if ($row === null || (string) ($row['name'] ?? '') !== $requestedName) {
                throw new InvalidSchoolYearSelectionException('Selected school-year parameters conflict.');
            }

            return $this->authorizedContext($row, $userId, true);
        }

        if ($requestedId !== null) {
            $row = $this->schoolYearModel->find($requestedId);

            if ($row === null) {
                throw new SchoolYearNotFoundException('Selected school year was not found.');
            }

            return $this->authorizedContext($row, $userId, true);
        }

        if ($requestedName !== '') {
            $row = $this->schoolYearModel
                ->where('name', $requestedName)
                ->first();

            if ($row === null) {
                throw new SchoolYearNotFoundException('Selected school year was not found.');
            }

            return $this->authorizedContext($row, $userId, true);
        }

        $sessionYearId = session('selected_school_year_id');

        if (is_numeric($sessionYearId)) {
            $row = $this->schoolYearModel->find((int) $sessionYearId);

            if ($row !== null && $this->isSelectable($row, $userId)) {
                return $this->fromRow($row, true);
            }

            $this->clearSelection();
        }

        return $this->active();
    }

    public function selectableYears(bool $includeDraft = false): array
    {
        $builder = $this->schoolYearModel
            ->orderBy('name', 'DESC')
            ->orderBy('id', 'DESC');

        if (! $includeDraft) {
            $builder->where('status !=', SchoolYearStatus::DRAFT);
        }

        $userId = (int) (session()->get('user_id') ?? 0);

        return array_values(array_filter(
            $builder->findAll(),
            fn (array $row): bool => $this->isSelectable($row, $userId)
        ));
    }

    public function select(int $schoolYearId): SchoolYearContext
    {
        $row = $this->schoolYearModel->find($schoolYearId);

        if ($row === null) {
            throw new SchoolYearNotFoundException('Selected school year was not found.');
        }

        $userId = (int) (session()->get('user_id') ?? 0);
        $context = $this->authorizedContext($row, $userId, true);
        $previousSelection = session('selected_school_year_id');

        if ($context->isActive()) {
            $this->clearSelection();
            $context = $this->fromRow($row, false);
        } else {
            session()->set('selected_school_year_id', $context->id());
            session()->remove('selected_school_year');
        }

        if ((string) $previousSelection !== (string) session('selected_school_year_id')) {
            $this->clearDependentSessionState();
        }

        return $context;
    }

    public function clearSelection(): void
    {
        session()->remove('selected_school_year_id');
        session()->remove('selected_school_year');
        $this->clearDependentSessionState();
    }

    public function active(): SchoolYearContext
    {
        $active = $this->schoolYearModel->active();

        if ($active === null) {
            throw new SchoolYearConfigurationException('Exactly one active school year must be configured.');
        }

        return $this->fromRow($active, false);
    }

    private function normalizeInt(mixed $value): ?int
    {
        if ($value === null || $value === '' || ! ctype_digit((string) $value)) {
            return null;
        }

        return (int) $value;
    }

    private function legacyYearName(IncomingRequest $request): string
    {
        $names = [];

        foreach (['school_year', 'schoolYear', 'year'] as $key) {
            $value = trim((string) ($request->getGet($key) ?? ''));
            if ($value !== '') {
                $names[$key] = $value;
            }
        }

        if (count(array_unique($names)) > 1) {
            throw new InvalidSchoolYearSelectionException('Conflicting school-year parameters were provided.');
        }

        if (isset($names['schoolYear']) || isset($names['year'])) {
            log_message('notice', 'Legacy school-year request alias used: {aliases}', [
                'aliases' => implode(',', array_keys($names)),
            ]);
        }

        return $names === [] ? '' : (string) reset($names);
    }

    private function authorizedContext(array $row, int $userId, bool $explicit): SchoolYearContext
    {
        if (! $this->isSelectable($row, $userId)) {
            throw new SchoolYearNotFoundException('Selected school year was not found.');
        }

        return $this->fromRow($row, $explicit);
    }

    private function isSelectable(array $row, int $userId): bool
    {
        return $this->accessPolicy->canSelect($userId > 0 ? $userId : null, $row);
    }

    private function clearDependentSessionState(): void
    {
        session()->remove([
            'class_section_id',
            'semester',
            'active_semester',
            'active_school_year',
            'teacher_scores_selected_semester',
            'grading_selected_semester',
        ]);
    }

    private function fromRow(array $row, bool $explicit): SchoolYearContext
    {
        return new SchoolYearContext(
            id: (int) $row['id'],
            yearName: (string) $row['name'],
            status: strtolower((string) $row['status']),
            explicitSelection: $explicit,
        );
    }
}
