const db = require('../db/db');
const { pushNotif } = require('../utils/notifications');
const { onTestFinished, updateDailyGoal, awardXP, updateChallenges } = require('./gamificationController');
const { COMBO_BONUSES } = require('../constants');

/* ── Prepared statements (avoid re-parsing on every request) ──────────── */
const stmts = {
  /* stats: 7 queries → 2 via CTE + json_group_array */
  statsMega: db.prepare(`
    WITH base AS (
      SELECT ts.id, ts.score, ts.total, ts.started_at, ts.finished_at,
             s.slug AS subject_slug, s.name AS subject_name
      FROM test_sessions ts
      LEFT JOIN subjects s ON s.id = ts.subject_id
      WHERE ts.user_id = @uid AND ts.status = 'completed'
    ),
    hm AS (
      SELECT date(ts.started_at) AS day, COUNT(*) AS cnt
      FROM test_sessions ts
      WHERE ts.user_id = @uid AND ts.started_at >= date('now', '-90 days')
      GROUP BY day ORDER BY day
    ),
    sd AS (
      SELECT DISTINCT date(ts.started_at) AS d
      FROM test_sessions ts WHERE ts.user_id = @uid
      ORDER BY d DESC LIMIT 90
    )
    SELECT
      (SELECT COALESCE(json_group_array(json_object(
         'week', week, 'sessions', sessions, 'avg_pct', avg_pct)), '[]')
       FROM (SELECT strftime('%Y-%W', finished_at) AS week,
                    COUNT(*) AS sessions,
                    AVG(CASE WHEN total>0 THEN score*100.0/total END) AS avg_pct
             FROM base WHERE finished_at >= date('now', '-84 days')
             GROUP BY week ORDER BY week)) AS weekly,
      (SELECT COALESCE(json_group_array(json_object('day', day, 'count', cnt)), '[]')
       FROM hm) AS heatmap,
      (SELECT COALESCE(json_group_array(json_object(
         'slug', subject_slug, 'name', subject_name,
         'sessions', sessions, 'avg_pct', avg_pct,
         'total_correct', total_correct, 'total_questions', total_questions)), '[]')
       FROM (SELECT subject_slug, subject_name, COUNT(*) AS sessions,
                    AVG(CASE WHEN total>0 THEN score*100.0/total END) AS avg_pct,
                    SUM(score) AS total_correct, SUM(total) AS total_questions
             FROM base GROUP BY subject_slug ORDER BY sessions DESC)) AS bySubject,
      (SELECT COALESCE(json_group_array(json_object(
         'id', id, 'score', score, 'total', total,
         'finished_at', finished_at, 'subject_slug', subject_slug)), '[]')
       FROM (SELECT id, score, total, finished_at, subject_slug
             FROM base ORDER BY finished_at DESC LIMIT 20)) AS trend,
      (SELECT json_object(
         'sessions', COUNT(*),
         'correct',  COALESCE(SUM(score), 0),
         'questions',COALESCE(SUM(total), 0),
         'avg_pct',  COALESCE(AVG(CASE WHEN total>0 THEN score*100.0/total END), 0))
       FROM base) AS totals,
      (SELECT COALESCE(json_group_array(d), '[]') FROM sd) AS streakDays
  `),
  courseProgress: db.prepare(`
    SELECT c.id, c.title, c.cover_emoji, c.subject_slug,
           COUNT(l.id) AS total_lessons, COUNT(lp.id) AS done_lessons
    FROM courses c JOIN lessons l ON l.course_id = c.id AND l.is_published = 1
    LEFT JOIN lesson_progress lp ON lp.lesson_id = l.id AND lp.user_id = @uid
    WHERE c.is_published = 1 GROUP BY c.id HAVING done_lessons > 0
    ORDER BY done_lessons * 1.0 / total_lessons DESC`),
  sessionCount: db.prepare('SELECT COUNT(*) AS total FROM test_sessions WHERE user_id = ?'),
};

function shuffle(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

const VALID_MODES = new Set(['exam', 'practice']);

/* ── POST /api/sessions ───────────────────────────────────────────────── */
function start(req, res, next) {
  const { subject_slug, topic_id, test_id } = req.body;
  const mode  = req.body.mode  || 'exam';
  const count = Number(req.body.count) || 25;

  if (!subject_slug) return res.status(400).json({ error: 'subject_slug is required' });
  if (!VALID_MODES.has(mode)) return res.status(400).json({ error: 'mode must be exam or practice' });
  if (!Number.isInteger(count) || count < 1 || count > 200)
    return res.status(400).json({ error: 'count must be an integer between 1 and 200' });

  const subject = db.prepare('SELECT id FROM subjects WHERE slug = ?').get(subject_slug);
  if (!subject) return res.status(404).json({ error: 'Subject not found' });

  let ids;
  let testTimeLimitSec = null;
  if (test_id) {
    const tRow = db.prepare('SELECT time_limit FROM tests WHERE id = ?').get(Number(test_id));
    if (tRow?.time_limit) testTimeLimitSec = tRow.time_limit * 60;
    const tq = db.prepare(
      'SELECT question_id AS id FROM test_questions WHERE test_id = ? ORDER BY order_index'
    ).all(Number(test_id));
    if (!tq.length) return res.status(404).json({ error: 'Test has no questions' });
    ids = shuffle(tq.map(r => r.id));
  } else {
    const rows = topic_id
      ? db.prepare('SELECT id FROM questions WHERE subject_id = ? AND topic_id = ?').all(subject.id, Number(topic_id))
      : db.prepare('SELECT id FROM questions WHERE subject_id = ?').all(subject.id);
    if (!rows.length) return res.status(404).json({ error: 'No questions found' });
    ids = shuffle(rows.map(r => r.id)).slice(0, count);
  }

  const total = ids.length;

  const createSession = db.transaction(() => {
    const { lastInsertRowid: session_id } = db.prepare(
      'INSERT INTO test_sessions (user_id, subject_id, mode, total) VALUES (?, ?, ?, ?)'
    ).run(req.user.id, subject.id, mode, total);

    const insertSQ = db.prepare(
      'INSERT INTO session_questions (session_id, question_id, order_index) VALUES (?, ?, ?)'
    );
    ids.forEach((qid, i) => insertSQ.run(session_id, qid, i));
    return session_id;
  });

  try {
    const session_id = createSession();
    const questions  = loadQuestionsForSession(ids);
    res.status(201).json({ session_id, total, mode, questions, time_limit_sec: testTimeLimitSec });
  } catch (err) {
    next(err);
  }
}

/* ── POST /api/sessions/:id/answer ───────────────────────────────────── */
function answer(req, res) {
  const session_id = Number(req.params.id);
  const { question_id, option_id, time_spent_sec, answer_text, chosen_options } = req.body;

  const session = db.prepare(
    'SELECT id, mode, status FROM test_sessions WHERE id = ? AND user_id = ?'
  ).get(session_id, req.user.id);

  if (!session) return res.status(404).json({ error: 'Session not found' });
  if (session.status !== 'in_progress')
    return res.status(400).json({ error: 'Session already finished' });

  // Verify question belongs to this session — prevents answering questions from other sessions
  const sq = db.prepare(
    'SELECT 1 FROM session_questions WHERE session_id = ? AND question_id = ?'
  ).get(session_id, question_id);
  if (!sq) return res.status(400).json({ error: 'Question not in this session' });

  const q = db.prepare('SELECT id, type, correct_text FROM questions WHERE id = ?').get(question_id);
  if (!q) return res.status(400).json({ error: 'Invalid question' });

  let isCorrect = 0;
  let chosenOptionId = null;
  let storedAnswerText = null;

  if (q.type === 'short_answer') {
    const userAns = String(answer_text || '').trim().toLowerCase().replace(/\s+/g, ' ');
    const correct  = String(q.correct_text || '').trim().toLowerCase().replace(/\s+/g, ' ');
    isCorrect = userAns === correct ? 1 : 0;
    storedAnswerText = String(answer_text || '').trim();

  } else if (q.type === 'multi') {
    const selected  = Array.isArray(chosen_options) ? chosen_options.map(Number) : [];
    const allOpts   = db.prepare('SELECT id, is_correct FROM options WHERE question_id = ?').all(question_id);
    const correctIds = allOpts.filter(o => o.is_correct).map(o => o.id);
    const wrongIds   = allOpts.filter(o => !o.is_correct).map(o => o.id);
    isCorrect = (
      correctIds.every(id => selected.includes(id)) &&
      wrongIds.every(id => !selected.includes(id))
    ) ? 1 : 0;
    storedAnswerText = JSON.stringify(selected);

  } else if (q.type === 'matching') {
    const pairs = (() => { try { return JSON.parse(answer_text || '{}'); } catch (e) { console.error('[answer] matching parse error:', e.message); return {}; } })();
    const allOpts = db.prepare('SELECT id, match_pair FROM options WHERE question_id = ?').all(question_id);
    isCorrect = allOpts.length > 0 && allOpts.every(opt => pairs[String(opt.id)] === opt.match_pair) ? 1 : 0;
    storedAnswerText = answer_text;

  } else {
    // single / true_false
    const opt = db.prepare(
      'SELECT id, is_correct FROM options WHERE id = ? AND question_id = ?'
    ).get(option_id, question_id);
    if (!opt) return res.status(400).json({ error: 'Invalid option' });
    isCorrect = opt.is_correct;
    chosenOptionId = opt.id;
  }

  db.prepare(`
    INSERT INTO user_answers (session_id, question_id, chosen_option_id, answer_text, is_correct, time_spent_sec)
    VALUES (?, ?, ?, ?, ?, ?)
    ON CONFLICT(session_id, question_id)
    DO UPDATE SET chosen_option_id = excluded.chosen_option_id,
                  answer_text      = excluded.answer_text,
                  is_correct       = excluded.is_correct,
                  time_spent_sec   = excluded.time_spent_sec,
                  answered_at      = datetime('now')
  `).run(session_id, question_id, chosenOptionId, storedAnswerText, isCorrect, time_spent_sec ?? null);

  const response = { is_correct: isCorrect === 1 };

  if (session.mode === 'practice') {
    if (q.type === 'short_answer') {
      response.correct_text = q.correct_text;
    } else {
      response.correct_options = db.prepare(
        'SELECT id, text FROM options WHERE question_id = ? AND is_correct = 1'
      ).all(question_id);
    }
  }

  res.json(response);
}

/* ── POST /api/sessions/:id/finish ───────────────────────────────────── */
function finish(req, res) {
  const session_id = Number(req.params.id);

  const session = db.prepare(
    'SELECT * FROM test_sessions WHERE id = ? AND user_id = ?'
  ).get(session_id, req.user.id);

  if (!session) return res.status(404).json({ error: 'Session not found' });

  // Atomically mark as completed — guard against concurrent finish() calls
  let score;
  try {
    score = db.transaction(() => {
      const { score: s } = db.prepare(
        'SELECT COUNT(*) AS score FROM user_answers WHERE session_id = ? AND is_correct = 1'
      ).get(session_id);
      const upd = db.prepare(
        "UPDATE test_sessions SET status = 'completed', score = ?, finished_at = datetime('now') WHERE id = ? AND status = 'in_progress'"
      ).run(s, session_id);
      if (upd.changes === 0) throw Object.assign(new Error('already_finished'), { code: 'ALREADY_FINISHED' });
      return s;
    })();
  } catch (e) {
    if (e.code === 'ALREADY_FINISHED') return res.status(400).json({ error: 'Session already finished' });
    throw e;
  }

  // Compute max combo (consecutive correct answers in order)
  let maxCombo = 0;
  try {
    const ansRows = db.prepare(
      'SELECT is_correct FROM user_answers WHERE session_id = ? ORDER BY rowid'
    ).all(session_id);
    let streak = 0;
    for (const a of ansRows) {
      streak = a.is_correct ? streak + 1 : 0;
      if (streak > maxCombo) maxCombo = streak;
    }
  } catch (e) { console.error('[finish] combo calc:', e.message); }

  // Gamification: award XP, update streak, check achievements
  try {
    const timeSec = Math.round((Date.now() - new Date(session.started_at).getTime()) / 1000);
    // Check if linked to a test with time_limit
    let testTimeLimitSec = null;
    try {
      const tl = db.prepare(`
        SELECT t.time_limit FROM assignment_sessions ases
        JOIN assignments a ON a.id = ases.assignment_id
        JOIN tests t ON t.id = a.test_id
        WHERE ases.session_id = ?
      `).get(session_id);
      if (tl?.time_limit) testTimeLimitSec = tl.time_limit * 60;
    } catch (e) { console.error('[finish] time_limit fetch:', e.message); }
    // Combo bonus XP (thresholds from constants)
    let comboBonus = 0;
    for (const [min, xp] of COMBO_BONUSES) {
      if (maxCombo >= min) { comboBonus = xp; break; }
    }
    onTestFinished(req.user.id, score, session.total, timeSec, testTimeLimitSec);
    if (comboBonus > 0) {
      try { awardXP(req.user.id, comboBonus, `Комбо x${maxCombo}`); } catch (e) { console.error('[finish] comboBonus awardXP:', e.message); }
    }
    updateDailyGoal(req.user.id, 1, score * 10 + 50 + comboBonus);
    // Update personal challenges
    try {
      const subj = db.prepare('SELECT s.slug FROM subjects s WHERE s.id = ?').get(session.subject_id);
      const topicIds = db.prepare(`
        SELECT DISTINCT q.topic_id FROM session_questions sq
        JOIN questions q ON q.id = sq.question_id
        WHERE sq.session_id = ? AND q.topic_id IS NOT NULL
      `).all(session_id).map(r => r.topic_id);
      const slug = subj ? subj.slug : null;
      for (const tid of (topicIds.length ? topicIds : [null])) {
        updateChallenges(req.user.id, score, session.total, slug, tid);
      }
    } catch (e) { console.error('[finish] updateChallenges:', e.message); }
  } catch (e) { console.error('[finish] gamification:', e.message); }

  // Notify teacher if session linked to a class assignment
  try {
    const link = db.prepare(`
      SELECT a.title, COALESCE(c.teacher_id, a.created_by) AS teacher_id, u.name AS student_name
      FROM assignment_sessions ass
      JOIN assignments a ON a.id = ass.assignment_id
      LEFT JOIN classes c ON c.id = a.class_id
      JOIN users u ON u.id = ?
      WHERE ass.session_id = ?
    `).get(req.user.id, session_id);
    if (link) {
      const pct = Math.round((score / session.total) * 100);
      pushNotif(link.teacher_id, 'session', `«${link.student_name}» сдал «${link.title}» — ${pct}%`, '/classes');
    }
  } catch (e) { console.error('[finish] pushNotif teacher:', e.message); }

  res.json({
    session_id,
    score,
    total:   session.total,
    percent: session.total ? Math.round((score / session.total) * 100) : 0,
    time_sec: Math.round((Date.now() - new Date(session.started_at)) / 1000),
    maxCombo,
    review:  buildReview(session_id),
  });
}

/* ── GET /api/sessions/:id/result ────────────────────────────────────── */
function result(req, res) {
  const session_id = Number(req.params.id);

  const session = db.prepare(
    'SELECT * FROM test_sessions WHERE id = ? AND user_id = ?'
  ).get(session_id, req.user.id);

  if (!session) return res.status(404).json({ error: 'Session not found' });
  if (session.status !== 'completed')
    return res.status(400).json({ error: 'Session not finished yet' });

  // Check if session is linked to an assignment using a test with show_answers setting
  let show_answers = 1;
  try {
    const assnSess = db.prepare(`
      SELECT t.show_answers FROM assignment_sessions ases
      JOIN assignments a ON a.id = ases.assignment_id
      JOIN tests t ON t.id = a.test_id
      WHERE ases.session_id = ?
    `).get(session_id);
    if (assnSess) show_answers = assnSess.show_answers;
  } catch {}

  // Compute max combo for display
  let maxCombo = 0;
  try {
    const ansRows = db.prepare(
      'SELECT is_correct FROM user_answers WHERE session_id = ? ORDER BY rowid'
    ).all(session_id);
    let streak = 0;
    for (const a of ansRows) {
      streak = a.is_correct ? streak + 1 : 0;
      if (streak > maxCombo) maxCombo = streak;
    }
  } catch {}

  res.json({
    session_id,
    score:        session.score,
    total:        session.total,
    percent:      session.total ? Math.round((session.score / session.total) * 100) : 0,
    show_answers,
    maxCombo,
    review:       buildReview(session_id),
  });
}

/* ── GET /api/sessions/history ───────────────────────────────────────── */
function history(req, res) {
  const limit  = Math.min(100, Math.max(1, Number(req.query.limit) || 20));
  const cursor = Number(req.query.cursor) || 0;

  // Cursor-based: if cursor provided, use it; otherwise fall back to offset pagination
  if (cursor) {
    const rows = db.prepare(`
      SELECT ts.id, ts.mode, ts.score, ts.total, ts.status,
             ts.started_at, ts.finished_at,
             s.slug AS subject_slug, s.name AS subject_name
      FROM test_sessions ts
      LEFT JOIN subjects s ON s.id = ts.subject_id
      WHERE ts.user_id = ? AND ts.id < ?
      ORDER BY ts.id DESC
      LIMIT ?
    `).all(req.user.id, cursor, limit);
    const nextCursor = rows.length === limit ? rows[rows.length - 1].id : null;
    return res.json({ rows, nextCursor, limit });
  }

  // Offset-based (legacy)
  const page   = Math.max(1, Number(req.query.page) || 1);
  const offset = (page - 1) * limit;
  const { total } = db.prepare('SELECT COUNT(*) AS total FROM test_sessions WHERE user_id = ?').get(req.user.id);
  const rows = db.prepare(`
    SELECT ts.id, ts.mode, ts.score, ts.total, ts.status,
           ts.started_at, ts.finished_at,
           s.slug AS subject_slug, s.name AS subject_name
    FROM test_sessions ts
    LEFT JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.user_id = ?
    ORDER BY ts.started_at DESC
    LIMIT ? OFFSET ?
  `).all(req.user.id, limit, offset);
  res.json({ rows, total, page, limit });
}

/* ── helpers ─────────────────────────────────────────────────────────── */
function _placeholders(n) { return Array(n).fill('?').join(','); }

function loadQuestionsForSession(ids) {
  if (!ids.length) return [];
  const ph = _placeholders(ids.length);

  const questions = db.prepare(
    `SELECT id, text, type, difficulty, allow_html, image FROM questions WHERE id IN (${ph})`
  ).all(...ids);

  const allOptions = db.prepare(
    `SELECT question_id, id, text, match_pair FROM options WHERE question_id IN (${ph}) ORDER BY question_id, order_index`
  ).all(...ids);

  const optsByQ = {};
  for (const o of allOptions) (optsByQ[o.question_id] ??= []).push(o);

  // Restore caller-expected order
  const qMap = {};
  for (const q of questions) qMap[q.id] = q;

  return ids.map(id => {
    const q = qMap[id];
    if (!q) return null;
    q.options = q.type !== 'short_answer' ? (optsByQ[id] || []) : [];
    return q;
  }).filter(Boolean);
}

function buildReview(session_id) {
  const sqRows = db.prepare(
    'SELECT question_id FROM session_questions WHERE session_id = ? ORDER BY order_index'
  ).all(session_id);
  if (!sqRows.length) return [];

  const ids = sqRows.map(r => r.question_id);
  const ph  = _placeholders(ids.length);

  const questions = db.prepare(
    `SELECT id, text, type, explanation, correct_text, allow_html, image FROM questions WHERE id IN (${ph})`
  ).all(...ids);

  const answers = db.prepare(
    `SELECT question_id, chosen_option_id, answer_text, is_correct FROM user_answers WHERE session_id = ? AND question_id IN (${ph})`
  ).all(session_id, ...ids);

  const options = db.prepare(
    `SELECT question_id, id, text, is_correct, match_pair FROM options WHERE question_id IN (${ph}) ORDER BY question_id, order_index`
  ).all(...ids);

  const ansMap = {};
  for (const a of answers) ansMap[a.question_id] = a;

  const optsByQ = {};
  for (const o of options) (optsByQ[o.question_id] ??= []).push(o);

  const qMap = {};
  for (const q of questions) qMap[q.id] = q;

  return ids.map(id => {
    const q  = qMap[id];
    const ua = ansMap[id];
    return {
      ...q,
      options:           optsByQ[id] || [],
      chosen_option_id:  ua?.chosen_option_id ?? null,
      answer_text:       ua?.answer_text       ?? null,
      is_correct:        ua ? ua.is_correct === 1 : null,
    };
  });
}

/* ── GET /api/sessions/weak-topics ───────────────────────────────────── */
function weakTopics(req, res) {
  const rows = db.prepare(`
    SELECT t.id   AS topic_id,
           t.name AS topic,
           s.name AS subject_name,
           s.slug AS subject_slug,
           COUNT(ua.id)  AS total,
           SUM(CASE WHEN ua.is_correct = 0 THEN 1 ELSE 0 END) AS wrong,
           ROUND(
             CAST(SUM(CASE WHEN ua.is_correct = 0 THEN 1 ELSE 0 END) AS REAL)
             / COUNT(ua.id) * 100
           , 0) AS error_pct
    FROM user_answers ua
    JOIN test_sessions ts ON ts.id = ua.session_id
    JOIN questions     q  ON q.id  = ua.question_id
    JOIN topics        t  ON t.id  = q.topic_id
    JOIN subjects      s  ON s.id  = q.subject_id
    WHERE ts.user_id = ? AND ts.status = 'completed' AND q.topic_id IS NOT NULL
    GROUP BY q.topic_id
    HAVING total >= 2
    ORDER BY error_pct DESC, wrong DESC
    LIMIT 8
  `).all(req.user.id);

  res.json(rows);
}

/* ── GET /api/sessions/:id/questions ── resume existing session ─────── */
function getSessionQuestions(req, res) {
  const session_id = Number(req.params.id);
  const session = db.prepare(`
    SELECT ts.id, ts.mode, ts.total, ts.status, ts.started_at, s.slug AS subject_slug
    FROM test_sessions ts
    LEFT JOIN subjects s ON s.id = ts.subject_id
    WHERE ts.id = ? AND ts.user_id = ?
  `).get(session_id, req.user.id);
  if (!session) return res.status(404).json({ error: 'Session not found' });
  if (session.status !== 'in_progress') return res.status(400).json({ error: 'Session already finished' });

  const ids = db.prepare(
    'SELECT question_id FROM session_questions WHERE session_id = ? ORDER BY order_index'
  ).all(session_id).map(r => r.question_id);

  // Resolve time limit from linked test (if any)
  let time_limit_sec = null;
  try {
    const tl = db.prepare(`
      SELECT t.time_limit FROM assignment_sessions ases
      JOIN assignments a ON a.id = ases.assignment_id
      JOIN tests t ON t.id = a.test_id
      WHERE ases.session_id = ? AND t.time_limit IS NOT NULL
    `).get(session_id);
    if (tl?.time_limit) time_limit_sec = tl.time_limit * 60;
  } catch {}

  const questions = loadQuestionsForSession(ids);
  res.json({
    session_id,
    total: session.total,
    mode: session.mode,
    subject_slug: session.subject_slug,
    questions,
    time_limit_sec,
    started_at: session.started_at,
  });
}

/* ── GET /api/sessions/stats ── student dashboard charts ────────────── */
function stats(req, res) {
  const uid = req.user.id;

  // 2 queries instead of 7: mega-CTE returns all session data as JSON columns
  const row = stmts.statsMega.get({ uid });
  const weekly      = JSON.parse(row.weekly);
  const heatmap     = JSON.parse(row.heatmap);
  const bySubject   = JSON.parse(row.bySubject);
  const trend       = JSON.parse(row.trend).reverse();
  const totals      = JSON.parse(row.totals);
  const dayKeys     = new Set(JSON.parse(row.streakDays));
  const courseProgress = stmts.courseProgress.all({ uid });

  // Streak calculation (JS side, uses pre-fetched day set)
  let streak = 0;
  const now = new Date();
  for (let i = 0; i <= 90; i++) {
    const d = new Date(now);
    d.setDate(d.getDate() - i);
    const key = d.toISOString().slice(0, 10);
    if (dayKeys.has(key)) streak++;
    else if (i > 0) break;
  }

  res.json({
    weekly: weekly.map(r => ({ week: r.week, sessions: r.sessions, avgPct: Math.round(r.avg_pct || 0) })),
    heatmap: heatmap.map(r => ({ day: r.day, count: r.count })),
    bySubject: bySubject.map(r => ({
      slug: r.slug, name: r.name, sessions: r.sessions,
      avgPct: Math.round(r.avg_pct || 0),
      correct: r.total_correct, questions: r.total_questions,
    })),
    trend: trend.map(r => ({
      pct: r.total > 0 ? Math.round(r.score * 100 / r.total) : 0,
      date: r.finished_at, subject: r.subject_slug,
    })),
    streak,
    totals: {
      sessions: totals.sessions || 0,
      correct:  totals.correct  || 0,
      questions:totals.questions|| 0,
      avgPct:   Math.round(totals.avg_pct || 0),
    },
    courseProgress: courseProgress.map(r => ({
      id: r.id, title: r.title, emoji: r.cover_emoji,
      subjectSlug: r.subject_slug,
      done: r.done_lessons, total: r.total_lessons,
      pct: r.total_lessons > 0 ? Math.round(r.done_lessons * 100 / r.total_lessons) : 0,
    })),
  });
}

module.exports = { start, answer, finish, result, history, weakTopics, getSessionQuestions, stats };
