<?php
require __DIR__ . '/bootstrap.php';

use App\Core\Auth;
use App\Core\Csrf;
use App\Core\Database;
use App\Core\Response;
use App\Core\Security;

use App\Services\NotificationService;
use App\Services\TierService;
$user = Auth::requireRole(['super_admin','event_manager','walk_leader']);
$pdo = Database::connection();
$input = request_input();
$method = $_SERVER['REQUEST_METHOD'];
$action = $input['action'] ?? 'overview';

function pyrk_loyalty_can_manage(array $user): void
{
    if (!in_array($user['role_name'] ?? '', ['super_admin','event_manager'], true)) {
        Response::error('You do not have permission to manage loyalty.', 403);
    }
}

function pyrk_loyalty_table_exists(PDO $pdo, string $table): bool
{
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?');
        $stmt->execute([$table]);
        return (int)$stmt->fetchColumn() > 0;
    } catch (Throwable $e) {
        return false;
    }
}

function pyrk_loyalty_member_exists(PDO $pdo, int $memberId): bool
{
    $stmt = $pdo->prepare('SELECT u.id FROM users u JOIN roles r ON r.id = u.role_id WHERE u.id = ? AND r.name = "member" LIMIT 1');
    $stmt->execute([$memberId]);
    return (bool)$stmt->fetchColumn();
}

function pyrk_loyalty_setting_defaults(): array
{
    return [
        'loyalty_points_per_km' => '10',
        'loyalty_walk_completion_bonus' => '25',
        'loyalty_photo_winner_points' => '200',
    ];
}


function pyrk_loyalty_safe_recalculate_tier(PDO $pdo, int $memberId, int $adminId = 0): void
{
    try {
        if (class_exists(\App\Services\TierService::class)) {
            \App\Services\TierService::recalculateMember($pdo, $memberId, $adminId);
        }
    } catch (Throwable) {
        // Tier recalculation must never break points or badges.
    }
}

function pyrk_loyalty_settings(PDO $pdo): array
{
    $defaults = pyrk_loyalty_setting_defaults();
    $settings = $defaults;
    try {
        $keys = array_keys($defaults);
        $placeholders = implode(',', array_fill(0, count($keys), '?'));
        $stmt = $pdo->prepare("SELECT setting_key, setting_value FROM app_settings WHERE setting_key IN ({$placeholders}) ORDER BY updated_at ASC, id ASC");
        $stmt->execute($keys);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $settings[(string)$row['setting_key']] = (string)$row['setting_value'];
        }
    } catch (Throwable) {
        // If app_settings does not exist yet, return safe defaults until the migration is imported.
    }

    return [
        'points_per_km' => (float)$settings['loyalty_points_per_km'],
        'walk_completion_bonus' => (int)round((float)$settings['loyalty_walk_completion_bonus']),
        'photo_winner_points' => (int)round((float)$settings['loyalty_photo_winner_points']),
    ];
}

function pyrk_loyalty_save_setting(PDO $pdo, string $key, string $value, int $userId): void
{
    $stmt = $pdo->prepare('INSERT INTO app_settings (setting_key, setting_value, updated_by, created_at, updated_at) VALUES (?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_by = VALUES(updated_by), updated_at = NOW()');
    $stmt->execute([$key, $value, $userId]);
}

function pyrk_loyalty_input_number(array $input, string $key, float $fallback): float
{
    if (!array_key_exists($key, $input)) return $fallback;
    $raw = trim((string)$input[$key]);
    if ($raw === '') return $fallback;
    if (!is_numeric($raw)) Response::error('Invalid value for ' . str_replace('_', ' ', $key) . '.');
    return (float)$raw;
}

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

function pyrk_loyalty_repair_walk_completion_points(PDO $pdo, int $adminId): array
{
    $settings = pyrk_loyalty_settings($pdo);
    $pointsPerKm = max(0.0, (float)($settings['points_per_km'] ?? 10));
    $completionBonus = max(0, (int)($settings['walk_completion_bonus'] ?? 25));

    if (!pyrk_loyalty_table_exists($pdo, 'loyalty_transactions')) {
        Response::error('Loyalty transactions table is not installed.', 500);
    }

    $sql = 'SELECT lt.id AS transaction_id, lt.user_id, lt.walk_id, lt.points AS old_points, lt.description,
                   a.status AS attendance_status, COALESCE(a.verified_km, 0) AS verified_km,
                   COALESCE(r.total_km, 0) AS route_km, w.title AS walk_title
            FROM loyalty_transactions lt
            JOIN attendance a ON a.walk_id = lt.walk_id AND a.user_id = lt.user_id
            JOIN walks w ON w.id = lt.walk_id
            LEFT JOIN routes r ON r.id = w.route_id
            WHERE lt.transaction_type = "walk_completed"
              AND lt.walk_id IS NOT NULL
              AND a.finish_time IS NOT NULL
            ORDER BY lt.id ASC';
    $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);

    $checked = 0;
    $fixed = 0;
    $totalDelta = 0;
    $membersTouched = [];
    $preview = [];

    foreach ($rows as $row) {
        $checked++;
        $km = (float)$row['verified_km'] > 0 ? (float)$row['verified_km'] : (float)$row['route_km'];
        $km = round(max(0.0, $km), 2);

        $description = (string)($row['description'] ?? '');
        $isPartial = (string)($row['attendance_status'] ?? '') === 'partially_completed'
            || stripos($description, 'partially completed') !== false;

        $expectedPoints = (int)round($km * $pointsPerKm) + ($isPartial ? 0 : $completionBonus);
        $newDescription = $isPartial
            ? ('Partially completed walk: ' . number_format($km, 2) . ' KM × ' . pyrk_loyalty_fmt($pointsPerKm) . ' pts/KM')
            : ('Completed walk: ' . number_format($km, 2) . ' KM × ' . pyrk_loyalty_fmt($pointsPerKm) . ' pts/KM + ' . $completionBonus . ' bonus');

        $oldPoints = (int)$row['old_points'];
        $delta = $expectedPoints - $oldPoints;
        if ($delta === 0 && $description === $newDescription) continue;

        $pdo->prepare('UPDATE loyalty_transactions SET points = ?, description = ? WHERE id = ?')
            ->execute([$expectedPoints, $newDescription, (int)$row['transaction_id']]);

        if ($delta !== 0) {
            $pdo->prepare('INSERT INTO member_profiles (user_id, loyalty_points, created_at, updated_at)
                           VALUES (?, ?, NOW(), NOW())
                           ON DUPLICATE KEY UPDATE loyalty_points = GREATEST(0, COALESCE(loyalty_points,0) + VALUES(loyalty_points)), updated_at = NOW()')
                ->execute([(int)$row['user_id'], $delta]);
            $totalDelta += $delta;
            $membersTouched[(int)$row['user_id']] = true;
        }

        $fixed++;
        if (count($preview) < 12) {
            $preview[] = [
                'transaction_id' => (int)$row['transaction_id'],
                'member_id' => (int)$row['user_id'],
                'walk_id' => (int)$row['walk_id'],
                'old_points' => $oldPoints,
                'new_points' => $expectedPoints,
                'delta' => $delta,
                'km' => $km,
            ];
        }
    }

    foreach (array_keys($membersTouched) as $memberId) {
        pyrk_loyalty_safe_recalculate_tier($pdo, (int)$memberId, $adminId);
    }

    return [
        'checked' => $checked,
        'fixed' => $fixed,
        'members_touched' => count($membersTouched),
        'points_delta' => $totalDelta,
        'points_per_km' => $pointsPerKm,
        'walk_completion_bonus' => $completionBonus,
        'preview' => $preview,
    ];
}


function pyrk_loyalty_badge_input(array $input): array
{
    $name = trim((string)($input['badge_name'] ?? ''));
    if ($name === '') Response::error('Badge name is required.');

    $ruleType = (string)($input['rule_type'] ?? 'manual');
    $allowed = ['total_km','total_walks','manual','photo_winner'];
    if (!in_array($ruleType, $allowed, true)) Response::error('Invalid badge rule type.');

    $status = (string)($input['status'] ?? 'active');
    if (!in_array($status, ['active','inactive'], true)) $status = 'active';

    return [
        'badge_name' => $name,
        'description' => trim((string)($input['description'] ?? '')),
        'badge_icon' => trim((string)($input['badge_icon'] ?? '🏅')) ?: '🏅',
        'rule_type' => $ruleType,
        'rule_value' => (float)($input['rule_value'] ?? 0),
        'points_reward' => (int)($input['points_reward'] ?? 0),
        'status' => $status,
    ];
}

function pyrk_loyalty_award_badge(PDO $pdo, int $memberId, int $badgeId, ?int $walkId = null): bool
{
    $stmt = $pdo->prepare('SELECT * FROM badges WHERE id = ? LIMIT 1');
    $stmt->execute([$badgeId]);
    $badge = $stmt->fetch();
    if (!$badge) Response::error('Badge not found.', 404);

    $exists = $pdo->prepare('SELECT id FROM user_badges WHERE user_id = ? AND badge_id = ? LIMIT 1');
    $exists->execute([$memberId, $badgeId]);
    if ($exists->fetch()) return false;

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

    pyrk_loyalty_sync_badge_group($pdo, $memberId, $badgeId, (int)(Auth::user()['id'] ?? 0));

    $points = (int)($badge['points_reward'] ?? 0);
    if ($points !== 0) {
        $pdo->prepare('UPDATE member_profiles SET loyalty_points = GREATEST(0, COALESCE(loyalty_points,0) + ?), updated_at = NOW() WHERE user_id = ?')->execute([$points, $memberId]);
        $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, ?, ?, "badge_earned", ?, NOW())')->execute([$memberId, $walkId, $points, 'Badge earned: ' . $badge['badge_name']]);
    }
    pyrk_loyalty_safe_recalculate_tier($pdo, $memberId, (int)(Auth::user()['id'] ?? 0));
    return true;
}

function pyrk_loyalty_evaluate_badges(PDO $pdo, ?int $memberId = null): array
{
    $params = [];
    $sql = 'SELECT u.id, u.full_name, COALESCE(mp.total_kilometers,0) AS total_kilometers, COALESCE(mp.total_walks,0) AS total_walks
            FROM users u
            JOIN roles r ON r.id = u.role_id
            LEFT JOIN member_profiles mp ON mp.user_id = u.id
            WHERE r.name = "member"';
    if ($memberId) {
        $sql .= ' AND u.id = ?';
        $params[] = $memberId;
    }
    $membersStmt = $pdo->prepare($sql);
    $membersStmt->execute($params);
    $members = $membersStmt->fetchAll();

    $badges = $pdo->query('SELECT * FROM badges WHERE status = "active" AND rule_type IN ("total_km","total_walks") ORDER BY rule_value ASC')->fetchAll();
    $awarded = [];
    foreach ($members as $member) {
        foreach ($badges as $badge) {
            $ok = false;
            if ($badge['rule_type'] === 'total_km') $ok = (float)$member['total_kilometers'] >= (float)$badge['rule_value'];
            if ($badge['rule_type'] === 'total_walks') $ok = (int)$member['total_walks'] >= (int)$badge['rule_value'];
            if ($ok && pyrk_loyalty_award_badge($pdo, (int)$member['id'], (int)$badge['id'])) {
                $awarded[] = ['member_id' => (int)$member['id'], 'member_name' => $member['full_name'], 'badge_name' => $badge['badge_name']];
            }
        }
    }
    return $awarded;
}

function pyrk_loyalty_badge_awards(PDO $pdo, int $memberId = 0): array
{
    $sql = 'SELECT ub.id AS award_id, ub.user_id, u.full_name, u.email, u.mobile, ub.badge_id, b.badge_name, b.badge_icon, b.description, b.points_reward, b.rule_type, ub.walk_id, w.title AS walk_title, ub.earned_at
            FROM user_badges ub
            JOIN users u ON u.id = ub.user_id
            JOIN badges b ON b.id = ub.badge_id
            LEFT JOIN walks w ON w.id = ub.walk_id';
    $params = [];
    if ($memberId > 0) {
        $sql .= ' WHERE ub.user_id = ?';
        $params[] = $memberId;
    }
    $sql .= ' ORDER BY ub.earned_at DESC, ub.id DESC LIMIT 300';
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

function pyrk_loyalty_cancel_badge_award(PDO $pdo, int $awardId, int $adminId): array
{
    $stmt = $pdo->prepare('SELECT ub.*, u.full_name, b.badge_name, b.points_reward FROM user_badges ub JOIN users u ON u.id = ub.user_id JOIN badges b ON b.id = ub.badge_id WHERE ub.id = ? LIMIT 1 FOR UPDATE');
    $stmt->execute([$awardId]);
    $award = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$award) Response::error('Awarded badge not found.', 404);

    $points = (int)($award['points_reward'] ?? 0);
    $memberId = (int)$award['user_id'];
    $badgeName = (string)($award['badge_name'] ?? 'Badge');

    $pdo->prepare('DELETE FROM user_badges WHERE id = ?')->execute([$awardId]);

    if ($points > 0) {
        $pdo->prepare('UPDATE member_profiles SET loyalty_points = GREATEST(0, COALESCE(loyalty_points,0) - ?), updated_at = NOW() WHERE user_id = ?')->execute([$points, $memberId]);
        $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, ?, ?, "adjustment", ?, NOW())')
            ->execute([$memberId, $award['walk_id'] ?: null, -$points, 'Badge cancelled: ' . $badgeName]);
    }

    Security::audit($adminId, 'badge_award_cancelled', 'user_badges', $awardId, [
        'member_id' => $memberId,
        'member_name' => $award['full_name'] ?? null,
        'badge_name' => $badgeName,
        'points_removed' => $points,
    ], $award);

    return ['member_id' => $memberId, 'badge_name' => $badgeName, 'points_removed' => $points];
}



function pyrk_loyalty_badge_group_table_ready(PDO $pdo): bool
{
    return pyrk_loyalty_table_exists($pdo, 'badge_auto_group_rules')
        && pyrk_loyalty_table_exists($pdo, 'member_group_members')
        && pyrk_loyalty_table_exists($pdo, 'member_groups');
}

function pyrk_loyalty_group_options(PDO $pdo): array
{
    if (!pyrk_loyalty_table_exists($pdo, 'member_groups')) return [];
    try {
        $stmt = $pdo->query('SELECT g.id, g.group_name, g.description, g.status,
            (SELECT COUNT(*) FROM member_group_members mgm WHERE mgm.group_id = g.id) AS member_count
            FROM member_groups g
            ORDER BY g.status = "active" DESC, g.group_name ASC');
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable) {
        return [];
    }
}

function pyrk_loyalty_badge_group_rule_input(array $input): array
{
    $enabled = !empty($input['auto_group_enabled']) ? 1 : 0;
    $groupId = (int)($input['auto_group_id'] ?? 0);

    if ($enabled && $groupId <= 0) {
        Response::error('Choose the group that members should enter when they earn this badge.');
    }

    return [
        'auto_group_enabled' => $enabled,
        'auto_group_id' => $enabled ? $groupId : 0,
    ];
}

function pyrk_loyalty_badge_group_rule_save(PDO $pdo, int $badgeId, array $groupRule): void
{
    if (!pyrk_loyalty_badge_group_table_ready($pdo)) return;

    $pdo->prepare('DELETE FROM badge_auto_group_rules WHERE badge_id = ?')->execute([$badgeId]);

    if (!empty($groupRule['auto_group_enabled']) && (int)$groupRule['auto_group_id'] > 0) {
        $stmt = $pdo->prepare('INSERT INTO badge_auto_group_rules (badge_id, group_id, active, created_at, updated_at) VALUES (?, ?, 1, NOW(), NOW())');
        $stmt->execute([$badgeId, (int)$groupRule['auto_group_id']]);
    }
}

function pyrk_loyalty_badge_group_rule_for_badge(PDO $pdo, int $badgeId): ?array
{
    if (!pyrk_loyalty_badge_group_table_ready($pdo) || $badgeId <= 0) return null;

    $stmt = $pdo->prepare('SELECT bagr.*, 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;
}

function pyrk_loyalty_sync_badge_group(PDO $pdo, int $memberId, int $badgeId, int $adminId = 0): ?array
{
    $rule = pyrk_loyalty_badge_group_rule_for_badge($pdo, $badgeId);
    if (!$rule) return null;

    $groupId = (int)$rule['group_id'];

    $exists = $pdo->prepare('SELECT id FROM member_group_members WHERE group_id = ? AND user_id = ? LIMIT 1');
    $exists->execute([$groupId, $memberId]);
    if ($exists->fetch()) {
        return [
            'group_id' => $groupId,
            'group_name' => $rule['group_name'] ?? '',
            'added' => false,
        ];
    }

    $stmt = $pdo->prepare('INSERT INTO member_group_members (group_id, user_id, added_by, created_at) VALUES (?, ?, ?, NOW())');
    $stmt->execute([$groupId, $memberId, $adminId ?: null]);

    if (class_exists(Security::class)) {
        Security::audit($adminId ?: null, 'badge_auto_group_member_added', 'member_groups', $groupId, [
            'member_id' => $memberId,
            'badge_id' => $badgeId,
            'group_name' => $rule['group_name'] ?? null,
        ]);
    }

    return [
        'group_id' => $groupId,
        'group_name' => $rule['group_name'] ?? '',
        'added' => true,
    ];
}

function pyrk_loyalty_sync_existing_badge_group_members(PDO $pdo, int $badgeId, int $adminId = 0): array
{
    $rule = pyrk_loyalty_badge_group_rule_for_badge($pdo, $badgeId);
    if (!$rule) return ['added' => 0, 'group_id' => 0, 'group_name' => ''];

    $stmt = $pdo->prepare('SELECT user_id FROM user_badges WHERE badge_id = ?');
    $stmt->execute([$badgeId]);

    $added = 0;
    foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $memberId) {
        $result = pyrk_loyalty_sync_badge_group($pdo, (int)$memberId, $badgeId, $adminId);
        if ($result && !empty($result['added'])) $added++;
    }

    return [
        'added' => $added,
        'group_id' => (int)$rule['group_id'],
        'group_name' => (string)($rule['group_name'] ?? ''),
    ];
}

function pyrk_loyalty_badges_with_group_rules(PDO $pdo): array
{
    if (!pyrk_loyalty_badge_group_table_ready($pdo)) {
        return $pdo->query('SELECT *, 0 AS auto_group_enabled, NULL AS auto_group_id, NULL AS auto_group_name FROM badges ORDER BY status ASC, rule_type ASC, rule_value ASC, badge_name ASC')->fetchAll();
    }

    $sql = 'SELECT b.*,
        CASE WHEN bagr.id IS NULL OR bagr.active <> 1 THEN 0 ELSE 1 END AS auto_group_enabled,
        bagr.group_id AS auto_group_id,
        g.group_name AS auto_group_name,
        (SELECT COUNT(*) FROM user_badges ub WHERE ub.badge_id = b.id) AS earned_count
        FROM badges b
        LEFT JOIN badge_auto_group_rules bagr ON bagr.badge_id = b.id AND bagr.active = 1
        LEFT JOIN member_groups g ON g.id = bagr.group_id
        ORDER BY b.status ASC, b.rule_type ASC, b.rule_value ASC, b.badge_name ASC';
    return $pdo->query($sql)->fetchAll();
}


if ($method === 'GET' && $action === 'overview') {
    $memberId = (int)($input['member_id'] ?? 0);

    $members = $pdo->query('SELECT u.id, u.full_name, u.email, u.mobile, u.profile_photo_path, COALESCE(mp.total_walks,0) AS total_walks, COALESCE(mp.total_kilometers,0) AS total_kilometers, COALESCE(mp.loyalty_points,0) AS loyalty_points, (SELECT COUNT(*) FROM user_badges ub WHERE ub.user_id = u.id) AS badges_count FROM users u JOIN roles r ON r.id = u.role_id LEFT JOIN member_profiles mp ON mp.user_id = u.id WHERE r.name = "member" ORDER BY u.full_name ASC')->fetchAll();
    $badges = pyrk_loyalty_badges_with_group_rules($pdo);
    $groups = pyrk_loyalty_group_options($pdo);

    $transactions = [];
    if (pyrk_loyalty_table_exists($pdo, 'loyalty_transactions')) {
        $transactionsSql = 'SELECT lt.id, lt.user_id, u.full_name, lt.walk_id, w.title AS walk_title, lt.points, lt.transaction_type, lt.description, lt.created_at FROM loyalty_transactions lt JOIN users u ON u.id = lt.user_id LEFT JOIN walks w ON w.id = lt.walk_id';
        $params = [];
        if ($memberId) {
            $transactionsSql .= ' WHERE lt.user_id = ?';
            $params[] = $memberId;
        }
        $transactionsSql .= ' ORDER BY lt.created_at DESC, lt.id DESC LIMIT 200';
        $transactionsStmt = $pdo->prepare($transactionsSql);
        $transactionsStmt->execute($params);
        $transactions = $transactionsStmt->fetchAll();
    }

    $selectedMember = null;
    if ($memberId) {
        $stmt = $pdo->prepare('SELECT u.id, u.full_name, u.email, u.mobile, u.profile_photo_path, COALESCE(mp.total_walks,0) AS total_walks, COALESCE(mp.total_kilometers,0) AS total_kilometers, COALESCE(mp.loyalty_points,0) AS loyalty_points, (SELECT COUNT(*) FROM user_badges ub WHERE ub.user_id = u.id) AS badges_count FROM users u JOIN roles r ON r.id = u.role_id LEFT JOIN member_profiles mp ON mp.user_id = u.id WHERE r.name = "member" AND u.id = ? LIMIT 1');
        $stmt->execute([$memberId]);
        $selectedMember = $stmt->fetch() ?: null;
    }

    Response::ok(['members' => $members, 'badges' => $badges, 'groups' => $groups ?? [], 'badge_group_rules_ready' => pyrk_loyalty_badge_group_table_ready($pdo), 'badge_awards' => pyrk_loyalty_badge_awards($pdo, $memberId), 'transactions' => $transactions, 'selected_member' => $selectedMember, 'settings' => pyrk_loyalty_settings($pdo)]);
}


if ($method === 'POST' && $action === 'save_settings') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();

    $currentSettings = pyrk_loyalty_settings($pdo);
    $pointsPerKm = pyrk_loyalty_input_number($input, 'points_per_km', (float)$currentSettings['points_per_km']);
    $completionBonus = (int)round(pyrk_loyalty_input_number($input, 'walk_completion_bonus', (float)$currentSettings['walk_completion_bonus']));
    $photoWinnerPoints = (int)round(pyrk_loyalty_input_number($input, 'photo_winner_points', (float)$currentSettings['photo_winner_points']));

    if ($pointsPerKm < 0) Response::error('Points per KM cannot be negative.');
    if ($completionBonus < 0) Response::error('Walk completion bonus cannot be negative.');
    if ($photoWinnerPoints < 0) Response::error('Photo winner points cannot be negative.');
    if ($pointsPerKm > 10000 || $completionBonus > 1000000 || $photoWinnerPoints > 1000000) Response::error('Point settings are too high.');

    pyrk_loyalty_save_setting($pdo, 'loyalty_points_per_km', rtrim(rtrim(number_format($pointsPerKm, 2, '.', ''), '0'), '.'), (int)$user['id']);
    pyrk_loyalty_save_setting($pdo, 'loyalty_walk_completion_bonus', (string)$completionBonus, (int)$user['id']);
    pyrk_loyalty_save_setting($pdo, 'loyalty_photo_winner_points', (string)$photoWinnerPoints, (int)$user['id']);

    Security::audit((int)$user['id'], 'loyalty_settings_updated', 'app_settings', null, [
        'points_per_km' => $pointsPerKm,
        'walk_completion_bonus' => $completionBonus,
        'photo_winner_points' => $photoWinnerPoints,
    ]);

    Response::ok(['settings' => pyrk_loyalty_settings($pdo)], 'Loyalty settings saved.');
}

if ($method === 'POST' && $action === 'save_badge') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $data = pyrk_loyalty_badge_input($input);
    $groupRule = pyrk_loyalty_badge_group_rule_input($input);
    $id = (int)($input['id'] ?? 0);
    try {
        if ($id) {
            $stmt = $pdo->prepare('UPDATE badges SET badge_name = ?, description = ?, badge_icon = ?, rule_type = ?, rule_value = ?, points_reward = ?, status = ?, updated_at = NOW() WHERE id = ?');
            $stmt->execute([$data['badge_name'], $data['description'], $data['badge_icon'], $data['rule_type'], $data['rule_value'], $data['points_reward'], $data['status'], $id]);
            pyrk_loyalty_badge_group_rule_save($pdo, $id, $groupRule);
            $synced = pyrk_loyalty_sync_existing_badge_group_members($pdo, $id, (int)$user['id']);
            Security::audit((int)$user['id'], 'badge_updated', 'badges', $id, array_merge($data, ['auto_group' => $groupRule]));
            Response::ok(['badge_id' => $id, 'auto_group_sync' => $synced], 'Badge updated successfully.');
        }
        $stmt = $pdo->prepare('INSERT INTO badges (badge_name, description, badge_icon, rule_type, rule_value, points_reward, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())');
        $stmt->execute([$data['badge_name'], $data['description'], $data['badge_icon'], $data['rule_type'], $data['rule_value'], $data['points_reward'], $data['status']]);
        $id = (int)$pdo->lastInsertId();
        pyrk_loyalty_badge_group_rule_save($pdo, $id, $groupRule);
        Security::audit((int)$user['id'], 'badge_created', 'badges', $id, array_merge($data, ['auto_group' => $groupRule]));
        Response::ok(['badge_id' => $id], 'Badge created successfully.');
    } catch (Throwable $e) {
        if (strpos($e->getMessage(), 'Duplicate') !== false) Response::error('A badge with this name already exists.');
        Response::error('Could not save badge: ' . $e->getMessage(), 500);
    }
}


if ($method === 'POST' && $action === 'sync_badge_group') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $badgeId = (int)($input['badge_id'] ?? $input['id'] ?? 0);
    if ($badgeId <= 0) Response::error('Badge is required.');

    try {
        $result = pyrk_loyalty_sync_existing_badge_group_members($pdo, $badgeId, (int)$user['id']);
        Response::ok($result, $result['group_id'] ? ('Synced ' . (int)$result['added'] . ' existing badge member(s) into ' . $result['group_name'] . '.') : 'This badge is not linked to a group.');
    } catch (Throwable $e) {
        Response::error('Could not sync badge group: ' . $e->getMessage(), 500);
    }
}


if ($method === 'POST' && $action === 'award_badge') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $memberId = (int)($input['member_id'] ?? 0);
    $badgeId = (int)($input['badge_id'] ?? 0);
    if (!$memberId || !$badgeId) Response::error('Member and badge are required.');
    if (!pyrk_loyalty_member_exists($pdo, $memberId)) Response::error('Member not found.', 404);
    $pdo->beginTransaction();
    try {
        $awarded = pyrk_loyalty_award_badge($pdo, $memberId, $badgeId);
        $pdo->commit();
        Security::audit((int)$user['id'], 'badge_awarded', 'users', $memberId, ['badge_id' => $badgeId]);
        Response::ok(['awarded' => $awarded], $awarded ? 'Badge awarded.' : 'Member already has this badge.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not award badge: ' . $e->getMessage(), 500);
    }
}

if ($method === 'POST' && $action === 'adjust_points') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $memberId = (int)($input['member_id'] ?? 0);
    $points = (int)($input['points'] ?? 0);
    $description = trim((string)($input['description'] ?? 'Manual loyalty adjustment'));
    if (!$memberId) Response::error('Member is required.');
    if ($points === 0) Response::error('Points cannot be zero.');
    if (!pyrk_loyalty_member_exists($pdo, $memberId)) Response::error('Member not found.', 404);
    $pdo->beginTransaction();
    try {
        $pdo->prepare('UPDATE member_profiles SET loyalty_points = GREATEST(0, COALESCE(loyalty_points,0) + ?), updated_at = NOW() WHERE user_id = ?')->execute([$points, $memberId]);
        $pdo->prepare('INSERT INTO loyalty_transactions (user_id, walk_id, points, transaction_type, description, created_at) VALUES (?, NULL, ?, ?, ?, NOW())')->execute([$memberId, $points, $points > 0 ? 'manual_reward' : 'adjustment', $description]);
        $pdo->commit();
        Security::audit((int)$user['id'], 'loyalty_points_adjusted', 'users', $memberId, ['points' => $points, 'description' => $description]);
        (new NotificationService())->notifyPointsAdjusted($memberId, $points, $description, (int)$user['id']);
        Response::ok([], 'Loyalty points updated.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not adjust points: ' . $e->getMessage(), 500);
    }
}


if ($method === 'POST' && $action === 'cancel_badge_award') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $awardId = (int)($input['award_id'] ?? 0);
    if (!$awardId) Response::error('Missing awarded badge id.');

    $pdo->beginTransaction();
    try {
        $result = pyrk_loyalty_cancel_badge_award($pdo, $awardId, (int)$user['id']);
        $pdo->commit();
        Response::ok($result, 'Badge cancelled and related points removed.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not cancel badge: ' . $e->getMessage());
    }
}

if ($method === 'POST' && $action === 'repair_walk_completion_points') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $pdo->beginTransaction();
    try {
        $result = pyrk_loyalty_repair_walk_completion_points($pdo, (int)$user['id']);
        $pdo->commit();
        Security::audit((int)$user['id'], 'loyalty_walk_points_repaired', 'loyalty_transactions', null, $result);
        $message = $result['fixed'] > 0
            ? ('Repaired ' . (int)$result['fixed'] . ' completed-walk transaction(s). Added point delta: ' . (int)$result['points_delta'] . '.')
            : 'All completed-walk loyalty transactions are already correct.';
        Response::ok($result, $message);
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not repair walk completion points: ' . $e->getMessage(), 500);
    }
}


if ($method === 'POST' && $action === 'evaluate_badges') {
    pyrk_loyalty_can_manage($user);
    Csrf::validateFromRequest();
    $memberId = (int)($input['member_id'] ?? 0);
    $pdo->beginTransaction();
    try {
        $awarded = pyrk_loyalty_evaluate_badges($pdo, $memberId ?: null);
        $pdo->commit();
        Security::audit((int)$user['id'], 'badges_evaluated', 'badges', null, ['member_id' => $memberId ?: null, 'awarded' => count($awarded)]);
        Response::ok(['awarded' => $awarded], count($awarded) ? count($awarded) . ' badge(s) awarded.' : 'No new badges to award.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not evaluate badges: ' . $e->getMessage(), 500);
    }
}

Response::error('Unsupported loyalty action.', 404);
