<?php

namespace App\Services;

use App\Models\CoachAvailabilityModel;
use App\Models\CoachModel;
use App\Models\RoleModel;
use App\Models\UserRoleModel;
use CodeIgniter\Shield\Entities\User;
use CodeIgniter\Shield\Models\UserModel;

class CoachesService
{
    private AuthorizationService $authorizationService;

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

        $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);
        $rows    = $this->getCoachRows($filters);
        $coachIds = array_map(static fn (array $row): int => (int) $row['id'], $rows);
        $stats   = $this->buildCoachStats($coachIds);

        foreach ($rows as &$row) {
            $coachId                   = (int) $row['id'];
            $row['active_children']    = $stats['children'][$coachId] ?? 0;
            $row['evaluations_count']  = $stats['evaluations'][$coachId] ?? 0;
            $row['avg_child_score']    = $stats['scores'][$coachId] ?? null;
            $row['avg_child_score_label'] = format_score($row['avg_child_score']);
            $row['availability_summary'] = $stats['availability'][$coachId] ?? 'Program neconfigurat';
        }
        unset($row);

        return [
            'pageTitle' => 'Traineri',
            'pageDescription' => 'Echipa academiei, capacitatea curenta si incarcarea operationala pe trainer.',
            'filters' => $filters,
            'coaches' => $rows,
            'canManage' => $filters['current_role'] === 'admin',
        ];
    }

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

        $values = [
            'full_name' => '',
            'display_role' => 'Trainer',
            'phone' => '',
            'email' => '',
            'avatar_path' => '',
            'is_active' => '1',
            'notes' => '',
            'username' => '',
            'password' => '',
            'password_confirmation' => '',
            'availability' => $this->defaultAvailability(),
        ];

        if ($coach !== null) {
            $values = [
                'full_name' => $coach['full_name'],
                'display_role' => $coach['display_role'],
                'phone' => $coach['phone'],
                'email' => $coach['email'],
                'avatar_path' => $coach['avatar_path'],
                'is_active' => (string) ((int) $coach['is_active']),
                'notes' => $coach['notes'],
                'username' => $coach['username'],
                'password' => '',
                'password_confirmation' => '',
                'availability' => $this->availabilityMap($coachId),
            ];
        }

        foreach ($input as $key => $value) {
            if ($key === 'availability' && is_array($value)) {
                $values['availability'] = $this->normalizeAvailabilityInput($value);
                continue;
            }

            if (array_key_exists($key, $values) && is_string($value)) {
                $values[$key] = trim($value);
            }
        }

        return [
            'pageTitle' => $coachId === null ? 'Trainer nou' : 'Editare trainer',
            'pageDescription' => $coachId === null
                ? 'Creeaza profilul de trainer, contul de acces si disponibilitatea saptamanala.'
                : 'Actualizeaza datele trainerului fara sa pierzi istoricul evaluarilor efectuate.',
            'coach' => $coach,
            'values' => $values,
            'errors' => $errors,
            'roleOptions' => ['Head Trainer', 'Trainer', 'Assistant Trainer'],
            'submitUrl' => $coachId === null ? url_to('coaches.create') : url_to('coaches.update', $coachId),
            'cancelUrl' => $coachId === null ? url_to('coaches.index') : url_to('coaches.show', $coachId),
            'canManage' => $this->authorizationService->userHasRole((int) $user->id, 'admin'),
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @return array{success: bool, coach_id?: int, errors?: array<string, string>}
     */
    public function save(array $input, User $user, ?int $coachId = null): array
    {
        $validation = service('validation');
        $validation->setRules(config('Validation')->coach);

        $payload = $this->normalizePayload($input);

        if (! $validation->run($payload)) {
            return ['success' => false, 'errors' => $validation->getErrors()];
        }

        $extraErrors = $this->validateCredentials($payload, $coachId);
        if ($extraErrors !== []) {
            return ['success' => false, 'errors' => $extraErrors];
        }

        $existing = $coachId !== null ? $this->getCoachWithUser($coachId) : null;
        $userModel = model(UserModel::class);
        $coachModel = model(CoachModel::class);
        $roleId = (int) (model(RoleModel::class)->where('slug', 'coach')->first()['id'] ?? 0);
        $now = date('Y-m-d H:i:s');

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

        if ($existing === null) {
            $account = new \CodeIgniter\Shield\Entities\User([
                'username' => $payload['username'],
                'email' => $payload['email'],
                'password' => $payload['password'],
                'active' => (int) $payload['is_active'],
            ]);

            $userModel->save($account);

            $createdUser = $userModel->findByCredentials(['email' => $payload['email']]);
            $createdUser?->addGroup('coach');

            if ($createdUser !== null && $roleId > 0) {
                model(UserRoleModel::class)->insert([
                    'user_id' => (int) $createdUser->id,
                    'role_id' => $roleId,
                    'created_at' => $now,
                ]);
            }

            $coachModel->insert([
                'user_id' => (int) $createdUser?->id,
                'full_name' => $payload['full_name'],
                'display_role' => $payload['display_role'],
                'phone' => $payload['phone'] !== '' ? $payload['phone'] : null,
                'email' => $payload['email'],
                'avatar_path' => $payload['avatar_path'] !== '' ? $payload['avatar_path'] : null,
                'is_active' => (int) $payload['is_active'],
                'notes' => $payload['notes'] !== '' ? $payload['notes'] : null,
                'created_at' => $now,
                'updated_at' => $now,
            ]);

            $coachId = (int) $coachModel->getInsertID();
        } else {
            $account = $userModel->withGroups()->find($existing['user_id']);
            if ($account !== null) {
                $account->username = $payload['username'];
                $account->email = $payload['email'];
                $account->active = (int) $payload['is_active'];
                if ($payload['password'] !== '') {
                    $account->password = $payload['password'];
                }
                $userModel->save($account);
            }

            $coachModel->update($coachId, [
                'full_name' => $payload['full_name'],
                'display_role' => $payload['display_role'],
                'phone' => $payload['phone'] !== '' ? $payload['phone'] : null,
                'email' => $payload['email'],
                'avatar_path' => $payload['avatar_path'] !== '' ? $payload['avatar_path'] : null,
                'is_active' => (int) $payload['is_active'],
                'notes' => $payload['notes'] !== '' ? $payload['notes'] : null,
                'updated_at' => $now,
            ]);
        }

        $this->syncAvailability((int) $coachId, $payload['availability'], $now);

        $db->transComplete();

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

        return ['success' => true, 'coach_id' => (int) $coachId];
    }

    /**
     * @return array<string, mixed>|null
     */
    public function getProfileData(int $coachId, User $user): ?array
    {
        $coach = $this->getCoachWithUser($coachId);
        if ($coach === null) {
            return null;
        }

        if ($this->authorizationService->userHasRole((int) $user->id, 'coach') && ! $this->authorizationService->userHasRole((int) $user->id, 'admin')) {
            $currentTrainerId = $this->authorizationService->getCoachIdForUser((int) $user->id);
            if ($currentTrainerId !== $coachId) {
                return null;
            }
        }

        $children = $this->db()
            ->table('children')
            ->select('id, full_name, status, birth_date')
            ->where('primary_coach_id', $coachId)
            ->orderBy('full_name', 'ASC')
            ->get()
            ->getResultArray();

        $recentEvaluations = $this->db()
            ->table('evaluations e')
            ->select('e.evaluation_date, e.final_score, e.final_status, 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')
            ->where('e.evaluator_coach_id', $coachId)
            ->orderBy('e.evaluation_date', 'DESC')
            ->orderBy('e.id', 'DESC')
            ->limit(8)
            ->get()
            ->getResultArray();

        $quickChecks = array_values(array_filter($recentEvaluations, static fn (array $row): bool => $row['type_slug'] === 'quick-check'));

        return [
            'pageTitle' => $coach['full_name'],
            'pageDescription' => 'Profil operational cu copii asignati, evaluari recente si disponibilitate.',
            'coach' => $coach,
            'children' => $children,
            'recentEvaluations' => $recentEvaluations,
            'recentQuickChecks' => $quickChecks,
            'availability' => $this->availabilityRows($coachId),
            'canManage' => $this->authorizationService->userHasRole((int) $user->id, 'admin'),
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    private function buildFilters(array $input, int $userId): array
    {
        $role = $this->authorizationService->getPrimaryRole($userId);
        $lockedTrainerId = $role === 'coach' ? $this->authorizationService->getCoachIdForUser($userId) : null;

        return [
            'current_role' => $role,
            'coach_locked' => $lockedTrainerId !== null,
            'selected_status' => (string) ($input['status'] ?? 'all'),
            'query' => trim((string) ($input['q'] ?? '')),
            'locked_coach_id' => $lockedTrainerId,
            'status_options' => [
                'all' => 'Toate statusurile',
                'active' => 'Activ',
                'inactive' => 'Inactiv',
            ],
        ];
    }

    /**
     * @param array<string, mixed> $filters
     * @return list<array<string, mixed>>
     */
    private function getCoachRows(array $filters): array
    {
        $builder = $this->db()
            ->table('coaches c')
            ->select('c.*, users.username')
            ->join('users', 'users.id = c.user_id', 'left')
            ->orderBy('c.full_name', 'ASC');

        if ($filters['locked_coach_id'] !== null) {
            $builder->where('c.id', $filters['locked_coach_id']);
        }

        if ($filters['selected_status'] === 'active') {
            $builder->where('c.is_active', 1);
        } elseif ($filters['selected_status'] === 'inactive') {
            $builder->where('c.is_active', 0);
        }

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

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

    /**
     * @param list<int> $coachIds
     * @return array<string, array<int, mixed>>
     */
    private function buildCoachStats(array $coachIds): array
    {
        if ($coachIds === []) {
            return ['children' => [], 'evaluations' => [], 'scores' => [], 'availability' => []];
        }

        $children = [];
        foreach ($this->db()->table('children')->select('primary_coach_id, COUNT(*) as total')->where('status', 'active')->whereIn('primary_coach_id', $coachIds)->groupBy('primary_coach_id')->get()->getResultArray() as $row) {
            $children[(int) $row['primary_coach_id']] = (int) $row['total'];
        }

        $evaluations = [];
        foreach ($this->db()->table('evaluations')->select('evaluator_coach_id, COUNT(*) as total')->whereIn('evaluator_coach_id', $coachIds)->groupBy('evaluator_coach_id')->get()->getResultArray() as $row) {
            $evaluations[(int) $row['evaluator_coach_id']] = (int) $row['total'];
        }

        $scores = [];
        foreach ($this->db()->table('children c')->select('c.primary_coach_id, ROUND(AVG(e.final_score), 2) as avg_score')->join('evaluations e', 'e.child_id = c.id', 'left')->whereIn('c.primary_coach_id', $coachIds)->where('c.status', 'active')->groupBy('c.primary_coach_id')->get()->getResultArray() as $row) {
            $scores[(int) $row['primary_coach_id']] = $row['avg_score'] !== null ? (float) $row['avg_score'] : null;
        }

        $availability = [];
        foreach ($this->availabilityRowsForMany($coachIds) as $coachId => $rows) {
            $availability[$coachId] = implode(' • ', array_map(
                static fn (array $row): string => day_of_week_label((int) $row['day_of_week']) . ' ' . substr((string) $row['start_time'], 0, 5) . '-' . substr((string) $row['end_time'], 0, 5),
                $rows,
            ));
        }

        return compact('children', 'evaluations', 'scores', 'availability');
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    private function normalizePayload(array $input): array
    {
        return [
            'full_name' => trim((string) ($input['full_name'] ?? '')),
            'display_role' => trim((string) ($input['display_role'] ?? 'Trainer')),
            'phone' => trim((string) ($input['phone'] ?? '')),
            'email' => trim((string) ($input['email'] ?? '')),
            'avatar_path' => trim((string) ($input['avatar_path'] ?? '')),
            'is_active' => (string) ((int) ($input['is_active'] ?? 1)),
            'notes' => trim((string) ($input['notes'] ?? '')),
            'username' => trim((string) ($input['username'] ?? '')),
            'password' => (string) ($input['password'] ?? ''),
            'password_confirmation' => (string) ($input['password_confirmation'] ?? ''),
            'availability' => $this->normalizeAvailabilityInput((array) ($input['availability'] ?? [])),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function validateCredentials(array $payload, ?int $coachId): array
    {
        $errors = [];
        $existing = $coachId !== null ? $this->getCoachWithUser($coachId) : null;
        $ignoreUserId = $existing['user_id'] ?? null;

        if ($coachId === null && trim($payload['password']) === '') {
            $errors['password'] = 'Parola este obligatorie pentru contul de trainer.';
        }

        if ($payload['password'] !== '' && $payload['password'] !== $payload['password_confirmation']) {
            $errors['password_confirmation'] = 'Confirmarea parolei nu corespunde.';
        }

        $usernameTaken = $this->db()
            ->table('users')
            ->select('id')
            ->where('LOWER(username)', strtolower($payload['username']))
            ->get()
            ->getRowArray();

        if ($usernameTaken !== null && (int) $usernameTaken['id'] !== (int) $ignoreUserId) {
            $errors['username'] = 'Username-ul este deja folosit.';
        }

        $emailTaken = $this->db()
            ->table('auth_identities')
            ->select('user_id')
            ->where('type', 'email_password')
            ->where('LOWER(secret)', strtolower($payload['email']))
            ->get()
            ->getRowArray();

        if ($emailTaken !== null && (int) $emailTaken['user_id'] !== (int) $ignoreUserId) {
            $errors['email'] = 'Exista deja un cont cu acest email.';
        }

        return $errors;
    }

    /**
     * @param array<string, mixed> $availability
     * @return array<int, array<string, string|bool>>
     */
    private function normalizeAvailabilityInput(array $availability): array
    {
        $normalized = $this->defaultAvailability();

        foreach ($availability as $day => $row) {
            $day = (int) $day;
            if (! isset($normalized[$day]) || ! is_array($row)) {
                continue;
            }

            $normalized[$day] = [
                'enabled' => isset($row['enabled']) && (string) $row['enabled'] === '1',
                'start' => trim((string) ($row['start'] ?? $normalized[$day]['start'])),
                'end' => trim((string) ($row['end'] ?? $normalized[$day]['end'])),
            ];
        }

        return $normalized;
    }

    /**
     * @return array<int, array<string, string|bool>>
     */
    private function defaultAvailability(): array
    {
        $days = [];

        for ($day = 1; $day <= 7; $day++) {
            $days[$day] = [
                'enabled' => false,
                'start' => '16:00',
                'end' => '19:00',
            ];
        }

        return $days;
    }

    /**
     * @return array<int, array<string, string|bool>>
     */
    private function availabilityMap(int $coachId): array
    {
        $map = $this->defaultAvailability();

        foreach ($this->availabilityRows($coachId) as $row) {
            $day = (int) $row['day_of_week'];
            $map[$day] = [
                'enabled' => true,
                'start' => substr((string) $row['start_time'], 0, 5),
                'end' => substr((string) $row['end_time'], 0, 5),
            ];
        }

        return $map;
    }

    private function syncAvailability(int $coachId, array $availability, string $now): void
    {
        $this->db()->table('coach_availability')->where('coach_id', $coachId)->delete();

        $rows = [];
        foreach ($availability as $day => $slot) {
            if (! ($slot['enabled'] ?? false)) {
                continue;
            }

            $rows[] = [
                'coach_id' => $coachId,
                'day_of_week' => (int) $day,
                'start_time' => ($slot['start'] ?? '16:00') . ':00',
                'end_time' => ($slot['end'] ?? '19:00') . ':00',
                'is_available' => 1,
                'created_at' => $now,
            ];
        }

        if ($rows !== []) {
            model(CoachAvailabilityModel::class)->insertBatch($rows);
        }
    }

    /**
     * @return array<string, mixed>|null
     */
    private function getCoachWithUser(int $coachId): ?array
    {
        return $this->db()
            ->table('coaches c')
            ->select('c.*, users.username')
            ->join('users', 'users.id = c.user_id', 'left')
            ->where('c.id', $coachId)
            ->get()
            ->getRowArray();
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function availabilityRows(int $coachId): array
    {
        return $this->db()
            ->table('coach_availability')
            ->where('coach_id', $coachId)
            ->orderBy('day_of_week', 'ASC')
            ->get()
            ->getResultArray();
    }

    /**
     * @param list<int> $coachIds
     * @return array<int, list<array<string, mixed>>>
     */
    private function availabilityRowsForMany(array $coachIds): array
    {
        $grouped = [];
        if ($coachIds === []) {
            return $grouped;
        }

        $rows = $this->db()
            ->table('coach_availability')
            ->whereIn('coach_id', $coachIds)
            ->orderBy('day_of_week', 'ASC')
            ->get()
            ->getResultArray();

        foreach ($rows as $row) {
            $grouped[(int) $row['coach_id']][] = $row;
        }

        return $grouped;
    }

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