<?php
declare(strict_types=1);

namespace App\Modules\Achievements\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\Achievements\Models\AchievementDefinition;
use App\Modules\Achievements\Models\PlayerAchievement;

final class AchievementEngine
{
    /**
     * Check all active achievement criteria against an event and award if met.
     */
    public static function checkAndAward(int $playerId, string $eventType, array $context = []): array
    {
        $awarded = [];
        $definitions = AchievementDefinition::allActive();

        foreach ($definitions as $def) {
            $achievementId = (int) $def['id'];

            // Skip if already earned
            if (PlayerAchievement::hasEarned($playerId, $achievementId)) {
                continue;
            }

            $criteria = [];
            if (!empty($def['criteria_json'])) {
                $criteria = json_decode($def['criteria_json'], true) ?: [];
            }

            // Check if this achievement's trigger matches the event
            $trigger = $criteria['trigger'] ?? '';
            if ($trigger !== '' && $trigger !== $eventType) {
                continue;
            }

            // Evaluate criteria
            if (self::evaluateCriteria($playerId, $criteria, $context)) {
                self::awardAchievement($playerId, $achievementId, $context);
                $awarded[] = $def;
            }
        }

        return $awarded;
    }

    /**
     * Award an achievement to a player.
     */
    public static function awardAchievement(int $playerId, int $achievementId, array $context = []): void
    {
        // Double-check not already awarded
        if (PlayerAchievement::hasEarned($playerId, $achievementId)) {
            return;
        }

        $definition = AchievementDefinition::find($achievementId);
        if (!$definition) {
            return;
        }

        $points = (int) ($definition->points ?? 0);

        PlayerAchievement::create([
            'player_id'      => $playerId,
            'achievement_id' => $achievementId,
            'earned_at'      => date('Y-m-d H:i:s'),
            'context_json'   => !empty($context) ? json_encode($context, JSON_UNESCAPED_UNICODE) : null,
            'points_earned'  => $points,
            'notified'       => 0,
        ]);

        EventBus::dispatch('player.achievement_earned', [
            'player_id'        => $playerId,
            'achievement_id'   => $achievementId,
            'achievement_code' => $definition->code,
            'achievement_name' => $definition->name_ar,
            'points'           => $points,
        ]);

        Logger::info("Achievement '{$definition->code}' awarded to player #{$playerId} (+{$points} pts)");
    }

    /**
     * Get leaderboard of top players by points.
     */
    public static function getLeaderboard(?int $disciplineId = null, int $limit = 50): array
    {
        return PlayerAchievement::getLeaderboard($disciplineId, $limit);
    }

    /**
     * Check attendance-based achievements for a player.
     */
    public static function checkAttendanceAchievements(int $playerId): array
    {
        $db = App::getInstance()->db();

        // Get attendance stats
        $totalAttendance = $db->selectOne(
            "SELECT COUNT(*) as cnt FROM session_attendance WHERE player_id = ? AND status = 'present'",
            [$playerId]
        );
        $total = (int) ($totalAttendance['cnt'] ?? 0);

        // Check consecutive attendance streak
        $streak = self::calculateAttendanceStreak($playerId);

        $context = [
            'total_attendance' => $total,
            'streak'           => $streak,
        ];

        return self::checkAndAward($playerId, 'attendance_check', $context);
    }

    /**
     * Check milestone achievements (time-based, membership duration, etc.).
     */
    public static function checkMilestoneAchievements(int $playerId): array
    {
        $db = App::getInstance()->db();

        $player = $db->selectOne(
            "SELECT created_at, date_of_birth FROM players WHERE id = ?",
            [$playerId]
        );

        if (!$player) {
            return [];
        }

        // Calculate membership duration in months
        $registeredAt = new \DateTime($player['created_at']);
        $now = new \DateTime();
        $diff = $registeredAt->diff($now);
        $monthsMember = ($diff->y * 12) + $diff->m;

        $context = [
            'months_member'   => $monthsMember,
            'registered_at'   => $player['created_at'],
        ];

        return self::checkAndAward($playerId, 'milestone_check', $context);
    }

    /**
     * Evaluate achievement criteria against player data.
     */
    private static function evaluateCriteria(int $playerId, array $criteria, array $context): bool
    {
        if (empty($criteria)) {
            return false;
        }

        $type = $criteria['type'] ?? '';

        switch ($type) {
            case 'attendance_total':
                $required = (int) ($criteria['count'] ?? 0);
                $actual = (int) ($context['total_attendance'] ?? 0);
                if ($actual <= 0) {
                    $db = App::getInstance()->db();
                    $row = $db->selectOne(
                        "SELECT COUNT(*) as cnt FROM session_attendance WHERE player_id = ? AND status = 'present'",
                        [$playerId]
                    );
                    $actual = (int) ($row['cnt'] ?? 0);
                }
                return $actual >= $required;

            case 'attendance_streak':
                $required = (int) ($criteria['count'] ?? 0);
                $streak = (int) ($context['streak'] ?? self::calculateAttendanceStreak($playerId));
                return $streak >= $required;

            case 'membership_months':
                $required = (int) ($criteria['months'] ?? 0);
                $actual = (int) ($context['months_member'] ?? 0);
                return $actual >= $required;

            case 'fitness_score':
                $required = (float) ($criteria['min_score'] ?? 0);
                $db = App::getInstance()->db();
                $latest = $db->selectOne(
                    "SELECT overall_fitness_score FROM player_fitness_tests WHERE player_id = ? ORDER BY test_date DESC LIMIT 1",
                    [$playerId]
                );
                $score = (float) ($latest['overall_fitness_score'] ?? 0);
                return $score >= $required;

            case 'evaluation_score':
                $required = (float) ($criteria['min_score'] ?? 0);
                $db = App::getInstance()->db();
                $latest = $db->selectOne(
                    "SELECT overall_score FROM player_evaluations WHERE player_id = ? AND status = 'approved' ORDER BY evaluation_date DESC LIMIT 1",
                    [$playerId]
                );
                $score = (float) ($latest['overall_score'] ?? 0);
                return $score >= $required;

            case 'tournament_wins':
                $required = (int) ($criteria['count'] ?? 0);
                $actual = (int) ($context['total_wins'] ?? 0);
                return $actual >= $required;

            case 'custom':
                // Custom criteria evaluated from context
                $field = $criteria['field'] ?? '';
                $operator = $criteria['operator'] ?? '>=';
                $value = $criteria['value'] ?? 0;
                $actual = $context[$field] ?? 0;
                return self::compareValues($actual, $operator, $value);

            default:
                return false;
        }
    }

    /**
     * Calculate the current consecutive attendance streak for a player.
     */
    private static function calculateAttendanceStreak(int $playerId): int
    {
        $db = App::getInstance()->db();

        $records = $db->select(
            "SELECT attendance_date, status
             FROM session_attendance
             WHERE player_id = ?
             ORDER BY attendance_date DESC
             LIMIT 100",
            [$playerId]
        );

        $streak = 0;
        foreach ($records as $record) {
            if ($record['status'] === 'present') {
                $streak++;
            } else {
                break;
            }
        }

        return $streak;
    }

    /**
     * Compare two values with an operator.
     */
    private static function compareValues($actual, string $operator, $expected): bool
    {
        $actual = is_numeric($actual) ? (float) $actual : $actual;
        $expected = is_numeric($expected) ? (float) $expected : $expected;

        return match ($operator) {
            '>='   => $actual >= $expected,
            '>'    => $actual > $expected,
            '<='   => $actual <= $expected,
            '<'    => $actual < $expected,
            '=='   => $actual == $expected,
            '!='   => $actual != $expected,
            default => false,
        };
    }
}
