'use strict';
const db = require('../../db/db');
const { stmts, GOAL_TIERS, AVATAR_FRAMES, rankName } = require('./_shared');
const { computeFeaturesForUser } = require('../../middleware/features');
const { getXPInfo, getDailyGoal, ensureChallenges, awardXP, awardCoins } = require('./service');

/* ═══════════════════════════════════════════════════════════════════════
   Gamification — public API handlers (mounted under /api/gamification/*)
   ═══════════════════════════════════════════════════════════════════════ */

/* GET /api/gamification/me — current user XP, level, streak, goals */
function getMe(req, res) {
  const info = getXPInfo(req.user.id);
  if (!info) return res.status(404).json({ error: 'User not found' });
  const goal = getDailyGoal(req.user.id);
  const pref = stmts.getUserPrefs.get(req.user.id);
  const tierKey = (pref && pref.goal_tier) || 'medium';
  const frameId = (pref && pref.avatar_frame) || 'default';
  const frame = AVATAR_FRAMES.find(f => f.id === frameId) || AVATAR_FRAMES[0];
  res.json({ ...info, dailyGoal: goal, goalTier: tierKey, goalTiers: GOAL_TIERS, avatarFrame: frame });
}

/* GET /api/gamification/frames — available avatar frames */
function getFrames(req, res) {
  const unlocked = stmts.getUnlockedSlugs.all(req.user.id).map(r => r.slug);
  const user = stmts.getUserFrame.get(req.user.id);
  const selected = (user && user.avatar_frame) || 'default';
  const frames = AVATAR_FRAMES.map(f => ({
    ...f,
    unlocked: !f.unlock || unlocked.includes(f.unlock),
    selected: f.id === selected,
  }));
  res.json({ frames, selected });
}

/* POST /api/gamification/frame — set avatar frame */
function setFrame(req, res) {
  const { frame } = req.body;
  const f = AVATAR_FRAMES.find(fr => fr.id === frame);
  if (!f) return res.status(400).json({ error: 'Unknown frame' });
  if (f.unlock) {
    const ach = stmts.checkFrameUnlock.get(f.unlock, req.user.id);
    if (!ach) return res.status(403).json({ error: 'Frame not unlocked' });
  }
  stmts.setUserFrame.run(frame, req.user.id);
  res.json({ frame, css: f.css });
}

/* POST /api/gamification/goal-tier — set daily goal difficulty */
function setGoalTier(req, res) {
  const { tier } = req.body;
  if (!GOAL_TIERS[tier]) return res.status(400).json({ error: 'Invalid tier. Use: easy, medium, hard' });
  stmts.setUserGoalTier.run(tier, req.user.id);
  res.json({ tier, ...GOAL_TIERS[tier] });
}

/* GET /api/gamification/achievements — all achievements + user unlocks.
   Response stays an array (backward compatible). Each item now carries
   `group_slug` / `track` / `tier` / `sort_order` so the UI can group +
   order without an extra endpoint. Group display titles are hard-coded
   client-side (small + stable).

   Feature gating: achievements whose `required_feature` is disabled for
   this user (per merged global/class/free_student flags) are hidden —
   but only if the user hasn't unlocked them yet. Already-earned
   achievements are never taken away, even after the module is turned
   off. */
function getAchievements(req, res) {
  const all = stmts.getAllAchs.all();
  const unlocked = stmts.getUserAchs.all(req.user.id);
  const unlockedMap = {};
  for (const u of unlocked) unlockedMap[u.achievement_id] = u.unlocked_at;

  const feats = computeFeaturesForUser(req.user.id, req.user.role);

  const result = [];
  for (const a of all) {
    const isUnlocked = !!unlockedMap[a.id];
    // Hide locked achievements whose required_feature is explicitly off.
    // Missing flag is treated as ON (opt-in disable model).
    if (!isUnlocked && a.required_feature && feats[a.required_feature] === false) continue;
    result.push({
      ...a,
      unlocked: isUnlocked,
      unlocked_at: unlockedMap[a.id] || null,
    });
  }
  res.json(result);
}

/* GET /api/gamification/leaderboard?class_id=X&period=week|all */
function getLeaderboard(req, res) {
  const classId = req.query.class_id ? Number(req.query.class_id) : null;
  const period = req.query.period || 'all';

  let rows;
  if (period === 'week') {
    const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString();
    if (classId) {
      rows = db.prepare(`
        SELECT u.id, u.name, COALESCE(SUM(xl.amount), 0) AS week_xp, u.xp, u.level,
               u.streak_current AS streak
        FROM users u
        JOIN class_members cm ON cm.user_id = u.id AND cm.class_id = ?
        LEFT JOIN xp_log xl ON xl.user_id = u.id AND xl.created_at >= ?
        WHERE u.role = 'student'
        GROUP BY u.id ORDER BY week_xp DESC LIMIT 30
      `).all(classId, weekAgo);
    } else {
      rows = db.prepare(`
        SELECT u.id, u.name, COALESCE(SUM(xl.amount), 0) AS week_xp, u.xp, u.level,
               u.streak_current AS streak
        FROM users u
        LEFT JOIN xp_log xl ON xl.user_id = u.id AND xl.created_at >= ?
        WHERE u.role = 'student'
        GROUP BY u.id ORDER BY week_xp DESC LIMIT 30
      `).all(weekAgo);
    }
    rows.forEach((r, i) => { r.position = i + 1; r.sort_xp = r.week_xp; });
  } else {
    if (classId) {
      rows = db.prepare(`
        SELECT u.id, u.name, u.xp, u.level, u.streak_current AS streak
        FROM users u
        JOIN class_members cm ON cm.user_id = u.id AND cm.class_id = ?
        WHERE u.role = 'student'
        ORDER BY u.xp DESC LIMIT 30
      `).all(classId);
    } else {
      rows = db.prepare(`
        SELECT u.id, u.name, u.xp, u.level, u.streak_current AS streak
        FROM users u WHERE u.role = 'student'
        ORDER BY u.xp DESC LIMIT 30
      `).all();
    }
    rows.forEach((r, i) => { r.position = i + 1; r.sort_xp = r.xp; });
  }

  rows.forEach(r => { r.rank = rankName(r.level || 1); });
  res.json({ rows, period, class_id: classId });
}

/* GET /api/gamification/xp-history — recent XP log */
function getXPHistory(req, res) {
  const limit = Math.min(50, Number(req.query.limit) || 20);
  const rows = stmts.xpHistory.all(req.user.id, limit);
  res.json(rows);
}

/* GET /api/gamification/challenges — current week's personal challenges */
function getChallenges(req, res) {
  ensureChallenges(req.user.id);
  const { _currentWeek } = require('./service');
  const week = _currentWeek();
  const rows = stmts.getChallengesWeek.all(req.user.id, week);
  res.json(rows);
}

/* POST /api/gamification/challenges/:id/claim — claim challenge reward */
function claimChallenge(req, res) {
  const id = Number(req.params.id);
  const c = stmts.getChallengeOwned.get(id, req.user.id);
  if (!c) return res.status(404).json({ error: 'Challenge not found' });
  if (!c.completed) return res.status(400).json({ error: 'Challenge not completed yet' });
  if (c.claimed) return res.status(400).json({ error: 'Already claimed' });

  stmts.markClaimed.run(id);
  awardXP(req.user.id, c.xp_reward, `Испытание: ${c.title}`);
  awardCoins(req.user.id, Math.floor(c.xp_reward / 5), `Испытание: ${c.title}`);
  res.json({ xp: c.xp_reward });
}

/* POST /api/gamification/self-award — начисление XP из учебника */
function selfAward(req, res) {
  const amount = Number(req.body.amount);
  const source = String(req.body.source || '');
  if (!Number.isInteger(amount) || amount < 1 || amount > 50) {
    return res.status(400).json({ error: 'amount должен быть целым числом от 1 до 50' });
  }
  if (!/^[a-z0-9_-]{1,60}$/i.test(source)) {
    return res.status(400).json({ error: 'source неверный формат (a-z, 0-9, _, -, до 60 символов)' });
  }
  awardXP(req.user.id, amount, 'tb:' + source);
  const info = getXPInfo(req.user.id);
  res.json(info);
}

module.exports = {
  getMe, getFrames, setFrame, setGoalTier,
  getAchievements, getLeaderboard, getXPHistory,
  getChallenges, claimChallenge,
  selfAward,
};
