<?php

namespace App\Services;

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

class DashboardService
{
    private AttendanceService $attendanceService;
    private AuthorizationService $authorizationService;

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

        $this->attendanceService    = service('attendance');
        $this->authorizationService = service('authorization');
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function getDashboardData(array $input, User $user): array
    {
        $filters          = $this->buildFilters($input, (int) $user->id);
        $children         = $this->getScopedChildren($filters);
        $childIds         = array_map(static fn (array $child): int => (int) $child['id'], $children);
        $activeChildren   = array_values(array_filter($children, static fn (array $child): bool => $child['status'] === 'active'));
        $activeChildIds   = array_map(static fn (array $child): int => (int) $child['id'], $activeChildren);
        $allEvaluations   = $this->getEvaluations($childIds, null, null);
        $periodEvaluations = $this->getEvaluations($childIds, $filters['date_start'], $filters['date_end']);
        $latestFull       = $this->latestEvaluationsByChild($allEvaluations, 'full-evaluation');
        $latestAny        = $this->latestEvaluationsByChild($allEvaluations, null);
        $attendanceRows   = $this->getAttendanceRows($childIds, $filters['date_start'], $filters['date_end']);

        return [
            'pageTitle' => 'Dashboard',
            'filters' => $filters,
            'kpis' => $this->buildKpis($activeChildren, $periodEvaluations, $latestFull, $attendanceRows),
            'levelDistribution' => $this->buildLevelDistribution($activeChildren),
            'childrenPerCoach' => $this->buildChildrenPerCoach($activeChildren),
            'recentEvaluations' => $this->buildRecentEvaluations($periodEvaluations),
            'alerts' => $this->buildAlerts($children, $latestFull, $latestAny, $attendanceRows, $filters),
        ];
    }

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

        $levelOptions = $this->db()
            ->table('academy_levels')
            ->select('id, name, slug, color_hex')
            ->orderBy('sort_order', 'ASC')
            ->get()
            ->getResultArray();

        $coachOptions = $this->db()
            ->table('coaches')
            ->select('id, full_name')
            ->where('is_active', 1)
            ->orderBy('full_name', 'ASC')
            ->get()
            ->getResultArray();

        $selectedTrainer = $coachId ?: (int) ($input['coach_id'] ?? 0);
        $selectedLevel = (int) ($input['level_id'] ?? 0);
        $selectedStatus = (string) ($input['child_status'] ?? 'all');
        if (! in_array($selectedStatus, ['all', 'active', 'pause', 'left'], true)) {
            $selectedStatus = 'all';
        }

        return [
            'current_role' => $primaryRole,
            'date_start' => $dateStart,
            'date_end' => $dateEnd,
            'selected_period' => $selectedPeriod,
            'selected_coach_id' => $selectedTrainer,
            'selected_level_id' => $selectedLevel,
            'selected_child_status' => $selectedStatus,
            'coach_locked' => $primaryRole === 'coach',
            'period_options' => [
                '7' => 'Ultimele 7 zile',
                '30' => 'Ultimele 30 zile',
                '90' => 'Ultimele 90 zile',
                'month' => 'Luna curenta',
            ],
            'status_options' => [
                'all' => 'Toate statusurile',
                'active' => 'Activ',
                'pause' => 'Pauza',
                'left' => 'Plecat',
            ],
            'coach_options' => $coachOptions,
            'level_options' => $levelOptions,
            'no_evaluation_days' => (int) ($settings['no_evaluation_days'] ?? 45),
            'absence_alert_threshold' => (int) ($settings['absence_alert_threshold'] ?? 25),
        ];
    }

    /**
     * @return array{0: string, 1: string}
     */
    private function resolveDates(string $selectedPeriod, DateTimeImmutable $today): array
    {
        if ($selectedPeriod === 'month') {
            return [
                $today->modify('first day of this month')->format('Y-m-d'),
                $today->format('Y-m-d'),
            ];
        }

        $days = (int) $selectedPeriod;
        if (! in_array($days, [7, 30, 90], true)) {
            $days = 30;
        }

        return [
            $today->modify('-' . ($days - 1) . ' days')->format('Y-m-d'),
            $today->format('Y-m-d'),
        ];
    }

    /**
     * @param array<string, mixed> $filters
     * @return list<array<string, mixed>>
     */
    private function getScopedChildren(array $filters): array
    {
        $builder = $this->db()
            ->table('children c')
            ->select('c.id, c.full_name, c.status, c.primary_coach_id, c.academy_level_id, levels.name as level_name, levels.slug as level_slug, levels.color_hex, 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')
            ->orderBy('c.full_name', 'ASC');

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

        if ($filters['selected_level_id'] > 0) {
            $builder->where('c.academy_level_id', $filters['selected_level_id']);
        }

        if ($filters['selected_child_status'] !== 'all') {
            $builder->where('c.status', $filters['selected_child_status']);
        }

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

    /**
     * @param list<int> $childIds
     * @return list<array<string, mixed>>
     */
    private function getEvaluations(array $childIds, ?string $dateStart, ?string $dateEnd): array
    {
        if ($childIds === []) {
            return [];
        }

        $builder = $this->db()
            ->table('evaluations e')
            ->select('e.id, e.child_id, e.evaluation_date, e.final_score, e.final_status, e.promotion_signal, children.full_name as child_name, types.name as type_name, types.slug as type_slug')
            ->join('children', 'children.id = e.child_id')
            ->join('evaluation_types types', 'types.id = e.evaluation_type_id')
            ->whereIn('e.child_id', $childIds)
            ->orderBy('e.evaluation_date', 'DESC')
            ->orderBy('e.id', 'DESC');

        if ($dateStart !== null && $dateEnd !== null) {
            $builder->where('e.evaluation_date >=', $dateStart)->where('e.evaluation_date <=', $dateEnd);
        }

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

    /**
     * @param list<int> $childIds
     * @return list<array<string, mixed>>
     */
    private function getAttendanceRows(array $childIds, string $dateStart, string $dateEnd): array
    {
        if ($childIds === []) {
            return [];
        }

        return $this->db()
            ->table('attendance_records ar')
            ->select('ar.child_id, ar.status, children.full_name as child_name, sessions.session_date')
            ->join('children', 'children.id = ar.child_id')
            ->join('attendance_sessions sessions', 'sessions.id = ar.attendance_session_id')
            ->whereIn('ar.child_id', $childIds)
            ->where('sessions.is_cancelled', 0)
            ->where('sessions.session_date >=', $dateStart)
            ->where('sessions.session_date <=', $dateEnd)
            ->get()
            ->getResultArray();
    }

    /**
     * @param list<array<string, mixed>> $evaluations
     * @return array<int, array<string, mixed>>
     */
    private function latestEvaluationsByChild(array $evaluations, ?string $typeSlug): array
    {
        $latest = [];

        foreach ($evaluations as $evaluation) {
            if ($typeSlug !== null && $evaluation['type_slug'] !== $typeSlug) {
                continue;
            }

            $childId = (int) $evaluation['child_id'];
            if (! isset($latest[$childId])) {
                $latest[$childId] = $evaluation;
            }
        }

        return $latest;
    }

    /**
     * @param list<array<string, mixed>> $activeChildren
     * @param list<array<string, mixed>> $periodEvaluations
     * @param array<int, array<string, mixed>> $latestFull
     * @param list<array<string, mixed>> $attendanceRows
     * @return list<array<string, mixed>>
     */
    private function buildKpis(array $activeChildren, array $periodEvaluations, array $latestFull, array $attendanceRows): array
    {
        $readyCount = 0;
        $almostCount = 0;

        foreach ($activeChildren as $child) {
            $latest = $latestFull[(int) $child['id']] ?? null;

            if (! $latest) {
                continue;
            }

            if ($latest['final_status'] === 'READY') {
                $readyCount++;
            }

            if ($latest['final_status'] === 'ALMOST READY') {
                $almostCount++;
            }
        }

        $attendanceBreakdown = [
            'present' => 0,
            'recovery' => 0,
            'absent_excused' => 0,
            'absent_unexcused' => 0,
        ];

        foreach ($attendanceRows as $row) {
            $status = (string) $row['status'];
            if (! isset($attendanceBreakdown[$status])) {
                $attendanceBreakdown[$status] = 0;
            }

            $attendanceBreakdown[$status]++;
        }

        return [
            [
                'label' => 'Copii activi',
                'value' => count($activeChildren),
                'meta' => 'Inscrieri active in selectie',
                'icon' => 'children',
            ],
            [
                'label' => 'Evaluari in perioada',
                'value' => count($periodEvaluations),
                'meta' => 'Quick Check + Full Evaluation',
                'icon' => 'evaluations',
            ],
            [
                'label' => 'Ready',
                'value' => $readyCount,
                'meta' => 'Ultimul full evaluation este READY',
                'icon' => 'ready',
            ],
            [
                'label' => 'Aproape gata',
                'value' => $almostCount,
                'meta' => 'Copii aproape de promovare',
                'icon' => 'almost',
            ],
            [
                'label' => 'Rata prezenta',
                'value' => format_percentage($this->attendanceService->calculateRateFromBreakdown($attendanceBreakdown), 1),
                'meta' => 'Raport prezente + recovery / total sesiuni',
                'icon' => 'attendance',
            ],
        ];
    }

    /**
     * @param list<array<string, mixed>> $activeChildren
     * @return array<string, mixed>
     */
    private function buildLevelDistribution(array $activeChildren): array
    {
        $grouped = [];

        foreach ($activeChildren as $child) {
            $key = (string) $child['level_slug'];
            if (! isset($grouped[$key])) {
                $grouped[$key] = [
                    'label' => $child['level_name'],
                    'count' => 0,
                    'color' => $child['color_hex'] ?: '#94a3b8',
                ];
            }

            $grouped[$key]['count']++;
        }

        return [
            'labels' => array_values(array_map(static fn (array $row): string => $row['label'], $grouped)),
            'series' => array_values(array_map(static fn (array $row): int => $row['count'], $grouped)),
            'colors' => array_values(array_map(static fn (array $row): string => $row['color'], $grouped)),
        ];
    }

    /**
     * @param list<array<string, mixed>> $activeChildren
     * @return array<string, mixed>
     */
    private function buildChildrenPerCoach(array $activeChildren): array
    {
        $grouped = [];

        foreach ($activeChildren as $child) {
            $key = $child['coach_name'] ?: 'Neasignat';
            if (! isset($grouped[$key])) {
                $grouped[$key] = 0;
            }

            $grouped[$key]++;
        }

        return [
            'categories' => array_keys($grouped),
            'series' => array_values($grouped),
        ];
    }

    /**
     * @param list<array<string, mixed>> $periodEvaluations
     * @return list<array<string, mixed>>
     */
    private function buildRecentEvaluations(array $periodEvaluations): array
    {
        return array_slice(array_map(static function (array $evaluation): array {
            return [
                'child_name' => $evaluation['child_name'],
                'date' => $evaluation['evaluation_date'],
                'type_name' => $evaluation['type_name'],
                'final_score' => number_format((float) $evaluation['final_score'], 2),
                'final_status' => $evaluation['final_status'],
            ];
        }, $periodEvaluations), 0, 6);
    }

    /**
     * @param list<array<string, mixed>> $children
     * @param array<int, array<string, mixed>> $latestFull
     * @param array<int, array<string, mixed>> $latestAny
     * @param list<array<string, mixed>> $attendanceRows
     * @param array<string, mixed> $filters
     * @return list<array<string, mixed>>
     */
    private function buildAlerts(array $children, array $latestFull, array $latestAny, array $attendanceRows, array $filters): array
    {
        $buckets = [
            'ready' => [],
            'almost' => [],
            'attendance' => [],
            'overdue' => [],
        ];
        $attendanceByChild = [];

        foreach ($attendanceRows as $row) {
            $childId = (int) $row['child_id'];
            if (! isset($attendanceByChild[$childId])) {
                $attendanceByChild[$childId] = ['present' => 0, 'recovery' => 0, 'absent_excused' => 0, 'absent_unexcused' => 0];
            }

            $attendanceByChild[$childId][$row['status']]++;
        }

        foreach ($children as $child) {
            if ($child['status'] === 'left') {
                continue;
            }

            $childId   = (int) $child['id'];
            $childName = $child['full_name'];
            $latest    = $latestFull[$childId] ?? null;

            if ($latest && $latest['final_status'] === 'READY') {
                $buckets['ready'][] = [
                    'type' => 'ready',
                    'child_name' => $childName,
                    'title' => 'Ready pentru avansare',
                    'description' => 'Ultimul full evaluation este READY (' . number_format((float) $latest['final_score'], 2) . ').',
                ];
            } elseif ($latest && $latest['final_status'] === 'ALMOST READY') {
                $buckets['almost'][] = [
                    'type' => 'almost',
                    'child_name' => $childName,
                    'title' => 'Aproape gata',
                    'description' => 'Copilul este aproape de pragul READY in ultimul full evaluation.',
                ];
            }

            $attendance = $attendanceByChild[$childId] ?? ['present' => 0, 'recovery' => 0, 'absent_excused' => 0, 'absent_unexcused' => 0];
            $attendanceRate = $this->attendanceService->calculateRateFromBreakdown($attendance);
            $absenceRate    = 100 - $attendanceRate;

            if (array_sum($attendance) > 0 && $absenceRate >= (float) $filters['absence_alert_threshold']) {
                $buckets['attendance'][] = [
                    'type' => 'attendance',
                    'child_name' => $childName,
                    'title' => 'Absente ridicate',
                    'description' => 'Absenta estimata la ' . format_percentage($absenceRate, 1) . ' in perioada filtrata.',
                ];
            }

            $latestEvaluation = $latestAny[$childId] ?? null;
            if (! $latestEvaluation) {
                continue;
            }

            $daysSinceEvaluation = (new DateTimeImmutable($filters['date_end']))->diff(new DateTimeImmutable($latestEvaluation['evaluation_date']))->days;

            if ($daysSinceEvaluation >= (int) $filters['no_evaluation_days']) {
                $buckets['overdue'][] = [
                    'type' => 'overdue',
                    'child_name' => $childName,
                    'title' => 'Fara evaluare recenta',
                    'description' => 'Ultima evaluare a fost inregistrata acum ' . $daysSinceEvaluation . ' zile.',
                ];
            }
        }

        $alerts = array_merge(
            array_slice($buckets['ready'], 0, 2),
            array_slice($buckets['almost'], 0, 2),
            array_slice($buckets['attendance'], 0, 2),
            array_slice($buckets['overdue'], 0, 2),
        );

        return array_slice($alerts, 0, 8);
    }

    /**
     * @return array<string, string>
     */
    private function settingsMap(): array
    {
        $rows = $this->db()
            ->table('settings')
            ->select('`key`, value')
            ->get()
            ->getResultArray();

        $settings = [];
        foreach ($rows as $row) {
            $settings[$row['key']] = $row['value'];
        }

        return $settings;
    }

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