'use strict';
const db = require('../../db/db');

/* ═══════════════════════════════════════════════════════════════════════
   Gamification — shared module: helpers + stmts + defs
   All sibling files (./service, ./api, ./admin) import from here.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── XP thresholds: level = floor(sqrt(xp / 100)) + 1 ── */
function xpToLevel(xp)   { return Math.floor(Math.sqrt((xp || 0) / 100)) + 1; }
function levelMinXp(lv)  { return (lv - 1) * (lv - 1) * 100; }
function levelMaxXp(lv)  { return lv * lv * 100; }

const RANKS = [
  [1,  'Новичок'],
  [5,  'Ученик'],
  [10, 'Знаток'],
  [15, 'Эксперт'],
  [20, 'Мастер'],
  [30, 'Гуру'],
];
function rankName(level) {
  let name = 'Новичок';
  for (const [min, r] of RANKS) if (level >= min) name = r;
  return name;
}

/* ── Daily-goal tiers (used by daily logic + setGoalTier API) ── */
const GOAL_TIERS = {
  easy:   { tests: 2, xp: 100, bonus: 30,  label: 'Лёгкая' },
  medium: { tests: 3, xp: 200, bonus: 50,  label: 'Средняя' },
  hard:   { tests: 5, xp: 500, bonus: 100, label: 'Тяжёлая' },
};

/* ── Achievement definitions (seed via seedAchievements()) ──
   Each row carries the legacy `cat` for back-compat AND the new
   taxonomy fields used by the profile UI:
     group   — top-level bucket (onboarding/volume/mastery/...)
     track   — sub-track inside a group (e.g. 'tests', 'streak')
     tier    — 1..5 within a track (NULL for singletons)
     sort_order — fixed display order; new entries pick gaps. */
const ACHIEVEMENT_DEFS = [
  // Onboarding
  { slug: 'first_test',     title: 'Первый тест',         icon: 'target',     cat: 'start',   group: 'onboarding',  track: 'first_test',    tier: 1, sort_order: 110, desc: 'Пройти свой первый тест' },
  { slug: 'first_perfect',  title: 'Идеальный результат', icon: 'hundred',    cat: 'start',   group: 'onboarding',  track: 'first_perfect', tier: 1, sort_order: 120, desc: 'Получить 100% на тесте' },
  { slug: 'first_class',    title: 'Вступил в класс',     icon: 'school',     cat: 'start',   group: 'onboarding',  track: 'first_class',   tier: 1, sort_order: 130, desc: 'Присоединиться к классу' },
  // Volume — quantity-driven
  { slug: 'tests_10',       title: '10 тестов',           icon: 'file-text',  cat: 'volume',  group: 'volume',      track: 'tests',         tier: 1, sort_order: 210, desc: 'Завершить 10 тестов' },
  { slug: 'tests_50',       title: '50 тестов',           icon: 'books',      cat: 'volume',  group: 'volume',      track: 'tests',         tier: 2, sort_order: 220, desc: 'Завершить 50 тестов' },
  { slug: 'tests_100',      title: '100 тестов',          icon: 'trophy',     cat: 'volume',  group: 'volume',      track: 'tests',         tier: 3, sort_order: 230, desc: 'Завершить 100 тестов' },
  { slug: 'assign_first',   title: 'Первое задание',      icon: 'clipboard',       cat: 'assign', group: 'volume', track: 'assign',         tier: 1, sort_order: 240, desc: 'Сдать первое задание' },
  { slug: 'assign_10',      title: '10 заданий',          icon: 'clipboard-check', cat: 'assign', group: 'volume', track: 'assign',         tier: 2, sort_order: 250, desc: 'Сдать 10 заданий' },
  { slug: 'xp_1000',        title: '1000 XP',             icon: 'diamond',    cat: 'xp',      group: 'volume',      track: 'xp_total',      tier: 1, sort_order: 260, desc: 'Набрать 1000 XP' },
  { slug: 'xp_5000',        title: '5000 XP',             icon: 'diamond',    cat: 'xp',      group: 'volume',      track: 'xp_total',      tier: 2, sort_order: 270, desc: 'Набрать 5000 XP' },
  { slug: 'xp_10000',       title: '10 000 XP',           icon: 'diamond',    cat: 'xp',      group: 'volume',      track: 'xp_total',      tier: 3, sort_order: 280, desc: 'Набрать 10 000 XP' },
  // Mastery — quality / completion
  { slug: 'level_3',        title: 'Начинающий',          icon: 'book-open',  cat: 'level',   group: 'mastery',     track: 'level',         tier: 1, sort_order: 310, desc: 'Достичь 3 уровня' },
  { slug: 'level_5',        title: 'Ученик',              icon: 'book-open',  cat: 'level',   group: 'mastery',     track: 'level',         tier: 2, sort_order: 320, desc: 'Достичь 5 уровня' },
  { slug: 'level_10',       title: 'Знаток',              icon: 'brain',      cat: 'level',   group: 'mastery',     track: 'level',         tier: 3, sort_order: 330, desc: 'Достичь 10 уровня' },
  { slug: 'level_20',       title: 'Мастер',              icon: 'crown',      cat: 'level',   group: 'mastery',     track: 'level',         tier: 4, sort_order: 340, desc: 'Достичь 20 уровня' },
  { slug: 'score_90',       title: 'Отличник',            icon: 'star',       cat: 'mastery', group: 'mastery',     track: 'score',         tier: 1, sort_order: 350, desc: '5 тестов подряд на 90%+' },
  { slug: 'speed_demon',    title: 'Скорострел',          icon: 'zap',        cat: 'mastery', group: 'mastery',     track: 'speed',         tier: 1, sort_order: 360, desc: 'Тест на 90%+ за <50% времени' },
  { slug: 'theory_first',   title: 'Первый урок',         icon: 'book-open',  cat: 'theory',  group: 'mastery',     track: 'theory',        tier: 1, sort_order: 370, desc: 'Прочитать первый урок' },
  { slug: 'theory_10',      title: 'Читатель',            icon: 'library',    cat: 'theory',  group: 'mastery',     track: 'theory',        tier: 2, sort_order: 380, desc: 'Прочитать 10 уроков' },
  { slug: 'theory_course',  title: 'Завершил курс',       icon: 'graduation-cap', cat: 'theory', group: 'mastery',  track: 'theory',        tier: 3, sort_order: 390, desc: 'Пройти полный курс целиком' },
  // Consistency — streaks, goals, plan
  { slug: 'streak_3',       title: 'Три дня подряд',      icon: 'flame',      cat: 'streak',  group: 'consistency', track: 'streak',        tier: 1, sort_order: 410, desc: '3 дня активности подряд' },
  { slug: 'streak_7',       title: 'Неделя подряд',       icon: 'flame',      cat: 'streak',  group: 'consistency', track: 'streak',        tier: 2, sort_order: 420, desc: '7 дней активности подряд' },
  { slug: 'streak_30',      title: 'Месяц подряд',        icon: 'flame',      cat: 'streak',  group: 'consistency', track: 'streak',        tier: 3, sort_order: 430, desc: '30 дней активности подряд' },
  // Exploration — feature discovery
  { slug: 'lab_first',       title: 'Первый опыт',         icon: 'flask-conical', cat: 'lab', group: 'exploration', track: 'lab',           tier: 1, sort_order: 510, desc: 'Провести первый эксперимент в лаборатории' },
  { slug: 'lab_5',           title: 'Юный химик',          icon: 'flask-conical', cat: 'lab', group: 'exploration', track: 'lab',           tier: 2, sort_order: 520, desc: 'Провести 5 экспериментов' },
  { slug: 'lab_20',          title: 'Лаборант',            icon: 'test-tubes',    cat: 'lab', group: 'exploration', track: 'lab',           tier: 3, sort_order: 530, desc: 'Провести 20 экспериментов' },
  { slug: 'lab_50',          title: 'Исследователь',       icon: 'microscope',    cat: 'lab', group: 'exploration', track: 'lab',           tier: 4, sort_order: 540, desc: 'Провести 50 экспериментов' },
  { slug: 'lab_reactions_10',title: '10 реакций',          icon: 'atom',          cat: 'lab', group: 'exploration', track: 'lab_reactions', tier: 1, sort_order: 550, desc: 'Обнаружить 10 различных реакций' },
  { slug: 'lab_reactions_30',title: '30 реакций',          icon: 'atom',          cat: 'lab', group: 'exploration', track: 'lab_reactions', tier: 2, sort_order: 560, desc: 'Обнаружить 30 различных реакций' },
  { slug: 'rb_first',        title: 'Первый вид КК',       icon: 'leaf',          cat: 'redbook', group: 'exploration', track: 'red_book',       tier: 1, sort_order: 570, desc: 'Открыть первый вид Красной книги' },
  { slug: 'rb_10',           title: '10 видов КК',         icon: 'leaf',          cat: 'redbook', group: 'exploration', track: 'red_book',       tier: 2, sort_order: 580, desc: 'Открыть 10 видов Красной книги' },
  { slug: 'rb_25',           title: 'Четверть коллекции',  icon: 'trees',         cat: 'redbook', group: 'exploration', track: 'red_book',       tier: 3, sort_order: 590, desc: 'Открыть 25 видов Красной книги' },
  { slug: 'rb_50',           title: 'Половина коллекции',  icon: 'trees',         cat: 'redbook', group: 'exploration', track: 'red_book',       tier: 4, sort_order: 600, desc: 'Открыть 50 видов Красной книги' },
  { slug: 'rb_all_cr',       title: 'Защитник природы',    icon: 'shield-check',  cat: 'redbook', group: 'exploration', track: 'red_book',       tier: 5, sort_order: 610, desc: 'Открыть все CR-виды Красной книги' },
  { slug: 'rb_quest_first',  title: 'Первый квест КК',     icon: 'map',           cat: 'redbook', group: 'exploration', track: 'red_book_quest', tier: 1, sort_order: 620, desc: 'Выполнить первый квест Красной книги' },
  { slug: 'rb_quest_5',      title: 'Квестмастер КК',      icon: 'map-pin',       cat: 'redbook', group: 'exploration', track: 'red_book_quest', tier: 2, sort_order: 630, desc: 'Выполнить 5 квестов Красной книги' },
  { slug: 'rb_sighting',     title: 'Наблюдатель',         icon: 'eye',           cat: 'redbook', group: 'exploration', track: 'red_book_obs',   tier: 1, sort_order: 640, desc: 'Добавить первое наблюдение вида' },

  // ── Phase 3 additions (migration 031) ──────────────────────────
  // Consistency extensions
  { slug: 'streak_100', title: 'Сто дней подряд', icon: 'flame',   cat: 'streak', group: 'consistency', track: 'streak',      tier: 4, sort_order: 440, desc: 'Невероятная серия — 100 дней активности подряд' },
  { slug: 'goal_30',    title: 'Цель: 30 дней',    icon: 'target',  cat: 'streak', group: 'consistency', track: 'goal_streak', tier: 1, sort_order: 450, desc: '30 дней подряд выполнять цель дня' },
  { slug: 'early_bird', title: 'Ранняя пташка',    icon: 'sunrise', cat: 'streak', group: 'consistency', track: 'time',        tier: 1, sort_order: 460, desc: 'Активность до 9 утра' },
  { slug: 'night_owl',  title: 'Ночная сова',      icon: 'moon',    cat: 'streak', group: 'consistency', track: 'time',        tier: 2, sort_order: 470, desc: 'Активность после 23:00' },

  // Exam-prep
  { slug: 'exam_first',        title: 'Старт экзамена',     icon: 'flag',         cat: 'exam', group: 'exam', track: 'exam_attempts', tier: 1, sort_order: 700, desc: 'Решена первая задача экзамена 9 класса' },
  { slug: 'exam_25_attempts',  title: '25 задач экзамена',  icon: 'list-checks',  cat: 'exam', group: 'exam', track: 'exam_attempts', tier: 2, sort_order: 710, desc: '25 решённых попыток в банке экзамена' },
  { slug: 'exam_100_attempts', title: '100 задач экзамена', icon: 'check-square', cat: 'exam', group: 'exam', track: 'exam_attempts', tier: 3, sort_order: 720, desc: '100 решённых попыток в банке экзамена' },
  { slug: 'exam_variant_clear',title: 'Чистый вариант',     icon: 'crown',        cat: 'exam', group: 'exam', track: 'variants',      tier: 1, sort_order: 730, desc: 'Все 10 задач варианта решены верно' },
  { slug: 'exam_5_variants',   title: '5 вариантов',        icon: 'layers',       cat: 'exam', group: 'exam', track: 'variants',      tier: 2, sort_order: 740, desc: '5 разных вариантов закрыто целиком' },
  { slug: 'exam_topic_master', title: 'Мастер темы',        icon: 'target',       cat: 'exam', group: 'exam', track: 'topic',         tier: 1, sort_order: 750, desc: '≥90% точности на одной из подтем (≥10 попыток)' },
  { slug: 'exam_mock_done',    title: 'Первый пробник',     icon: 'timer',        cat: 'exam', group: 'exam', track: 'mock',          tier: 1, sort_order: 760, desc: 'Завершён первый пробный экзамен' },
  { slug: 'exam_mock_pass',    title: 'Пробник сдан',       icon: 'badge-check',  cat: 'exam', group: 'exam', track: 'mock',          tier: 2, sort_order: 770, desc: 'Балл ≥ 7 на пробном экзамене' },
  { slug: 'exam_mock_perfect', title: 'Пробник на 10',      icon: 'trophy',       cat: 'exam', group: 'exam', track: 'mock',          tier: 3, sort_order: 780, desc: 'Идеальные 10 баллов на пробном экзамене' },

  // Mastery extensions — textbooks + flashcards
  { slug: 'tb_first_para',   title: 'Открыл параграф', icon: 'book-marked', cat: 'theory', group: 'mastery', track: 'tb_progress', tier: 1, sort_order: 800, desc: 'Прочитан первый параграф учебника' },
  { slug: 'tb_chapter_done', title: 'Глава закрыта',    icon: 'book-check',  cat: 'theory', group: 'mastery', track: 'tb_progress', tier: 2, sort_order: 810, desc: 'Все параграфы одной главы пройдены' },
  { slug: 'tb_book_done',    title: 'Учебник целиком',  icon: 'book-up',     cat: 'theory', group: 'mastery', track: 'tb_progress', tier: 3, sort_order: 820, desc: 'Все параграфы учебника пройдены' },
  { slug: 'tb_3_books',      title: 'Полка из трёх',    icon: 'library-big', cat: 'theory', group: 'mastery', track: 'tb_progress', tier: 4, sort_order: 830, desc: '3 учебника закрыто полностью' },
  { slug: 'fc_first_deck',   title: 'Первая колода',    icon: 'layers',      cat: 'theory', group: 'mastery', track: 'flashcards',  tier: 1, sort_order: 840, desc: 'Все карточки одной колоды разок повторены' },
  { slug: 'fc_100_cards',    title: '100 карточек',     icon: 'rotate-cw',   cat: 'theory', group: 'mastery', track: 'flashcards',  tier: 2, sort_order: 850, desc: '100 повторов карточек в сумме' },
  { slug: 'fc_1000_cards',   title: '1000 карточек',    icon: 'sparkles',    cat: 'theory', group: 'mastery', track: 'flashcards',  tier: 3, sort_order: 860, desc: '1000 повторов карточек в сумме' },

  // Exploration extensions
  { slug: 'bc_first_molecule', title: 'Первая молекула', icon: 'flask-round', cat: 'lab',   group: 'exploration', track: 'biochem',   tier: 1, sort_order: 870, desc: 'Собрана первая молекула в био-конструкторе' },
  { slug: 'bc_5_challenges',   title: '5 био-задач',     icon: 'beaker',      cat: 'lab',   group: 'exploration', track: 'biochem',   tier: 2, sort_order: 880, desc: '5 биохимических задач решено' },
  { slug: 'bc_20_challenges',  title: '20 био-задач',    icon: 'atom',        cat: 'lab',   group: 'exploration', track: 'biochem',   tier: 3, sort_order: 890, desc: '20 биохимических задач решено' },
  { slug: 'game_win_5',        title: '5 побед в играх', icon: 'gamepad-2',   cat: 'volume',group: 'exploration', track: 'minigames', tier: 1, sort_order: 900, desc: '5 побед в Виселице или Кроссворде суммарно' },
  { slug: 'game_win_25',       title: '25 побед в играх',icon: 'gamepad',     cat: 'volume',group: 'exploration', track: 'minigames', tier: 2, sort_order: 910, desc: '25 побед в Виселице или Кроссворде суммарно' },
  { slug: 'pet_streak_7',      title: 'Заботливый',      icon: 'heart',       cat: 'volume',group: 'exploration', track: 'pet',       tier: 1, sort_order: 920, desc: '7 дней подряд гладить питомца' },
  { slug: 'pet_streak_30',     title: 'Лучший друг',     icon: 'paw-print',   cat: 'volume',group: 'exploration', track: 'pet',       tier: 2, sort_order: 930, desc: '30 дней подряд гладить питомца' },

  // Social
  { slug: 'lb_top10',         title: 'Топ-10 недели',       icon: 'trending-up',  cat: 'volume', group: 'social', track: 'leaderboard', tier: 1, sort_order: 1000, desc: 'Попасть в топ-10 лидерборда недели' },
  { slug: 'lb_top1',          title: 'Первое место',        icon: 'medal',        cat: 'volume', group: 'social', track: 'leaderboard', tier: 2, sort_order: 1010, desc: '№1 в лидерборде недели' },
  { slug: 'lq_first',         title: 'Первый live-quiz',    icon: 'radio',        cat: 'volume', group: 'social', track: 'live_quiz',   tier: 1, sort_order: 1020, desc: 'Принять участие в первом live-квизе' },
  { slug: 'lq_3_quizzes',     title: '3 live-квиза',        icon: 'megaphone',    cat: 'volume', group: 'social', track: 'live_quiz',   tier: 2, sort_order: 1030, desc: 'Принять участие в 3 live-квизах' },
  { slug: 'cr_first_join',    title: 'Первое подключение',  icon: 'door-open',    cat: 'volume', group: 'social', track: 'classroom',   tier: 1, sort_order: 1040, desc: 'Первое подключение к уроку в classroom' },
  { slug: 'cr_5_lessons',     title: '5 уроков',            icon: 'presentation', cat: 'volume', group: 'social', track: 'classroom',   tier: 2, sort_order: 1050, desc: 'Посещено 5 уроков classroom' },
  { slug: 'cr_25_lessons',    title: '25 уроков',           icon: 'school',       cat: 'volume', group: 'social', track: 'classroom',   tier: 3, sort_order: 1060, desc: 'Посещено 25 уроков classroom' },
  { slug: 'class_5_members',  title: 'Учитель: 5 учеников', icon: 'users',        cat: 'start',  group: 'social', track: 'teacher',     tier: 1, sort_order: 1070, desc: 'Учитель: класс из ≥5 учеников' },
  { slug: 'class_25_members', title: 'Учитель: 25 учеников',icon: 'users-2',      cat: 'start',  group: 'social', track: 'teacher',     tier: 2, sort_order: 1080, desc: 'Учитель: класс из ≥25 учеников' },
  { slug: 'parent_link',      title: 'Связан с родителем',  icon: 'user-plus',    cat: 'start',  group: 'social', track: 'family',      tier: 1, sort_order: 1090, desc: 'Связь с родителем установлена' },
];

/* Display metadata for top-level groups. Order = render order in UI.
   'exam' was added in migration 031 (Phase 3) — it slots between
   consistency and exploration in display order. */
const ACHIEVEMENT_GROUPS = [
  { slug: 'onboarding',  title: 'Старт',         icon: 'flag',           sort_order: 100 },
  { slug: 'volume',      title: 'Объём',         icon: 'bar-chart-2',    sort_order: 200 },
  { slug: 'mastery',     title: 'Качество',      icon: 'award',          sort_order: 300 },
  { slug: 'consistency', title: 'Постоянство',   icon: 'flame',          sort_order: 400 },
  { slug: 'exam',        title: 'Экзамен 9',     icon: 'clipboard-list', sort_order: 450 },
  { slug: 'exploration', title: 'Исследование',  icon: 'compass',        sort_order: 500 },
  { slug: 'social',      title: 'Социальное',    icon: 'users',          sort_order: 600 },
];

/* ── Avatar frames (unlocked by achievement slug) ── */
const AVATAR_FRAMES = [
  { id: 'default',     name: 'Стандарт',   css: '',                                                                                       unlock: null },
  { id: 'fire',        name: 'Огненная',   css: 'box-shadow:0 0 0 3px #FF6B35,0 0 12px rgba(255,107,53,0.4)',                              unlock: 'streak_7' },
  { id: 'diamond',     name: 'Бриллиант',  css: 'box-shadow:0 0 0 3px #06D6E0,0 0 12px rgba(6,214,224,0.4)',                              unlock: 'xp_5000' },
  { id: 'gold',        name: 'Золотая',    css: 'box-shadow:0 0 0 3px #FFD700,0 0 12px rgba(255,215,0,0.4)',                              unlock: 'tests_100' },
  { id: 'violet_glow', name: 'Фиолет',     css: 'box-shadow:0 0 0 3px #9B5DE5,0 0 16px rgba(155,93,229,0.5)',                              unlock: 'level_10' },
  { id: 'rainbow',     name: 'Радуга',     css: 'background:conic-gradient(#FF6B6B,#FFD93D,#6BCB77,#4D96FF,#9B5DE5,#FF6B6B);padding:3px', unlock: 'level_20' },
  { id: 'crown',       name: 'Корона',     css: 'box-shadow:0 0 0 3px #FFD700,0 0 20px rgba(255,215,0,0.6)',                              unlock: 'xp_10000' },
  { id: 'perfect',     name: 'Идеал',      css: 'box-shadow:0 0 0 3px #06D664,0 0 12px rgba(6,214,100,0.4)',                              unlock: 'first_perfect' },
];

/* ── Prepared statements (module-level to avoid re-parsing per request) ── */
const stmts = {
  // XP & coins
  insertXpLog:       db.prepare('INSERT INTO xp_log (user_id, amount, reason) VALUES (?, ?, ?)'),
  incrXP:            db.prepare('UPDATE users SET xp = xp + ? WHERE id = ?'),
  getXP:             db.prepare('SELECT xp FROM users WHERE id = ?'),
  setLevel:          db.prepare('UPDATE users SET level = ? WHERE id = ?'),
  incrCoins:         db.prepare('UPDATE users SET coins = coins + ? WHERE id = ?'),

  // Streak
  getStreak:         db.prepare('SELECT streak_current, streak_best, streak_date FROM users WHERE id = ?'),
  setStreak:         db.prepare('UPDATE users SET streak_current = ?, streak_best = ?, streak_date = ? WHERE id = ?'),

  // getXPInfo
  getUserXPInfo:     db.prepare('SELECT xp, level, streak_current, streak_best, streak_date FROM users WHERE id = ?'),

  countUserAssignments: db.prepare(`SELECT COUNT(*) as n FROM assignment_sessions ass JOIN test_sessions ts ON ts.id = ass.session_id WHERE ts.user_id = ? AND ts.status = 'completed'`),

  // Achievements
  getAchBySlug:      db.prepare('SELECT id, title, icon FROM achievements WHERE slug = ?'),
  hasUserAch:        db.prepare('SELECT 1 FROM user_achievements WHERE user_id = ? AND achievement_id = ?'),
  insertUserAch:     db.prepare('INSERT INTO user_achievements (user_id, achievement_id) VALUES (?, ?)'),
  insertAchNotif:    db.prepare("INSERT INTO notifications (user_id, type, message, link) VALUES (?, 'achievement', ?, '/profile')"),
  getUserForAch:     db.prepare(`
    SELECT u.xp, u.level, u.streak_current, u.lab_experiments, u.lab_reactions,
           (SELECT COUNT(*) FROM test_sessions WHERE user_id = u.id AND status = 'completed')                   AS test_count,
           (SELECT COUNT(*) FROM test_sessions WHERE user_id = u.id AND status = 'completed' AND score = total) AS perfect_count,
           (SELECT COUNT(*) FROM class_members  WHERE user_id = u.id)                                           AS class_count
    FROM users u WHERE u.id = ?
  `),
  getLast5Tests:     db.prepare(`
    SELECT score, total FROM test_sessions
    WHERE user_id = ? AND status = 'completed'
    ORDER BY finished_at DESC LIMIT 5
  `),

  // Lab
  incrLabExp:        db.prepare('UPDATE users SET lab_experiments = lab_experiments + 1 WHERE id = ?'),
  incrLabReact:      db.prepare('UPDATE users SET lab_reactions = lab_reactions + ? WHERE id = ?'),

  // Daily goals
  getDailyGoal:      db.prepare('SELECT * FROM daily_goals WHERE user_id = ? AND date = ?'),
  getUserGoalTier:   db.prepare('SELECT goal_tier FROM users WHERE id = ?'),
  insertDailyGoal:   db.prepare('INSERT INTO daily_goals (user_id, date, tests_target, tests_done, xp_target, xp_earned) VALUES (?, ?, ?, 0, ?, 0)'),
  incrDailyGoal:     db.prepare('UPDATE daily_goals SET tests_done = tests_done + ?, xp_earned = xp_earned + ? WHERE user_id = ? AND date = ?'),
  checkGoalBonus:    db.prepare("SELECT 1 FROM xp_log WHERE user_id = ? AND reason = 'daily_goal' AND date(created_at) = ?"),

  // Challenges
  getOpenChallenges: db.prepare('SELECT * FROM challenges WHERE user_id = ? AND week = ? AND completed = 0'),
  incrChallenge:     db.prepare('UPDATE challenges SET progress = MIN(progress + ?, target) WHERE id = ?'),
  getChallengeById:  db.prepare('SELECT * FROM challenges WHERE id = ?'),
  completeChallenge: db.prepare('UPDATE challenges SET completed = 1 WHERE id = ?'),
  getChallengesWeek: db.prepare('SELECT * FROM challenges WHERE user_id = ? AND week = ? ORDER BY completed, id'),
  getChallengeOwned: db.prepare('SELECT * FROM challenges WHERE id = ? AND user_id = ?'),
  markClaimed:       db.prepare('UPDATE challenges SET claimed = 1 WHERE id = ?'),

  // API handlers
  getUserPrefs:      db.prepare('SELECT goal_tier, avatar_frame FROM users WHERE id = ?'),
  getUnlockedSlugs:  db.prepare('SELECT a.slug FROM user_achievements ua JOIN achievements a ON a.id = ua.achievement_id WHERE ua.user_id = ?'),
  getUserFrame:      db.prepare('SELECT avatar_frame FROM users WHERE id = ?'),
  checkFrameUnlock:  db.prepare('SELECT a.id FROM achievements a JOIN user_achievements ua ON ua.achievement_id = a.id WHERE a.slug = ? AND ua.user_id = ?'),
  setUserFrame:      db.prepare('UPDATE users SET avatar_frame = ? WHERE id = ?'),
  setUserGoalTier:   db.prepare('UPDATE users SET goal_tier = ? WHERE id = ?'),
  getAllAchs:        db.prepare(`
    SELECT id, slug, title, icon, category, description,
           group_slug, track, tier, sort_order, required_feature
      FROM achievements
     ORDER BY sort_order, id
  `),
  getUserAchs:       db.prepare('SELECT achievement_id, unlocked_at FROM user_achievements WHERE user_id = ?'),
  xpHistory:         db.prepare('SELECT amount, reason, created_at FROM xp_log WHERE user_id = ? ORDER BY created_at DESC LIMIT ?'),

  // Admin
  checkUserById:     db.prepare('SELECT id FROM users WHERE id = ?'),
  getUserGamInfo:    db.prepare('SELECT xp, level, coins FROM users WHERE id = ?'),
  adminResetUser:    db.prepare("UPDATE users SET xp=0, level=1, coins=0, streak_current=0, streak_best=0, streak_date=NULL, avatar_frame='default' WHERE id=?"),
  deleteXpLog:       db.prepare('DELETE FROM xp_log WHERE user_id=?'),
  deleteUserAchs:    db.prepare('DELETE FROM user_achievements WHERE user_id=?'),
  deleteDailyGoals:  db.prepare('DELETE FROM daily_goals WHERE user_id=?'),
  deleteChallenges:  db.prepare('DELETE FROM challenges WHERE user_id=?'),
  deleteUserPurch:   db.prepare('DELETE FROM user_purchases WHERE user_id=?'),
  adminGetUserFull:  db.prepare('SELECT id, name, xp, level, coins, streak_current, streak_best, goal_tier, avatar_frame FROM users WHERE id=?'),
  adminGetUserAchs:  db.prepare('SELECT a.slug, a.title, a.icon, ua.unlocked_at FROM user_achievements ua JOIN achievements a ON a.id=ua.achievement_id WHERE ua.user_id=?'),
  adminGetUserPurch: db.prepare('SELECT si.name, si.type, up.purchased_at FROM user_purchases up JOIN shop_items si ON si.id=up.item_id WHERE up.user_id=?'),
  adminGetUserXPH:   db.prepare('SELECT amount, reason, created_at FROM xp_log WHERE user_id=? ORDER BY created_at DESC LIMIT 30'),
  adminTotalXP:      db.prepare('SELECT COALESCE(SUM(xp),0) as v FROM users'),
  adminTotalCoins:   db.prepare('SELECT COALESCE(SUM(coins),0) as v FROM users'),
  adminAvgLevel:     db.prepare("SELECT ROUND(AVG(level),1) as v FROM users WHERE role='student'"),
  adminAchCount:     db.prepare('SELECT COUNT(*) as v FROM user_achievements'),
  adminTopXP:        db.prepare("SELECT id, name, xp, level, coins FROM users WHERE role='student' ORDER BY xp DESC LIMIT 10"),
  adminRecentXP:     db.prepare('SELECT xl.amount, xl.reason, xl.created_at, u.name FROM xp_log xl JOIN users u ON u.id=xl.user_id ORDER BY xl.created_at DESC LIMIT 20'),
  adminTotalPurch:   db.prepare('SELECT COUNT(*) as v FROM user_purchases'),
  adminRecentPurch:  db.prepare(`SELECT up.purchased_at, u.name AS user_name, si.name AS item_name, si.price, si.type
    FROM user_purchases up JOIN users u ON u.id=up.user_id JOIN shop_items si ON si.id=up.item_id
    ORDER BY up.purchased_at DESC LIMIT 20`),
};

/* ── Kill-switch ─────────────────────────────────────────────────
   The 'gamification' feature flag is the master switch. When admin
   turns it off, ALL XP/coin awards become no-ops, /api/gamification
   routes return 404, and the front-end hides every XP/coin/streak
   element via body.no-gamification.

   The flag is read often (every awardXP call) so we cache the value
   and only re-read after a short TTL. Toggling the flag also calls
   invalidateGamificationCache() from adminController so the change
   takes effect immediately without waiting for the TTL. */
const _gamCache = { value: null, expiresAt: 0 };
const _GAM_TTL_MS = 30_000;
const _stmtFeatureGam = db.prepare("SELECT value FROM app_settings WHERE key = 'feature_gamification_enabled'");

function isGamificationEnabled() {
  const now = Date.now();
  if (_gamCache.value !== null && now < _gamCache.expiresAt) return _gamCache.value;
  let on = true;
  try {
    const row = _stmtFeatureGam.get();
    // Default ON if row missing (migration 029 seeds '1', but be defensive).
    on = !row || row.value === '1';
  } catch { on = true; }
  _gamCache.value = on;
  _gamCache.expiresAt = now + _GAM_TTL_MS;
  return on;
}

function invalidateGamificationCache() {
  _gamCache.value = null;
  _gamCache.expiresAt = 0;
}

module.exports = {
  xpToLevel, levelMinXp, levelMaxXp, rankName, RANKS,
  GOAL_TIERS, ACHIEVEMENT_DEFS, ACHIEVEMENT_GROUPS, AVATAR_FRAMES,
  stmts,
  isGamificationEnabled, invalidateGamificationCache,
};
