<?php

namespace App\Services;

use CodeIgniter\Shield\Entities\User;
use DateTimeImmutable;

class EvaluationsService
{
    private AuthorizationService $authorizationService;
    private EvaluationEngineService $evaluationEngine;
    private JourneyService $journeyService;

    public function __construct()
    {
        helper('academy');

        $this->authorizationService = service('authorization');
        $this->evaluationEngine     = service('evaluationEngine');
        $this->journeyService       = service('journey');
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>|null
     */
    public function getIndexData(array $input, User $user, string $typeSlug): ?array
    {
        $type = $this->getEvaluationType($typeSlug);
        if ($type === null) {
            return null;
        }

        $filters     = $this->buildIndexFilters($input, (int) $user->id, $typeSlug);
        $evaluations = $this->getEvaluationRows((int) $type['id'], $filters);
        $stats       = $this->buildStats($evaluations);

        return [
            'pageTitle' => $typeSlug === 'quick-check' ? 'Quick Check' : 'Evaluari',
            'pageDescription' => $typeSlug === 'quick-check'
                ? 'Monitorizare rapida a progresului, separata de evaluarile complete care pot sustine promovarea.'
                : 'Evaluari complete cu scorare ponderata, status calculat automat si semnal de promovare.',
            'scopeType' => $type,
            'filters' => $filters,
            'stats' => $stats,
            'evaluations' => $evaluations,
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @param array<string, string> $errors
     * @return array<string, mixed>|null
     */
    public function getFormData(int $childId, string $typeSlug, User $user, array $input = [], array $errors = []): ?array
    {
        if (! $this->authorizationService->userCanAccessChild((int) $user->id, $childId)) {
            return null;
        }

        $child = $this->getChildRow($childId);
        $type  = $this->getEvaluationType($typeSlug);
        if ($child === null || $type === null) {
            return null;
        }

        $criteria = $this->getCriteriaForType((int) $type['id'], $child['academy_level_id'] !== null ? (int) $child['academy_level_id'] : null);
        $rules    = $this->evaluationEngine->getActiveRuleSet();
        $coachId  = $this->authorizationService->getCoachIdForUser((int) $user->id);
        $defaultEvaluatorTrainerId = $this->authorizationService->userHasRole((int) $user->id, 'coach')
            ? $coachId
            : ((int) ($child['primary_coach_id'] ?? 0) > 0 ? (int) $child['primary_coach_id'] : null);

        $values = [
            'evaluation_date' => date('Y-m-d'),
            'evaluator_coach_id' => $defaultEvaluatorTrainerId !== null ? (string) $defaultEvaluatorTrainerId : '',
            'notes' => '',
            'scores' => [],
        ];

        foreach ($criteria as $criterion) {
            $values['scores'][(int) $criterion['id']] = '';
        }

        if ($input !== []) {
            $values['evaluation_date'] = trim((string) ($input['evaluation_date'] ?? $values['evaluation_date']));
            $values['evaluator_coach_id'] = trim((string) ($input['evaluator_coach_id'] ?? $values['evaluator_coach_id']));
            $values['notes'] = trim((string) ($input['notes'] ?? ''));

            foreach ((array) ($input['scores'] ?? []) as $criterionId => $score) {
                $criterionId = (int) $criterionId;
                if (array_key_exists($criterionId, $values['scores'])) {
                    $values['scores'][$criterionId] = trim((string) $score);
                }
            }
        }

        return [
            'pageTitle' => $type['name'] . ' nou',
            'pageDescription' => 'Evaluarea foloseste criteriile active asociate tipului selectat si calculeaza scorul final in backend.',
            'child' => $child,
            'evaluationType' => $type,
            'criteria' => $criteria,
            'rules' => $rules,
            'values' => $values,
            'errors' => $errors,
            'canChooseEvaluator' => $this->authorizationService->userHasRole((int) $user->id, 'admin'),
            'evaluatorOptions' => $this->coachOptions(),
            'latestEvaluation' => $this->getLatestEvaluationForChildAndType($childId, $typeSlug),
            'submitUrl' => url_to('evaluations.create', $childId, $typeSlug),
            'cancelUrl' => url_to('children.show', $childId) . '?tab=' . ($typeSlug === 'quick-check' ? 'quick-checks' : 'evaluations'),
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @return array{success: bool, evaluation_id?: int, errors?: array<string, string>, forbidden?: bool}
     */
    public function save(int $childId, string $typeSlug, array $input, User $user): array
    {
        if (! $this->authorizationService->userCanAccessChild((int) $user->id, $childId)) {
            return ['success' => false, 'forbidden' => true];
        }

        $child = $this->getChildRow($childId);
        $type  = $this->getEvaluationType($typeSlug);
        if ($child === null || $type === null) {
            return ['success' => false, 'forbidden' => true];
        }

        $criteria = $this->getCriteriaForType((int) $type['id'], $child['academy_level_id'] !== null ? (int) $child['academy_level_id'] : null);
        $payload  = [
            'evaluation_date' => trim((string) ($input['evaluation_date'] ?? '')),
            'evaluator_coach_id' => trim((string) ($input['evaluator_coach_id'] ?? '')),
            'notes' => trim((string) ($input['notes'] ?? '')),
            'scores' => (array) ($input['scores'] ?? []),
        ];

        $validation = service('validation');
        $validation->setRules(config('Validation')->evaluation);

        $errors = [];

        if (! $validation->run($payload)) {
            $errors = $validation->getErrors();
        }

        $criteriaRows = [];
        foreach ($criteria as $criterion) {
            $criterionId = (int) $criterion['id'];
            $value       = trim((string) ($payload['scores'][$criterionId] ?? ''));

            if ($value === '') {
                $errors['score_' . $criterionId] = 'Scorul este obligatoriu.';
                continue;
            }

            if (! is_numeric($value)) {
                $errors['score_' . $criterionId] = 'Scorul trebuie sa fie numeric.';
                continue;
            }

            $score = (float) $value;
            $min   = (float) $criterion['min_score'];
            $max   = (float) $criterion['max_score'];

            if ($score < $min || $score > $max) {
                $errors['score_' . $criterionId] = 'Scorul trebuie sa fie intre ' . format_score($min, 0) . ' si ' . format_score($max, 0) . '.';
                continue;
            }

            $criteriaRows[] = [
                'evaluation_criteria_id' => $criterionId,
                'weight' => (float) $criterion['weight'],
                'score' => $score,
                'is_critical' => (bool) $criterion['is_critical'],
                'notes' => null,
            ];
        }

        if ($errors !== []) {
            return ['success' => false, 'errors' => $errors];
        }

        $currentTrainerId = $this->authorizationService->getCoachIdForUser((int) $user->id);
        $evaluatorTrainerId = $this->authorizationService->userHasRole((int) $user->id, 'coach')
            ? $currentTrainerId
            : ($payload['evaluator_coach_id'] !== '' ? (int) $payload['evaluator_coach_id'] : null);

        $rules     = $this->evaluationEngine->getActiveRuleSet();
        $result    = $this->evaluationEngine->evaluate($criteriaRows, $rules);
        $signal    = (int) ($type['affects_promotion'] ? $result['promotion_signal'] : false);
        $timestamp = date('Y-m-d H:i:s');

        $db = $this->db();
        $db->transStart();

        $db->table('evaluations')->insert([
            'child_id' => $childId,
            'evaluation_type_id' => (int) $type['id'],
            'evaluation_date' => $payload['evaluation_date'],
            'evaluator_user_id' => (int) $user->id,
            'evaluator_coach_id' => $evaluatorTrainerId,
            'level_at_time_id' => $child['academy_level_id'] !== null ? (int) $child['academy_level_id'] : null,
            'final_score' => $result['final_score'],
            'final_status' => $result['final_status'],
            'promotion_signal' => $signal,
            'notes' => $payload['notes'] !== '' ? $payload['notes'] : null,
            'created_at' => $timestamp,
            'updated_at' => $timestamp,
        ]);

        $evaluationId = (int) $db->insertID();

        foreach ($criteriaRows as $criteriaRow) {
            $db->table('evaluation_scores')->insert(array_merge($criteriaRow, [
                'evaluation_id' => $evaluationId,
            ]));
        }

        $db->table('evaluation_status_history')->insert([
            'evaluation_id' => $evaluationId,
            'previous_status' => null,
            'new_status' => $result['final_status'],
            'reason' => 'Status initial calculat automat la creare.',
            'changed_by' => (int) $user->id,
            'created_at' => $timestamp,
        ]);

        $this->journeyService->recordEvent([
            'child_id' => $childId,
            'event_type' => $typeSlug === 'quick-check' ? 'quick_check_added' : 'evaluation_added',
            'title' => $typeSlug === 'quick-check' ? 'Quick Check adaugat' : 'Full Evaluation adaugata',
            'description' => $payload['notes'] !== '' ? $payload['notes'] : 'Evaluare noua adaugata din aplicatie.',
            'metadata' => json_encode([
                'evaluation_id' => $evaluationId,
                'type' => $typeSlug,
                'score' => $result['final_score'],
                'status' => $result['final_status'],
                'promotion_signal' => $signal,
            ], JSON_UNESCAPED_UNICODE),
            'created_by' => (int) $user->id,
            'created_at' => $timestamp,
        ]);

        $db->transComplete();

        if (! $db->transStatus()) {
            return ['success' => false, 'errors' => ['general' => 'Nu am putut salva evaluarea.']];
        }

        return ['success' => true, 'evaluation_id' => $evaluationId];
    }

    /**
     * @return array<string, mixed>|null
     */
    public function getDetailData(int $evaluationId, User $user): ?array
    {
        $evaluation = $this->db()
            ->table('evaluations e')
            ->select('e.*, children.full_name as child_name, children.id as child_id, children.status as child_status, children.primary_coach_id as child_primary_coach_id, types.name as type_name, types.slug as type_slug, levels.name as level_name, coaches.full_name as evaluator_coach_name, users.username as evaluator_username')
            ->join('children', 'children.id = e.child_id')
            ->join('evaluation_types types', 'types.id = e.evaluation_type_id')
            ->join('academy_levels levels', 'levels.id = e.level_at_time_id', 'left')
            ->join('coaches', 'coaches.id = e.evaluator_coach_id', 'left')
            ->join('users', 'users.id = e.evaluator_user_id', 'left')
            ->where('e.id', $evaluationId)
            ->get()
            ->getRowArray();

        if ($evaluation === null || ! $this->authorizationService->userCanAccessChild((int) $user->id, (int) $evaluation['child_id'])) {
            return null;
        }

        $criteriaScores = $this->db()
            ->table('evaluation_scores scores')
            ->select('scores.score, scores.weight, scores.is_critical, scores.notes, criteria.name, criteria.min_score, criteria.max_score')
            ->join('evaluation_criteria criteria', 'criteria.id = scores.evaluation_criteria_id')
            ->where('scores.evaluation_id', $evaluationId)
            ->orderBy('criteria.sort_order', 'ASC')
            ->get()
            ->getResultArray();

        $statusHistory = $this->db()
            ->table('evaluation_status_history history')
            ->select('history.*, users.username')
            ->join('users', 'users.id = history.changed_by', 'left')
            ->where('history.evaluation_id', $evaluationId)
            ->orderBy('history.created_at', 'DESC')
            ->get()
            ->getResultArray();

        return [
            'pageTitle' => $evaluation['type_name'],
            'pageDescription' => 'Detaliul complet al evaluarii, inclusiv criteriile punctate si istoricul statusului rezultat.',
            'evaluation' => $evaluation,
            'criteriaScores' => $criteriaScores,
            'statusHistory' => $statusHistory,
            'rules' => $this->evaluationEngine->getActiveRuleSet(),
            'childProfileUrl' => url_to('children.show', $evaluation['child_id']) . '?tab=' . ($evaluation['type_slug'] === 'quick-check' ? 'quick-checks' : 'evaluations'),
            'moduleUrl' => $evaluation['type_slug'] === 'quick-check' ? url_to('quickChecks.index') : url_to('evaluations.index'),
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    private function buildIndexFilters(array $input, int $userId, string $typeSlug): array
    {
        $primaryRole = $this->authorizationService->getPrimaryRole($userId);
        $coachId     = $primaryRole === 'coach' ? $this->authorizationService->getCoachIdForUser($userId) : null;
        $selectedPeriod = (string) ($input['period'] ?? '30');
        $today       = new DateTimeImmutable('today');
        $dateStart   = null;
        $dateEnd     = null;

        if ($selectedPeriod === '30' || $selectedPeriod === '90' || $selectedPeriod === '7') {
            $days      = (int) $selectedPeriod;
            $dateStart = $today->modify('-' . ($days - 1) . ' days')->format('Y-m-d');
            $dateEnd   = $today->format('Y-m-d');
        } else {
            $selectedPeriod = 'all';
        }

        $selectedTrainer = $coachId ?: (int) ($input['coach_id'] ?? 0);
        $selectedStatus = (string) ($input['status'] ?? 'all');

        return [
            'type_slug' => $typeSlug,
            'current_role' => $primaryRole,
            'coach_locked' => $primaryRole === 'coach',
            'selected_coach_id' => $selectedTrainer,
            'selected_status' => in_array($selectedStatus, ['all', 'READY', 'ALMOST READY', 'HOLD'], true) ? $selectedStatus : 'all',
            'query' => trim((string) ($input['q'] ?? '')),
            'selected_period' => $selectedPeriod,
            'date_start' => $dateStart,
            'date_end' => $dateEnd,
            'period_options' => [
                '7' => 'Ultimele 7 zile',
                '30' => 'Ultimele 30 zile',
                '90' => 'Ultimele 90 zile',
                'all' => 'Toata perioada',
            ],
            'status_options' => [
                'all' => 'Toate statusurile',
                'READY' => 'Ready',
                'ALMOST READY' => 'Almost Ready',
                'HOLD' => 'Hold',
            ],
            'coach_options' => $this->coachOptions(),
        ];
    }

    /**
     * @param array<string, mixed> $filters
     * @return list<array<string, mixed>>
     */
    private function getEvaluationRows(int $typeId, array $filters): array
    {
        $builder = $this->db()
            ->table('evaluations e')
            ->select('e.id, e.evaluation_date, e.final_score, e.final_status, e.promotion_signal, e.notes, children.id as child_id, children.full_name as child_name, children.status as child_status, coaches.full_name as evaluator_coach_name')
            ->join('children', 'children.id = e.child_id')
            ->join('coaches', 'coaches.id = e.evaluator_coach_id', 'left')
            ->where('e.evaluation_type_id', $typeId)
            ->orderBy('e.evaluation_date', 'DESC')
            ->orderBy('e.id', 'DESC');

        if ($filters['selected_coach_id'] > 0) {
            $builder->where('children.primary_coach_id', $filters['selected_coach_id']);
        }

        if ($filters['selected_status'] !== 'all') {
            $builder->where('e.final_status', $filters['selected_status']);
        }

        if ($filters['query'] !== '') {
            $builder->groupStart()
                ->like('children.full_name', $filters['query'])
                ->orLike('coaches.full_name', $filters['query'])
                ->groupEnd();
        }

        if ($filters['date_start'] !== null && $filters['date_end'] !== null) {
            $builder->where('e.evaluation_date >=', $filters['date_start'])
                ->where('e.evaluation_date <=', $filters['date_end']);
        }

        return $builder->get()->getResultArray();
    }

    /**
     * @param list<array<string, mixed>> $evaluations
     * @return list<array<string, mixed>>
     */
    private function buildStats(array $evaluations): array
    {
        $ready = count(array_filter($evaluations, static fn (array $row): bool => $row['final_status'] === 'READY'));
        $almost = count(array_filter($evaluations, static fn (array $row): bool => $row['final_status'] === 'ALMOST READY'));
        $avgScore = $evaluations === []
            ? null
            : round(array_sum(array_map(static fn (array $row): float => (float) $row['final_score'], $evaluations)) / count($evaluations), 2);

        return [
            ['label' => 'Total evaluari', 'value' => count($evaluations), 'meta' => 'Rezultate in filtrul curent', 'icon' => 'eval'],
            ['label' => 'Ready', 'value' => $ready, 'meta' => 'Status READY calculat automat', 'icon' => 'rdy'],
            ['label' => 'Almost Ready', 'value' => $almost, 'meta' => 'Sub pragul final, peste pragul de alerta', 'icon' => 'alm'],
            ['label' => 'Scor mediu', 'value' => format_score($avgScore), 'meta' => 'Media scorurilor finale in selectie', 'icon' => 'avg'],
        ];
    }

    /**
     * @return array<string, mixed>|null
     */
    private function getChildRow(int $childId): ?array
    {
        return $this->db()
            ->table('children c')
            ->select('c.id, c.full_name, c.status, c.academy_level_id, c.primary_coach_id, levels.name as level_name, groups.name as group_name, coaches.full_name as coach_name')
            ->join('academy_levels levels', 'levels.id = c.academy_level_id', 'left')
            ->join('academy_groups groups', 'groups.id = c.academy_group_id', 'left')
            ->join('coaches', 'coaches.id = c.primary_coach_id', 'left')
            ->where('c.id', $childId)
            ->get()
            ->getRowArray();
    }

    /**
     * @return array<string, mixed>|null
     */
    private function getEvaluationType(string $typeSlug): ?array
    {
        return $this->db()
            ->table('evaluation_types')
            ->where('slug', $typeSlug)
            ->where('status', 'active')
            ->get()
            ->getRowArray();
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function getCriteriaForType(int $typeId, ?int $levelId = null): array
    {
        if ($levelId !== null) {
            $levelCriteria = $this->db()
                ->table('academy_level_criteria level_criteria')
                ->select('criteria.id, criteria.name, criteria.description, criteria.min_score, criteria.max_score, criteria.is_critical, level_criteria.weight, level_criteria.is_required')
                ->join('evaluation_criteria criteria', 'criteria.id = level_criteria.evaluation_criteria_id')
                ->where('level_criteria.academy_level_id', $levelId)
                ->where('level_criteria.evaluation_type_id', $typeId)
                ->where('criteria.is_active', 1)
                ->orderBy('level_criteria.sort_order', 'ASC')
                ->orderBy('criteria.id', 'ASC')
                ->get()
                ->getResultArray();

            if ($levelCriteria !== []) {
                return $levelCriteria;
            }
        }

        return $this->db()
            ->table('evaluation_type_criteria pivot')
            ->select('criteria.id, criteria.name, criteria.description, criteria.min_score, criteria.max_score, criteria.is_critical, pivot.weight, pivot.is_required')
            ->join('evaluation_criteria criteria', 'criteria.id = pivot.evaluation_criteria_id')
            ->where('pivot.evaluation_type_id', $typeId)
            ->where('criteria.is_active', 1)
            ->orderBy('pivot.sort_order', 'ASC')
            ->get()
            ->getResultArray();
    }

    /**
     * @return array<string, mixed>|null
     */
    private function getLatestEvaluationForChildAndType(int $childId, string $typeSlug): ?array
    {
        return $this->db()
            ->table('evaluations e')
            ->select('e.evaluation_date, e.final_score, e.final_status, e.notes, coaches.full_name as evaluator_name')
            ->join('evaluation_types types', 'types.id = e.evaluation_type_id')
            ->join('coaches', 'coaches.id = e.evaluator_coach_id', 'left')
            ->where('e.child_id', $childId)
            ->where('types.slug', $typeSlug)
            ->orderBy('e.evaluation_date', 'DESC')
            ->orderBy('e.id', 'DESC')
            ->get()
            ->getRowArray();
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function coachOptions(): array
    {
        return $this->db()
            ->table('coaches')
            ->select('id, full_name')
            ->where('is_active', 1)
            ->orderBy('full_name', 'ASC')
            ->get()
            ->getResultArray();
    }

    private function db()
    {
        return db_connect();
    }
}
