// Data-access for the server-owned `appeals` table (Phase 6c). Mirrors the
// modNotes/moderation split: this module is the only place that touches the
// table's SQL. Reads LEFT JOIN the bot-owned mod_actions row (no hard FK — the
// join is by the plain mod_action_id column) to surface the original action's
// target/reason/created_at, and LEFT JOIN users to surface the submitter's
// username. All writes belong to the site (the bot never touches this table).
const { query } = require('../../utils/db')

// Shared SELECT for a single appeal enriched with the originating action + the
// submitting account. ma.* columns are null when the mod_action was purged.
const APPEAL_SELECT = `
  SELECT a.id, a.mod_action_id, a.discord_user_id, a.action_type, a.user_id,
         a.status, a.submitted_text, a.staff_response,
         a.handled_by_user_id, a.handled_by_tag, a.reversal_status,
         a.submitted_at, a.resolved_at,
         ma.target_tag        AS action_target_tag,
         ma.reason            AS action_reason,
         ma.created_at        AS action_created_at,
         ma.duration_seconds  AS action_duration_seconds,
         submitter.username   AS submitter_username
    FROM appeals a
    LEFT JOIN mod_actions ma ON ma.id = a.mod_action_id
    LEFT JOIN users submitter ON submitter.id = a.user_id`

async function insert({ modActionId, discordUserId, actionType, userId = null, submittedText }) {
  const res = await query(
    `INSERT INTO appeals (mod_action_id, discord_user_id, action_type, user_id, submitted_text)
     VALUES (?, ?, ?, ?, ?)`,
    [modActionId, discordUserId, actionType, userId, submittedText],
  )
  return res.insertId
}

async function getById(id) {
  const rows = await query(`${APPEAL_SELECT} WHERE a.id = ? LIMIT 1`, [id])
  return rows[0] || null
}

// The caller's own appeals, newest first (My Appeals page).
async function listForUser(userId) {
  return query(`${APPEAL_SELECT} WHERE a.user_id = ? ORDER BY a.id DESC`, [userId])
}

// All appeals for a Discord id (admin per-user view), newest first.
async function listForDiscordUser(discordUserId) {
  return query(`${APPEAL_SELECT} WHERE a.discord_user_id = ? ORDER BY a.id DESC`, [discordUserId])
}

// The staff queue: filtered to a set of statuses (array), newest first, paged.
// An empty `statuses` returns nothing rather than the whole table.
async function listQueue({ statuses = [], limit = 50, offset = 0 } = {}) {
  if (!statuses.length) return []
  const placeholders = statuses.map(() => '?').join(', ')
  return query(
    `${APPEAL_SELECT} WHERE a.status IN (${placeholders})
      ORDER BY a.submitted_at ASC, a.id ASC
      LIMIT ? OFFSET ?`,
    [...statuses, limit, offset],
  )
}

// The active (pending/under_review) appeal for a mod_action, or null. Used to
// enforce one-active-appeal-per-action.
async function activeForAction(modActionId) {
  const rows = await query(
    `SELECT id, status FROM appeals
      WHERE mod_action_id = ? AND status IN ('pending','under_review')
      LIMIT 1`,
    [modActionId],
  )
  return rows[0] || null
}

// The caller's ban/mute mod_actions that have NO active appeal — the set of
// actions the player is allowed to open an appeal against. Left-anti-join
// against active appeals for the same action id.
async function eligibleActions(discordUserId) {
  return query(
    `SELECT ma.id, ma.action_type, ma.target_tag, ma.reason,
            ma.duration_seconds, ma.created_at
       FROM mod_actions ma
       LEFT JOIN appeals a
         ON a.mod_action_id = ma.id AND a.status IN ('pending','under_review')
      WHERE ma.target_user_id = ?
        AND ma.action_type IN ('ban','mute')
        AND a.id IS NULL
      ORDER BY ma.created_at DESC`,
    [discordUserId],
  )
}

// Read a single bot-owned mod_action by id, for submit-time validation (does it
// exist? is it the caller's? is it appealable?). Read-only — the site never
// writes mod_actions. Returns null when the id is unknown.
async function getModAction(modActionId) {
  const rows = await query(
    `SELECT id, action_type, target_user_id, target_tag, reason, duration_seconds, created_at
       FROM mod_actions WHERE id = ? LIMIT 1`,
    [modActionId],
  )
  return rows[0] || null
}

// pending -> under_review, stamping the claiming staffer.
async function setUnderReview(id, { handlerUserId, handlerTag }) {
  await query(
    `UPDATE appeals
        SET status = 'under_review', handled_by_user_id = ?, handled_by_tag = ?
      WHERE id = ?`,
    [handlerUserId, handlerTag, id],
  )
}

// Resolve to a terminal status (approved/denied), recording the staff response,
// handler, reversal outcome, and resolution timestamp.
async function resolve(id, { status, staffResponse, handlerUserId, handlerTag, reversalStatus }) {
  await query(
    `UPDATE appeals
        SET status = ?, staff_response = ?, handled_by_user_id = ?, handled_by_tag = ?,
            reversal_status = ?, resolved_at = NOW()
      WHERE id = ?`,
    [status, staffResponse ?? null, handlerUserId, handlerTag, reversalStatus, id],
  )
}

// Straight status flip (used for withdraw). Stamps resolved_at when moving to a
// terminal status so the row shows when it closed.
async function setStatus(id, status) {
  await query(
    `UPDATE appeals
        SET status = ?,
            resolved_at = CASE WHEN ? IN ('approved','denied','withdrawn') THEN NOW() ELSE resolved_at END
      WHERE id = ?`,
    [status, status, id],
  )
}

// Count of appeals grouped by status, for the queue's tab badges.
async function countByStatus() {
  return query('SELECT status, COUNT(*) AS c FROM appeals GROUP BY status')
}

module.exports = {
  insert,
  getById,
  listForUser,
  listForDiscordUser,
  listQueue,
  activeForAction,
  eligibleActions,
  getModAction,
  setUnderReview,
  resolve,
  setStatus,
  countByStatus,
}
