<?php

namespace App\Services;

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

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

    public function __construct()
    {
        helper(['academy', 'url']);

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

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function getIndexData(array $input, User $user): array
    {
        $filters       = $this->buildFilters($input, (int) $user->id);
        $dynamicAlerts = $this->getDynamicAlerts($filters);
        $notifications = $this->getNotifications((int) $user->id, $filters['selected_notification_status']);

        return [
            'pageTitle' => 'Alerte',
            'pageDescription' => 'Semnale operationale generate din evaluari, prezenta si vechimea ultimei evaluari, plus inbox-ul de notificari.',
            'filters' => $filters,
            'summary' => $this->buildSummary($dynamicAlerts, $notifications),
            'dynamicAlerts' => $dynamicAlerts,
            'notifications' => $notifications,
        ];
    }

    public function markNotificationRead(int $notificationId, User $user): bool
    {
        $notification = db_connect()
            ->table('notifications')
            ->select('id, user_id')
            ->where('id', $notificationId)
            ->get()
            ->getRowArray();

        if ($notification === null || (int) $notification['user_id'] !== (int) $user->id) {
            return false;
        }

        db_connect()
            ->table('notifications')
            ->where('id', $notificationId)
            ->update([
                'status' => 'read',
                'read_at' => date('Y-m-d H:i:s'),
            ]);

        return true;
    }

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

        if (! in_array($selectedType, ['all', 'ready', 'almost', 'attendance', 'overdue'], true)) {
            $selectedType = 'all';
        }

        if (! in_array($notificationStatus, ['all', 'unread', 'read'], true)) {
            $notificationStatus = 'all';
        }

        return [
            'current_role' => $role,
            'coach_locked' => $lockedTrainerId !== null,
            'selected_period' => $period,
            'selected_type' => $selectedType,
            'selected_notification_status' => $notificationStatus,
            'date_start' => $dateStart,
            'date_end' => $dateEnd,
            'type_options' => [
                'all' => 'Toate alertele',
                'ready' => 'Ready',
                'almost' => 'Almost Ready',
                'attendance' => 'Absente ridicate',
                'overdue' => 'Fara evaluare recenta',
            ],
            'notification_status_options' => [
                'all' => 'Toate notificarile',
                'unread' => 'Unread',
                'read' => 'Read',
            ],
            'period_options' => [
                '7' => 'Ultimele 7 zile',
                '30' => 'Ultimele 30 zile',
                '90' => 'Ultimele 90 zile',
                'month' => 'Luna curenta',
            ],
            'locked_coach_id' => $lockedTrainerId,
            '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 getDynamicAlerts(array $filters): array
    {
        $children       = $this->getScopedChildren($filters['locked_coach_id']);
        $childIds       = array_values(array_map(static fn (array $child): int => (int) $child['id'], $children));
        $allEvaluations = $this->getEvaluations($childIds, null, null);
        $latestFull     = $this->latestEvaluationsByChild($allEvaluations, 'full-evaluation');
        $latestAny      = $this->latestEvaluationsByChild($allEvaluations, null);
        $attendanceRows = $this->getAttendanceRows($childIds, $filters['date_start'], $filters['date_end']);
        $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']]++;
        }

        $alerts = [];

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

            $childId   = (int) $child['id'];
            $childName = (string) $child['full_name'];
            $link      = url_to('children.show', $childId);
            $latest    = $latestFull[$childId] ?? null;

            if ($latest && $latest['final_status'] === 'READY') {
                $alerts[] = [
                    'type' => 'ready',
                    'badge' => 'PROMOTION_READY',
                    'title' => 'Ready pentru avansare',
                    'child_name' => $childName,
                    'description' => 'Ultimul full evaluation este READY cu scor ' . format_score($latest['final_score']) . '.',
                    'meta' => ($child['level_name'] ?: '-') . ' / ' . ($child['coach_name'] ?: '-'),
                    'url' => $link,
                ];
            } elseif ($latest && $latest['final_status'] === 'ALMOST READY') {
                $alerts[] = [
                    'type' => 'almost',
                    'badge' => 'ALMOST_READY',
                    'title' => 'Aproape gata',
                    'child_name' => $childName,
                    'description' => 'Copilul este aproape de pragul READY in ultimul full evaluation.',
                    'meta' => ($child['level_name'] ?: '-') . ' / ' . ($child['coach_name'] ?: '-'),
                    'url' => $link,
                ];
            }

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

                if ($absenceRate >= (float) $filters['absence_alert_threshold']) {
                    $alerts[] = [
                        'type' => 'attendance',
                        'badge' => 'ATTENDANCE_ALERT',
                        'title' => 'Absente ridicate',
                        'child_name' => $childName,
                        'description' => 'Absenta estimata la ' . format_percentage($absenceRate, 1) . ' in perioada selectata.',
                        'meta' => ($child['group_name'] ?: '-') . ' / ' . ($child['coach_name'] ?: '-'),
                        'url' => $link,
                    ];
                }
            }

            $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']) {
                $alerts[] = [
                    'type' => 'overdue',
                    'badge' => 'EVALUATION_OVERDUE',
                    'title' => 'Fara evaluare recenta',
                    'child_name' => $childName,
                    'description' => 'Ultima evaluare a fost inregistrata acum ' . $daysSinceEvaluation . ' zile.',
                    'meta' => ($child['level_name'] ?: '-') . ' / ' . ($child['coach_name'] ?: '-'),
                    'url' => $link,
                ];
            }
        }

        if ($filters['selected_type'] !== 'all') {
            $alerts = array_values(array_filter(
                $alerts,
                static fn (array $alert): bool => $alert['type'] === $filters['selected_type'],
            ));
        }

        usort($alerts, static function (array $left, array $right): int {
            $priority = ['ready' => 1, 'almost' => 2, 'attendance' => 3, 'overdue' => 4];
            $leftRank = $priority[$left['type']] ?? 99;
            $rightRank = $priority[$right['type']] ?? 99;

            if ($leftRank === $rightRank) {
                return strcmp($left['child_name'], $right['child_name']);
            }

            return $leftRank <=> $rightRank;
        });

        return $alerts;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function getScopedChildren(?int $lockedTrainerId): array
    {
        $builder = db_connect()
            ->table('children c')
            ->select('c.id, c.full_name, c.status, 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')
            ->orderBy('c.full_name', 'ASC');

        if ($lockedTrainerId !== null) {
            $builder->where('c.primary_coach_id', $lockedTrainerId);
        }

        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 = db_connect()
            ->table('evaluations e')
            ->select('e.child_id, e.evaluation_date, e.final_score, e.final_status, types.slug as type_slug')
            ->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<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<int> $childIds
     * @return list<array<string, mixed>>
     */
    private function getAttendanceRows(array $childIds, string $dateStart, string $dateEnd): array
    {
        if ($childIds === []) {
            return [];
        }

        return db_connect()
            ->table('attendance_records ar')
            ->select('ar.child_id, ar.status')
            ->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();
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function getNotifications(int $userId, string $selectedStatus): array
    {
        $builder = db_connect()
            ->table('notifications n')
            ->select('n.*, children.full_name as child_name')
            ->join('children', 'children.id = n.child_id', 'left')
            ->where('n.user_id', $userId)
            ->orderBy("CASE WHEN n.status = 'unread' THEN 0 ELSE 1 END", '', false)
            ->orderBy('n.created_at', 'DESC');

        if ($selectedStatus !== 'all') {
            $builder->where('n.status', $selectedStatus);
        }

        $rows = $builder->get()->getResultArray();

        foreach ($rows as &$row) {
            $row['resolved_url'] = $row['action_url'] ?: (! empty($row['child_id']) ? url_to('children.show', $row['child_id']) : url_to('dashboard'));
        }
        unset($row);

        return $rows;
    }

    /**
     * @param list<array<string, mixed>> $dynamicAlerts
     * @param list<array<string, mixed>> $notifications
     * @return list<array<string, mixed>>
     */
    private function buildSummary(array $dynamicAlerts, array $notifications): array
    {
        $byType = ['ready' => 0, 'almost' => 0, 'attendance' => 0, 'overdue' => 0];
        foreach ($dynamicAlerts as $alert) {
            $type = (string) $alert['type'];
            if (isset($byType[$type])) {
                $byType[$type]++;
            }
        }

        $unread = count(array_filter($notifications, static fn (array $row): bool => $row['status'] === 'unread'));

        return [
            ['label' => 'Alerte dinamice', 'value' => count($dynamicAlerts), 'meta' => 'Generate in timp real din DB'],
            ['label' => 'Unread notifications', 'value' => $unread, 'meta' => 'Inbox-ul utilizatorului curent'],
            ['label' => 'Ready', 'value' => $byType['ready'], 'meta' => 'Copii gata pentru discutie de avansare'],
            ['label' => 'Attendance alerts', 'value' => $byType['attendance'], 'meta' => 'Absente peste pragul configurat'],
        ];
    }

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

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

        return $settings;
    }
}
