<?php
namespace App\Services;

use App\Core\Database;
use PDO;

final class LoyaltyService
{
    private function safeRecalculateTier(PDO $pdo, int $userId): void
    {
        try { \App\Services\TierService::recalculateMember($pdo, $userId, 0); } catch (\Throwable) {}
    }

    private function settingFloat(PDO $pdo, string $key, float $default): float
    {
        try {
            // Always read the latest saved value. Some older databases briefly allowed
            // duplicate app_settings rows, and SELECT ... LIMIT 1 could pick an old zero.
            $stmt = $pdo->prepare('SELECT setting_value FROM app_settings WHERE setting_key = ? ORDER BY updated_at DESC, id DESC LIMIT 1');
            $stmt->execute([$key]);
            $value = $stmt->fetchColumn();
            if ($value === false || $value === null || trim((string)$value) === '') return $default;
            if (!is_numeric($value)) return $default;
            return (float)$value;
        } catch (\Throwable) {
            return $default;
        }
    }

    private function settingInt(PDO $pdo, string $key, int $default): int
    {
        return (int)round($this->settingFloat($pdo, $key, (float)$default));
    }

    private function formatNumber(float $value, int $decimals = 2): string
    {
        return rtrim(rtrim(number_format($value, $decimals, '.', ''), '0'), '.');
    }

    public function addWalkCompletion(int $userId, int $walkId, float $km): void
    {
        $pdo = Database::connection();
        $pointsPerKm = $this->settingFloat($pdo, 'loyalty_points_per_km', 10.0);
        $completionBonus = $this->settingInt($pdo, 'loyalty_walk_completion_bonus', 25);
        $points = (int)round($km * $pointsPerKm) + $completionBonus;

        $stmt = $pdo->prepare('UPDATE member_profiles SET total_walks = total_walks + 1, total_kilometers = total_kilometers + ?, loyalty_points = loyalty_points + ?, updated_at = NOW() WHERE user_id = ?');
        $stmt->execute([$km, $points, $userId]);

        $stmt = $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, ?, ?, "walk_completed", ?, NOW())');
        $stmt->execute([$userId, $walkId, $points, 'Completed walk: ' . number_format($km, 2) . ' KM × ' . $this->formatNumber($pointsPerKm) . ' pts/KM + ' . $completionBonus . ' bonus']);

        $this->evaluateBadges($userId, $walkId);
    }

    public function addPartialWalkCompletion(int $userId, int $walkId, float $km): void
    {
        $pdo = Database::connection();
        $km = max(0, $km);
        $pointsPerKm = $this->settingFloat($pdo, 'loyalty_points_per_km', 10.0);
        $points = (int)round($km * $pointsPerKm);

        $stmt = $pdo->prepare('UPDATE member_profiles SET total_kilometers = total_kilometers + ?, loyalty_points = loyalty_points + ?, updated_at = NOW() WHERE user_id = ?');
        $stmt->execute([$km, $points, $userId]);

        $stmt = $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, ?, ?, "walk_completed", ?, NOW())');
        $stmt->execute([$userId, $walkId, $points, 'Partially completed walk: ' . number_format($km, 2) . ' KM × ' . $this->formatNumber($pointsPerKm) . ' pts/KM']);

        $this->evaluateBadges($userId, $walkId);
    }

    public function awardPhotoWinner(int $userId, int $walkId): void
    {
        $pdo = Database::connection();
        $points = $this->settingInt($pdo, 'loyalty_photo_winner_points', 200);
        $stmt = $pdo->prepare('UPDATE member_profiles SET loyalty_points = loyalty_points + ?, updated_at = NOW() WHERE user_id = ?');
        $stmt->execute([$points, $userId]);
        $stmt = $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, ?, ?, "photo_winner", "Won the photo contest", NOW())');
        $stmt->execute([$userId, $walkId, $points]);
        $this->awardBadgeByName($userId, 'Photo Contest Winner', $walkId);
    }

    public function evaluateBadges(int $userId, ?int $walkId = null): void
    {
        $pdo = Database::connection();
        $profile = $pdo->prepare('SELECT total_walks, total_kilometers FROM member_profiles WHERE user_id = ?');
        $profile->execute([$userId]);
        $p = $profile->fetch(PDO::FETCH_ASSOC);
        if (!$p) return;

        $badges = $pdo->query('SELECT * FROM badges WHERE status = "active"')->fetchAll(PDO::FETCH_ASSOC);
        foreach ($badges as $badge) {
            $earned = false;
            if ($badge['rule_type'] === 'total_km' && (float)$p['total_kilometers'] >= (float)$badge['rule_value']) $earned = true;
            if ($badge['rule_type'] === 'total_walks' && (int)$p['total_walks'] >= (int)$badge['rule_value']) $earned = true;
            if ($earned) {
                $this->awardBadge((int)$userId, (int)$badge['id'], $walkId, (int)$badge['points_reward']);
            }
        }
    }


    private function badgeAutoGroupRule(PDO $pdo, int $badgeId): ?array
    {
        try {
            $stmt = $pdo->prepare('SELECT bagr.group_id, g.group_name
                FROM badge_auto_group_rules bagr
                JOIN member_groups g ON g.id = bagr.group_id
                WHERE bagr.badge_id = ? AND bagr.active = 1
                LIMIT 1');
            $stmt->execute([$badgeId]);
            $rule = $stmt->fetch(PDO::FETCH_ASSOC);
            return $rule ?: null;
        } catch (\Throwable) {
            return null;
        }
    }

    private function syncBadgeAutoGroup(PDO $pdo, int $userId, int $badgeId): void
    {
        $rule = $this->badgeAutoGroupRule($pdo, $badgeId);
        if (!$rule) return;

        try {
            $exists = $pdo->prepare('SELECT id FROM member_group_members WHERE group_id = ? AND user_id = ? LIMIT 1');
            $exists->execute([(int)$rule['group_id'], $userId]);
            if ($exists->fetch()) return;

            $stmt = $pdo->prepare('INSERT INTO member_group_members (group_id, user_id, added_by, created_at) VALUES (?, ?, NULL, NOW())');
            $stmt->execute([(int)$rule['group_id'], $userId]);
        } catch (\Throwable) {
            // Never fail badge earning because of a group sync problem.
        }
    }


    public function awardBadgeByName(int $userId, string $badgeName, ?int $walkId = null): void
    {
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT id, points_reward FROM badges WHERE badge_name = ? LIMIT 1');
        $stmt->execute([$badgeName]);
        $badge = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($badge) {
            $this->awardBadge($userId, (int)$badge['id'], $walkId, (int)$badge['points_reward']);
        }
    }

    private function awardBadge(int $userId, int $badgeId, ?int $walkId, int $points): void
    {
        $pdo = Database::connection();
        $exists = $pdo->prepare('SELECT id FROM user_badges WHERE user_id = ? AND badge_id = ? LIMIT 1');
        $exists->execute([$userId, $badgeId]);
        if ($exists->fetch()) return;

        $stmt = $pdo->prepare('INSERT INTO user_badges (user_id, badge_id, walk_id, earned_at, created_at) VALUES (?, ?, ?, NOW(), NOW())');
        $stmt->execute([$userId, $badgeId, $walkId]);

        $this->syncBadgeAutoGroup($pdo, $userId, $badgeId);

        if ($points > 0) {
            $pdo->prepare('UPDATE member_profiles SET loyalty_points = loyalty_points + ? WHERE user_id = ?')->execute([$points, $userId]);
            $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, ?, ?, "badge_earned", "Badge reward", NOW())')->execute([$userId, $walkId, $points]);
        }
    }
}
