const db = require('../db/db');
const { pushNotif } = require('../utils/notifications');
const { stripTags } = require('../utils/sanitize');
const { SESSION_MODES } = require('../constants');

const VALID_ASSIGN_MODES = SESSION_MODES;

/* ── Prepared statements (module-level to avoid re-parsing per request) ── */
const stmts = {
  getTestSubject:      db.prepare('SELECT subject_slug FROM tests WHERE id = ?'),
  getFileSubject:      db.prepare('SELECT subject_slug FROM files WHERE id = ?'),
  getClass:            db.prepare('SELECT id, teacher_id FROM classes WHERE id = ?'),
  getClassMembers:     db.prepare('SELECT user_id FROM class_members WHERE class_id = ?'),
  getSubjectBySlug:    db.prepare('SELECT id FROM subjects WHERE slug = ?'),
  countCompletedSess:  db.prepare(`
    SELECT COUNT(*) AS n FROM assignment_sessions ax
    JOIN test_sessions tx ON tx.id = ax.session_id AND tx.status = 'completed'
    WHERE ax.assignment_id = ? AND ax.user_id = ?
  `),
  getInProgressSess:   db.prepare(`
    SELECT ax.session_id FROM assignment_sessions ax
    JOIN test_sessions ts ON ts.id = ax.session_id AND ts.status = 'in_progress'
    WHERE ax.assignment_id = ? AND ax.user_id = ?
    ORDER BY ax.id DESC LIMIT 1
  `),
  insertSession:       db.prepare('INSERT INTO test_sessions (user_id, subject_id, mode, total) VALUES (?, ?, ?, ?)'),
  insertSessionQ:      db.prepare('INSERT INTO session_questions (session_id, question_id, order_index) VALUES (?, ?, ?)'),
  insertAssignSess:    db.prepare('INSERT INTO assignment_sessions (assignment_id, user_id, session_id, attempt_num) VALUES (?, ?, ?, ?)'),
  notifyClassMembers:  db.prepare('SELECT user_id FROM class_members WHERE class_id = ? AND user_id != ?'),
};

/* ════════════════════════════════════════════════════════════════════
   SHARED HELPERS for assignment creation
   Used by all 3 endpoints: createAssignment, bulkCreateAssignment,
   createDirectAssignment. Centralizes validation, resolution and INSERT.
   ════════════════════════════════════════════════════════════════════ */

/**
 * Validate fields and resolve foreign keys (test_id, file_id, textbook_slug).
 * Returns { ok: { ...fields }, error: null } or { ok: null, error: 'message' }.
 *
 * Doesn't touch the target (class/user) — that's the caller's responsibility.
 */
function _resolveAssignment(body) {
  const { title, topic_id, deadline, test_id, file_id, is_homework = 0,
          textbook_slug, textbook_paragraphs } = body || {};
  const mode         = body?.mode  || 'exam';
  const count        = Number(body?.count) || 25;
  const max_attempts = Math.max(0, Math.min(10, Number(body?.max_attempts) || 0));
  let   subject_slug = body?.subject_slug;

  if (!title?.trim())              return { error: 'title required' };
  if (!VALID_ASSIGN_MODES.has(mode)) return { error: 'mode must be exam, practice, repeat or ct' };
  if (!Number.isInteger(count) || count < 1 || count > 200)
    return { error: 'count must be an integer between 1 and 200' };
  if (deadline && isNaN(Date.parse(deadline)))
    return { error: 'deadline must be a valid date' };

  // Resolve test → subject
  if (test_id) {
    const t = stmts.getTestSubject.get(Number(test_id));
    if (!t) return { error: 'Test not found' };
    subject_slug = t.subject_slug;
  }

  // Resolve textbook → id + subject
  let textbook_id = null;
  if (textbook_slug) {
    const tb = db.prepare('SELECT id, subject FROM textbooks WHERE slug=? AND is_active=1').get(textbook_slug);
    if (!tb) return { error: 'Учебник не найден' };
    textbook_id = tb.id;
    if (!subject_slug) subject_slug = tb.subject;
  }

  // Resolve file → subject (fallback)
  if (file_id && !subject_slug) {
    const f = stmts.getFileSubject.get(Number(file_id));
    if (f?.subject_slug) subject_slug = f.subject_slug;
  }

  // Upload-only homework doesn't require subject
  if (!subject_slug && !is_homework) return { error: 'subject_slug required' };
  if (!subject_slug) subject_slug = 'other';

  return {
    ok: {
      title:        stripTags(title.trim()),
      subject_slug,
      mode,
      count,
      topic_id:     topic_id || null,
      deadline:     deadline || null,
      test_id:      test_id ? Number(test_id) : null,
      file_id:      file_id ? Number(file_id) : null,
      is_homework:  is_homework ? 1 : 0,
      max_attempts,
      textbook_id,
      textbook_paragraphs: textbook_paragraphs || null,
    },
  };
}

const _insertAssignmentStmt = db.prepare(`
  INSERT INTO assignments (
    class_id, user_id, title, subject_slug, mode, count, topic_id,
    deadline, created_by, test_id, file_id, is_homework, max_attempts,
    textbook_id, textbook_paragraphs
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);

/** Insert one assignments row. target is { class_id } or { user_id }. */
function _insertAssignment(target, fields, createdBy) {
  const r = _insertAssignmentStmt.run(
    target.class_id ?? null,
    target.user_id  ?? null,
    fields.title, fields.subject_slug, fields.mode, fields.count, fields.topic_id,
    fields.deadline, createdBy, fields.test_id, fields.file_id, fields.is_homework,
    fields.max_attempts, fields.textbook_id, fields.textbook_paragraphs,
  );
  return Number(r.lastInsertRowid);
}

/** Send "assignment created" notification to all recipients. */
function _notifyAssignment(target, title) {
  const msg = `Новое задание: «${title}»`;
  if (target.user_id) {
    pushNotif(target.user_id, 'assignment', `Для вас задание: «${title}»`, '/dashboard');
  } else if (target.class_id) {
    const members = stmts.getClassMembers.all(target.class_id);
    members.forEach(m => pushNotif(m.user_id, 'assignment', msg, '/dashboard'));
  }
}

/* ── POST /api/classes/:id/assignments ── create assignment for one class ── */
function createAssignment(req, res) {
  const cls = stmts.getClass.get(req.params.id);
  if (!cls) return res.status(404).json({ error: 'Class not found' });
  if (req.user.role !== 'admin' && cls.teacher_id !== req.user.id)
    return res.status(403).json({ error: 'Forbidden' });

  const { ok, error } = _resolveAssignment(req.body);
  if (error) return res.status(400).json({ error });

  const id = _insertAssignment({ class_id: cls.id }, ok, req.user.id);
  _notifyAssignment({ class_id: cls.id }, ok.title);
  res.status(201).json({ id });
}

/* ── PUT /api/assignments/:id ── update ───────────────────────────────── */
function updateAssignment(req, res) {
  const a = db.prepare(`
    SELECT a.id, COALESCE(c.teacher_id, a.created_by) AS teacher_id
    FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
  `).get(req.params.id);
  if (!a) return res.status(404).json({ error: 'Not found' });
  if (req.user.role !== 'admin' && a.teacher_id !== req.user.id)
    return res.status(403).json({ error: 'Forbidden' });

  const { title, mode, count, deadline } = req.body;
  let { subject_slug, test_id } = req.body;
  if (!title?.trim()) return res.status(400).json({ error: 'title required' });

  test_id = test_id ? Number(test_id) : null;

  if (test_id) {
    const t = db.prepare('SELECT subject_slug FROM tests WHERE id = ?').get(test_id);
    if (!t) return res.status(400).json({ error: 'Test not found' });
    subject_slug = t.subject_slug;
  }
  if (!subject_slug) return res.status(400).json({ error: 'subject_slug required' });

  db.prepare(`
    UPDATE assignments SET title = ?, subject_slug = ?, mode = ?, count = ?, deadline = ?, test_id = ? WHERE id = ?
  `).run(stripTags(title.trim()), subject_slug, mode || 'exam', Number(count) || 25, deadline || null, test_id, req.params.id);

  res.json({ ok: true });
}

/* ── DELETE /api/assignments/:id ──────────────────────────────────────── */
function deleteAssignment(req, res) {
  const a = db.prepare(`
    SELECT a.id, COALESCE(c.teacher_id, a.created_by) AS teacher_id
    FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
  `).get(req.params.id);
  if (!a) return res.status(404).json({ error: 'Not found' });
  if (req.user.role !== 'admin' && a.teacher_id !== req.user.id)
    return res.status(403).json({ error: 'Forbidden' });
  db.prepare('DELETE FROM assignments WHERE id = ?').run(req.params.id);
  res.json({ ok: true });
}

/* ── GET /api/assignments/teacher ── teacher: own created assignments ──── */
function teacherAssignments(req, res) {
  const isAdmin = req.user.role === 'admin';

  // Админ видит все задания без ограничений
  if (isAdmin) {
    const rows = db.prepare(`
      SELECT a.id, a.title, a.subject_slug, a.mode, a.count, a.deadline, a.created_at,
             a.test_id, t.title AS test_title,
             a.file_id, f.title AS file_title,
             a.user_id AS target_user_id, tu.name AS target_user_name,
             COALESCE(c.name, 'Личное задание') AS class_name,
             COALESCE(c.id, 0) AS class_id,
             CASE WHEN a.user_id IS NOT NULL THEN 1 ELSE COUNT(DISTINCT cm.user_id) END AS total_members,
             COUNT(DISTINCT CASE WHEN ts.status = 'completed' THEN ases.user_id END) AS completed_count
      FROM assignments a
      LEFT JOIN classes c  ON c.id = a.class_id AND a.class_id IS NOT NULL
      LEFT JOIN users tu   ON tu.id = a.user_id
      LEFT JOIN tests t    ON t.id = a.test_id
      LEFT JOIN files f    ON f.id = a.file_id
      LEFT JOIN class_members cm   ON cm.class_id = c.id
      LEFT JOIN assignment_sessions ases ON ases.assignment_id = a.id
      LEFT JOIN test_sessions ts   ON ts.id = ases.session_id
      GROUP BY a.id
      ORDER BY a.created_at DESC
    `).all();
    return res.json(rows);
  }

  // Учитель видит только свои задания:
  // - классовые (любые свои)
  // - личные — только для учеников из своих классов
  const rows = db.prepare(`
    SELECT a.id, a.title, a.subject_slug, a.mode, a.count, a.deadline, a.created_at,
           a.test_id, t.title AS test_title,
           a.file_id, f.title AS file_title,
           a.user_id AS target_user_id, tu.name AS target_user_name,
           COALESCE(c.name, 'Личное задание') AS class_name,
           COALESCE(c.id, 0) AS class_id,
           CASE WHEN a.user_id IS NOT NULL THEN 1 ELSE COUNT(DISTINCT cm.user_id) END AS total_members,
           COUNT(DISTINCT CASE WHEN ts.status = 'completed' THEN ases.user_id END) AS completed_count
    FROM assignments a
    LEFT JOIN classes c  ON c.id = a.class_id AND a.class_id IS NOT NULL
    LEFT JOIN users tu   ON tu.id = a.user_id
    LEFT JOIN tests t    ON t.id = a.test_id
    LEFT JOIN files f    ON f.id = a.file_id
    LEFT JOIN class_members cm   ON cm.class_id = c.id
    LEFT JOIN assignment_sessions ases ON ases.assignment_id = a.id
    LEFT JOIN test_sessions ts   ON ts.id = ases.session_id
    WHERE a.created_by = ?
      AND (
        a.class_id IS NOT NULL
        OR (
          a.user_id IS NOT NULL
          AND EXISTS (
            SELECT 1 FROM class_members cm2
            JOIN classes c2 ON c2.id = cm2.class_id
            WHERE cm2.user_id = a.user_id AND c2.teacher_id = ?
          )
        )
      )
    GROUP BY a.id
    ORDER BY a.created_at DESC
  `).all(req.user.id, req.user.id);
  res.json(rows);
}

/* ── GET /api/assignments/my ── student: all pending/done assignments ──── */
function myAssignments(req, res) {
  const uid = req.user.id;
  const rows = db.prepare(`
    SELECT * FROM (
      SELECT a.id, a.title, a.subject_slug, a.mode, a.count, a.deadline, a.created_at,
             a.file_id, f.title AS file_title,
             a.textbook_id, a.textbook_paragraphs,
             tb.slug AS textbook_slug, tb.title AS textbook_title, tb.color AS textbook_color, tb.para_count AS textbook_para_count,
             tp.paragraphs_read AS textbook_read,
             c.name AS class_name, c.id AS class_id, u.name AS teacher_name,
             latest.session_id,
             ts.score, ts.total, ts.status AS session_status,
             ROUND(CAST(ts.score AS REAL) / ts.total * 100) AS percent,
             CASE WHEN latest.session_id IS NULL THEN 0 ELSE 1 END AS done,
             ac.completed_at AS completed_at,
             a.is_homework, a.max_attempts,
             (SELECT COUNT(*) FROM assignment_sessions ax
              JOIN test_sessions tx ON tx.id = ax.session_id AND tx.status = 'completed'
              WHERE ax.assignment_id = a.id AND ax.user_id = cm.user_id) AS attempts_used
      FROM class_members cm
      JOIN classes c ON c.id = cm.class_id
      JOIN users u ON u.id = c.teacher_id
      JOIN assignments a ON a.class_id = c.id AND a.user_id IS NULL
      LEFT JOIN files f ON f.id = a.file_id
      LEFT JOIN textbooks tb ON tb.id = a.textbook_id
      LEFT JOIN textbook_progress tp ON tp.user_id = cm.user_id AND tp.textbook_id = a.textbook_id
      LEFT JOIN assignment_completion ac ON ac.assignment_id = a.id AND ac.user_id = cm.user_id
      LEFT JOIN assignment_sessions latest ON latest.assignment_id = a.id AND latest.user_id = cm.user_id
        AND latest.id = (SELECT MAX(id) FROM assignment_sessions WHERE assignment_id = a.id AND user_id = cm.user_id)
      LEFT JOIN test_sessions ts ON ts.id = latest.session_id
      WHERE cm.user_id = ?
      UNION ALL
      SELECT a.id, a.title, a.subject_slug, a.mode, a.count, a.deadline, a.created_at,
             a.file_id, f.title AS file_title,
             a.textbook_id, a.textbook_paragraphs,
             tb.slug AS textbook_slug, tb.title AS textbook_title, tb.color AS textbook_color, tb.para_count AS textbook_para_count,
             tp.paragraphs_read AS textbook_read,
             'Личное задание' AS class_name, 0 AS class_id, u.name AS teacher_name,
             latest.session_id,
             ts.score, ts.total, ts.status AS session_status,
             ROUND(CAST(ts.score AS REAL) / ts.total * 100) AS percent,
             CASE WHEN latest.session_id IS NULL THEN 0 ELSE 1 END AS done,
             ac.completed_at AS completed_at,
             a.is_homework, a.max_attempts,
             (SELECT COUNT(*) FROM assignment_sessions ax
              JOIN test_sessions tx ON tx.id = ax.session_id AND tx.status = 'completed'
              WHERE ax.assignment_id = a.id AND ax.user_id = ?) AS attempts_used
      FROM assignments a
      JOIN users u ON u.id = a.created_by
      LEFT JOIN files f ON f.id = a.file_id
      LEFT JOIN textbooks tb ON tb.id = a.textbook_id
      LEFT JOIN textbook_progress tp ON tp.user_id = ? AND tp.textbook_id = a.textbook_id
      LEFT JOIN assignment_completion ac ON ac.assignment_id = a.id AND ac.user_id = ?
      LEFT JOIN assignment_sessions latest ON latest.assignment_id = a.id AND latest.user_id = ?
        AND latest.id = (SELECT MAX(id) FROM assignment_sessions WHERE assignment_id = a.id AND user_id = ?)
      LEFT JOIN test_sessions ts ON ts.id = latest.session_id
      WHERE a.user_id = ?
    ) ORDER BY done ASC, deadline ASC, created_at DESC
  `).all(uid, uid, uid, uid, uid, uid, uid);

  // Post-process: compute textbook reading completion from required vs read paragraphs
  for (const r of rows) {
    if (r.textbook_id) {
      const required = parseTextbookParas(r.textbook_paragraphs, r.textbook_para_count);
      let read = [];
      try { read = JSON.parse(r.textbook_read || '[]'); } catch {}
      const readKeys = new Set(read);
      const requiredKeys = required.map(n => 'p' + n);
      const readCount = requiredKeys.filter(k => readKeys.has(k)).length;
      r.textbook_required_count = requiredKeys.length;
      r.textbook_read_count     = readCount;
      r.textbook_all_read       = requiredKeys.length > 0 && readCount === requiredKeys.length;
      if (r.textbook_all_read || r.completed_at) r.done = 1;
    }
    // Strip raw paragraphs_read JSON from response (not needed by client)
    delete r.textbook_read;
  }

  res.json(rows);
}

/* Parse "1-5", "1,3,7", "1-3,5,7-9" → [1,2,3,4,5] or [1,3,7] etc.
   If empty/null, returns [1..fallback] (the whole book). */
function parseTextbookParas(spec, fallback) {
  if (!spec || !spec.trim()) {
    return Array.from({ length: fallback || 0 }, (_, i) => i + 1);
  }
  const out = new Set();
  for (const chunk of spec.split(',')) {
    const part = chunk.trim();
    if (!part) continue;
    const dash = part.match(/^(\d+)\s*[-–]\s*(\d+)$/);
    if (dash) {
      const a = Number(dash[1]), b = Number(dash[2]);
      for (let i = Math.min(a, b); i <= Math.max(a, b); i++) out.add(i);
    } else if (/^\d+$/.test(part)) {
      out.add(Number(part));
    }
  }
  return [...out].sort((a, b) => a - b);
}

/* ── POST /api/assignments/:id/start ── student starts session ─────────── */
function startAssignment(req, res) {
  const uid = req.user.id;
  const assignment = db.prepare(`
    SELECT a.* FROM assignments a
    WHERE a.id = ?
      AND (
        (a.class_id IS NOT NULL AND EXISTS (
          SELECT 1 FROM class_members WHERE class_id = a.class_id AND user_id = ?
        ))
        OR a.user_id = ?
      )
  `).get(req.params.id, uid, uid);
  if (!assignment) return res.status(404).json({ error: 'Assignment not found or not your class' });

  // Deadline check: reject if deadline has already passed
  if (assignment.deadline) {
    const dl = new Date(assignment.deadline.includes('T') ? assignment.deadline : assignment.deadline.replace(' ', 'T') + 'Z');
    if (dl < new Date()) return res.status(403).json({ error: 'Срок выполнения задания истёк' });
  }

  // File-only assignment: just return the download URL, no session needed
  if (assignment.file_id && !assignment.test_id) {
    return res.json({ is_file: true, file_id: assignment.file_id });
  }

  // assignment mode → session mode mapping
  const SESSION_MODE = { exam: 'exam', practice: 'practice', repeat: 'practice', ct: 'exam' };
  const sessionMode  = SESSION_MODE[assignment.mode] || 'exam';

  // Count completed attempts for this student
  const completedCount = stmts.countCompletedSess.get(req.params.id, uid).n;

  // Check attempt limit
  const maxAttempts = assignment.max_attempts || 0;
  if (maxAttempts > 0 && completedCount >= maxAttempts) {
    return res.status(403).json({
      error: 'Исчерпан лимит попыток',
      attempts_used: completedCount,
      max_attempts: maxAttempts,
    });
  }

  // Check for an existing in-progress session
  const inProgress = stmts.getInProgressSess.get(req.params.id, uid);

  if (inProgress?.session_id) {
    return res.json({
      session_id: inProgress.session_id,
      already_started: true,
      status: 'in_progress',
      assignment_mode: assignment.mode,
      attempts_used: completedCount,
      max_attempts: maxAttempts,
    });
  }

  const subject = stmts.getSubjectBySlug.get(assignment.subject_slug);
  if (!subject) return res.status(400).json({ error: 'Invalid subject' });

  let questionIds;

  if (assignment.test_id) {
    // Use exact questions from the pre-made test (in defined order)
    questionIds = db.prepare(
      'SELECT question_id FROM test_questions WHERE test_id = ? ORDER BY order_index'
    ).all(assignment.test_id).map(r => r.question_id);
  } else if (assignment.mode === 'ct') {
    // CT mode: Part A (single/true_false) first, then Part B (multi/short_answer)
    const baseWhere = `subject_id = ?${assignment.topic_id ? ' AND topic_id = ?' : ''}`;
    const baseArgs  = assignment.topic_id ? [subject.id, assignment.topic_id] : [subject.id];
    const half  = Math.ceil(assignment.count / 2);
    const partA = db.prepare(`SELECT id FROM questions WHERE ${baseWhere} AND type IN ('single','true_false') ORDER BY RANDOM() LIMIT ?`)
      .all(...baseArgs, half).map(q => q.id);
    const partB = db.prepare(`SELECT id FROM questions WHERE ${baseWhere} AND type IN ('multi','short_answer') ORDER BY RANDOM() LIMIT ?`)
      .all(...baseArgs, assignment.count - partA.length).map(q => q.id);
    const got   = partA.length + partB.length;
    const usedIds = [...partA, ...partB];
    const extra = (got < assignment.count && usedIds.length > 0)
      ? db.prepare(`SELECT id FROM questions WHERE ${baseWhere} AND id NOT IN (${usedIds.map(() => '?').join(',')}) ORDER BY RANDOM() LIMIT ?`)
          .all(...baseArgs, ...usedIds, assignment.count - got).map(q => q.id)
      : [];
    questionIds = [...partA, ...partB, ...extra];
  } else {
    const baseWhere = `subject_id = ?${assignment.topic_id ? ' AND topic_id = ?' : ''}`;
    const baseArgs  = assignment.topic_id ? [subject.id, assignment.topic_id] : [subject.id];
    questionIds = db.prepare(`SELECT id FROM questions WHERE ${baseWhere} ORDER BY RANDOM() LIMIT ?`)
      .all(...baseArgs, assignment.count).map(q => q.id);
  }

  if (!questionIds.length) return res.status(400).json({ error: 'No questions available' });

  const session_id = db.transaction(() => {
    const { lastInsertRowid: sid } = stmts.insertSession.run(uid, subject.id, sessionMode, questionIds.length);
    questionIds.forEach((qid, i) => stmts.insertSessionQ.run(sid, qid, i));
    stmts.insertAssignSess.run(req.params.id, uid, sid, completedCount + 1);
    return sid;
  })();

  res.json({
    session_id,
    assignment_mode: assignment.mode,
    attempt_num: completedCount + 1,
    attempts_used: completedCount,
    max_attempts: maxAttempts,
  });
}

/* ── GET /api/assignments/:id/results ── teacher view ──────────────────── */
function assignmentResults(req, res) {
  const a = db.prepare(`
    SELECT a.*, COALESCE(c.teacher_id, a.created_by) AS teacher_id, c.name AS class_name
    FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
  `).get(req.params.id);
  if (!a) return res.status(404).json({ error: 'Not found' });
  if (req.user.role !== 'admin' && a.teacher_id !== req.user.id)
    return res.status(403).json({ error: 'Forbidden' });

  let results;
  if (a.user_id) {
    // Direct assignment: single student — pick best attempt
    results = db.prepare(`
      SELECT u.id, u.name, u.email,
             best.session_id,
             best.score, best.total, best.session_status, best.finished_at,
             best.percent,
             best.attempts_used
      FROM users u
      LEFT JOIN (
        SELECT ases.user_id,
               ases.session_id,
               ts.score, ts.total, ts.status AS session_status, ts.finished_at,
               ROUND(CAST(ts.score AS REAL) / ts.total * 100) AS percent,
               COUNT(*) OVER (PARTITION BY ases.assignment_id, ases.user_id) AS attempts_used,
               ROW_NUMBER() OVER (PARTITION BY ases.user_id ORDER BY ts.score DESC, ts.finished_at DESC) AS rn
        FROM assignment_sessions ases
        JOIN test_sessions ts ON ts.id = ases.session_id
        WHERE ases.assignment_id = ?
      ) best ON best.user_id = u.id AND best.rn = 1
      WHERE u.id = ?
    `).all(req.params.id, a.user_id);
  } else {
    results = db.prepare(`
      SELECT u.id, u.name, u.email,
             best.session_id,
             best.score, best.total, best.session_status, best.finished_at,
             best.percent,
             best.attempts_used
      FROM class_members cm
      JOIN users u ON u.id = cm.user_id
      LEFT JOIN (
        SELECT ases.user_id,
               ases.session_id,
               ts.score, ts.total, ts.status AS session_status, ts.finished_at,
               ROUND(CAST(ts.score AS REAL) / ts.total * 100) AS percent,
               COUNT(*) OVER (PARTITION BY ases.assignment_id, ases.user_id) AS attempts_used,
               ROW_NUMBER() OVER (PARTITION BY ases.user_id ORDER BY ts.score DESC, ts.finished_at DESC) AS rn
        FROM assignment_sessions ases
        JOIN test_sessions ts ON ts.id = ases.session_id
        WHERE ases.assignment_id = ?
      ) best ON best.user_id = cm.user_id AND best.rn = 1
      WHERE cm.class_id = ?
      ORDER BY
        CASE WHEN best.percent IS NULL THEN 1 ELSE 0 END,
        best.percent DESC, u.name
    `).all(req.params.id, a.class_id);
  }

  res.json({ assignment: a, results });
}

/* ── GET /api/assignments/:id/question-stats ── per-question error rates ── */
function assignmentQuestionStats(req, res) {
  const a = db.prepare(`
    SELECT a.id, COALESCE(c.teacher_id, a.created_by) AS teacher_id, a.class_id, a.user_id
    FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
  `).get(req.params.id);
  if (!a) return res.status(404).json({ error: 'Not found' });
  if (req.user.role !== 'admin' && a.teacher_id !== req.user.id)
    return res.status(403).json({ error: 'Forbidden' });

  // All completed sessions for this assignment
  const sessions = db.prepare(`
    SELECT ases.session_id FROM assignment_sessions ases
    JOIN test_sessions ts ON ts.id = ases.session_id
    WHERE ases.assignment_id = ? AND ts.status = 'completed'
  `).all(req.params.id);

  if (!sessions.length) return res.json({ stats: [] });

  const sessionIds = sessions.map(s => s.session_id);
  const placeholders = sessionIds.map(() => '?').join(',');

  const rows = db.prepare(`
    SELECT q.id AS question_id,
           q.text AS question_text,
           q.type,
           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 questions q ON q.id = ua.question_id
    WHERE ua.session_id IN (${placeholders})
    GROUP BY ua.question_id
    ORDER BY error_pct DESC, wrong DESC
  `).all(...sessionIds);

  res.json({ stats: rows, session_count: sessionIds.length });
}

/* ── POST /api/assignments ── direct assignment to a single student ──────── */
function createDirectAssignment(req, res) {
  const { student_email, student_id, is_homework = 1 } = req.body;

  // Resolve student by id or email
  let student;
  if (student_id) {
    student = db.prepare("SELECT id, name FROM users WHERE id = ? AND role IN ('student','free_student')").get(Number(student_id));
    if (!student) return res.status(404).json({ error: 'Ученик не найден' });
  } else {
    if (!student_email?.trim()) return res.status(400).json({ error: 'student_email required' });
    student = db.prepare("SELECT id, name FROM users WHERE email = ? AND role IN ('student','free_student')")
      .get(student_email.trim().toLowerCase());
    if (!student) return res.status(404).json({ error: 'Ученик с таким email не найден' });
  }

  // Authorization: teacher can assign only to students in their classes OR in their «Мои ученики»
  if (req.user.role === 'teacher') {
    const inClass = db.prepare(`
      SELECT 1 FROM class_members cm
      JOIN classes c ON c.id = cm.class_id
      WHERE cm.user_id = ? AND c.teacher_id = ?
    `).get(student.id, req.user.id);
    const linked = inClass ? null : db.prepare(
      'SELECT 1 FROM teacher_students WHERE teacher_id=? AND student_id=?'
    ).get(req.user.id, student.id);
    if (!inClass && !linked) {
      return res.status(403).json({
        error: 'Ученик не входит в ваши классы и не добавлен в «Мои ученики». Добавьте его на странице «Мои ученики».',
      });
    }
  }

  // is_homework defaults to 1 for direct (vs 0 for class/bulk)
  const { ok, error } = _resolveAssignment({ is_homework, ...req.body });
  if (error) return res.status(400).json({ error });

  const id = _insertAssignment({ user_id: student.id }, ok, req.user.id);
  _notifyAssignment({ user_id: student.id }, ok.title);
  res.status(201).json({ id });
}

/* ── GET /api/assignments/:id/sessions/:session_id/review ── teacher view ── */
function assignmentSessionReview(req, res) {
  const assignmentId = Number(req.params.id);
  const sessionId    = Number(req.params.session_id);

  // Verify assignment ownership
  const a = db.prepare(`
    SELECT a.id, COALESCE(c.teacher_id, a.created_by) AS teacher_id
    FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
  `).get(assignmentId);
  if (!a) return res.status(404).json({ error: 'Assignment not found' });
  if (req.user.role !== 'admin' && a.teacher_id !== req.user.id)
    return res.status(403).json({ error: 'Forbidden' });

  // Verify session is linked to this assignment
  const link = db.prepare(
    'SELECT 1 FROM assignment_sessions WHERE assignment_id = ? AND session_id = ?'
  ).get(assignmentId, sessionId);
  if (!link) return res.status(404).json({ error: 'Session not linked to this assignment' });

  const session = db.prepare('SELECT score, total, status FROM test_sessions WHERE id = ?').get(sessionId);
  if (!session) return res.status(404).json({ error: 'Session not found' });

  // Build per-question review — batched
  const questionIds = db.prepare(
    'SELECT question_id FROM session_questions WHERE session_id = ? ORDER BY order_index'
  ).all(sessionId).map(r => r.question_id);

  if (!questionIds.length) return res.json({ session_id: sessionId, score: session.score, total: session.total, review: [] });

  const qPh = questionIds.map(() => '?').join(',');
  const questions = db.prepare(`SELECT id, text, type, explanation, correct_text FROM questions WHERE id IN (${qPh})`).all(...questionIds);
  const qMap = {};
  for (const q of questions) qMap[q.id] = q;

  const allOptions = db.prepare(`SELECT question_id, id, text, is_correct, match_pair FROM options WHERE question_id IN (${qPh}) ORDER BY order_index`).all(...questionIds);
  const optMap = {};
  for (const o of allOptions) { if (!optMap[o.question_id]) optMap[o.question_id] = []; optMap[o.question_id].push(o); }

  const allAnswers = db.prepare(`SELECT question_id, chosen_option_id, answer_text, is_correct FROM user_answers WHERE session_id = ? AND question_id IN (${qPh})`).all(sessionId, ...questionIds);
  const ansMap = {};
  for (const a of allAnswers) ansMap[a.question_id] = a;

  const review = questionIds.map(qid => {
    const q = qMap[qid];
    if (!q) return null;
    q.options = optMap[qid] || [];
    const ua = ansMap[qid];
    q.chosen_option_id = ua?.chosen_option_id ?? null;
    q.answer_text      = ua?.answer_text ?? null;
    q.is_correct       = ua ? ua.is_correct === 1 : null;
    return q;
  }).filter(Boolean);

  res.json({ session_id: sessionId, score: session.score, total: session.total, review });
}

/* ── GET /api/assignments/templates ── list my templates ───────────────── */
function listTemplates(req, res) {
  const rows = db.prepare(`
    SELECT id, label, subject_slug, mode, count, topic_id, test_id, file_id, is_homework, created_at
    FROM assignment_templates WHERE created_by = ? ORDER BY created_at DESC
  `).all(req.user.id);
  res.json(rows);
}

/* ── POST /api/assignments/templates ── save template ──────────────────── */
function saveTemplate(req, res) {
  const { label, subject_slug, mode = 'exam', count = 25, topic_id, test_id, file_id, is_homework = 0 } = req.body;
  if (!label?.trim()) return res.status(400).json({ error: 'label required' });
  if (!subject_slug)  return res.status(400).json({ error: 'subject_slug required' });
  const r = db.prepare(`
    INSERT INTO assignment_templates (created_by, label, subject_slug, mode, count, topic_id, test_id, file_id, is_homework)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(req.user.id, label.trim(), subject_slug, mode, Number(count), topic_id || null, test_id || null, file_id || null, is_homework ? 1 : 0);
  res.status(201).json({ id: r.lastInsertRowid });
}

/* ── DELETE /api/assignments/templates/:id ─────────────────────────────── */
function deleteTemplate(req, res) {
  db.prepare('DELETE FROM assignment_templates WHERE id = ? AND created_by = ?')
    .run(req.params.id, req.user.id);
  res.json({ ok: true });
}

/* ── POST /api/assignments/bulk ── assign to multiple classes at once ───── */
function bulkCreateAssignment(req, res) {
  const { class_ids } = req.body;
  if (!Array.isArray(class_ids) || !class_ids.length)
    return res.status(400).json({ error: 'class_ids[] required' });

  const { ok, error } = _resolveAssignment(req.body);
  if (error) return res.status(400).json({ error });

  // Authorize each class once, then INSERT + notify in a single transaction
  const created = db.transaction(() => {
    const ids = [];
    for (const class_id of class_ids) {
      const cls = stmts.getClass.get(Number(class_id));
      if (!cls) continue;
      if (req.user.role !== 'admin' && cls.teacher_id !== req.user.id) continue;
      const id = _insertAssignment({ class_id: cls.id }, ok, req.user.id);
      ids.push(id);
      _notifyAssignment({ class_id: cls.id }, ok.title);
    }
    return ids;
  })();

  res.status(201).json({ created, count: created.length });
}

module.exports = {
  VALID_ASSIGN_MODES,
  createAssignment,
  updateAssignment,
  deleteAssignment,
  teacherAssignments,
  myAssignments,
  startAssignment,
  assignmentResults,
  assignmentQuestionStats,
  createDirectAssignment,
  assignmentSessionReview,
  listTemplates,
  saveTemplate,
  deleteTemplate,
  bulkCreateAssignment,
};
