// Admin moderation dashboard (Phase 6). Read-only views over the bot's
// mod_actions log plus server-owned staff notes. Mounted behind the
// admin+moderator RBAC gate (see moderation.router.js). The only mutation here is
// adding a staff note; admin_only notes are further restricted to the admin role.
const moderation = require('../../../model/moderation/moderation.model')
const modNotes = require('../../../model/modNotes/modNotes.model')
const modNotesDb = require('../../../model/modNotes/modNotes.db')
const appeals = require('../../../model/appeals/appeals.model')
const { isTerminal, isAppealableType, reversalStatusFor } = require('../../../model/appeals/appeals.pure')
const botInternalClient = require('../../../utils/botInternalClient')
const activity = require('../../../model/activity/activity.model')

const log = require('../../../utils/logger')('moderation')

// The statuses the appeals queue can be filtered to. ?status=all expands to all
// of them; a specific ?status=<value> narrows to one; the default is the open
// set (pending + under_review) that still needs staff attention.
const APPEAL_STATUSES = ['pending', 'under_review', 'approved', 'denied', 'withdrawn']
const DEFAULT_APPEAL_STATUSES = ['pending', 'under_review']

const VALID_TYPES = new Set(['ban', 'kick', 'mute', 'warn'])
const MAX_LIMIT = 200
const DEFAULT_LIMIT = 50

// Parse ?limit/&offset the same way the activity log does: numeric, capped.
function pageParams(req) {
  const limit = Math.min(Number(req.query.limit) || DEFAULT_LIMIT, MAX_LIMIT)
  const offset = Number(req.query.offset) || 0
  return { limit, offset }
}

// Optional ?type filter — ignored unless it is a known action type.
function typeParam(req) {
  const t = req.query.type
  return VALID_TYPES.has(t) ? t : null
}

function isAdmin(req) {
  return req.user && req.user.role === 'admin'
}

async function getSummary(req, res) {
  try {
    return res.json(await moderation.summary())
  } catch (err) {
    log.error('summary failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getRecent(req, res) {
  try {
    const { limit, offset } = pageParams(req)
    return res.json(await moderation.recent({ type: typeParam(req), limit, offset }))
  } catch (err) {
    log.error('recent failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function search(req, res) {
  try {
    const term = (req.query.q || '').trim()
    if (!term) return res.json([])
    return res.json(await moderation.search(term, { limit: 20 }))
  } catch (err) {
    log.error('search failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Phase 6b event feeds ──────────────────────────────────────────────
const MEMBER_TYPES = new Set(['join', 'leave'])

async function getMembers(req, res) {
  try {
    const { limit, offset } = pageParams(req)
    const t = MEMBER_TYPES.has(req.query.type) ? req.query.type : null
    return res.json(await moderation.members({ type: t, limit, offset }))
  } catch (err) {
    log.error('members failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getFilterHits(req, res) {
  try {
    const { limit, offset } = pageParams(req)
    return res.json(await moderation.filterHits({ limit, offset }))
  } catch (err) {
    log.error('filterHits failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getSpamHits(req, res) {
  try {
    const { limit, offset } = pageParams(req)
    return res.json(await moderation.spamHits({ limit, offset }))
  } catch (err) {
    log.error('spamHits failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getUser(req, res) {
  try {
    const summary = await moderation.userSummary(req.params.discordId)
    const notesCount = await modNotesDb.countForUser(req.params.discordId, {
      includeAdminOnly: isAdmin(req),
    })
    return res.json({ ...summary, notes_count: notesCount })
  } catch (err) {
    log.error('getUser failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getUserActions(req, res) {
  try {
    const { limit, offset } = pageParams(req)
    return res.json(
      await moderation.userActions(req.params.discordId, { type: typeParam(req), limit, offset }),
    )
  } catch (err) {
    log.error('getUserActions failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getUserNotes(req, res) {
  try {
    const notes = await modNotes.listForUser(req.params.discordId, {
      includeAdminOnly: isAdmin(req),
    })
    return res.json(notes)
  } catch (err) {
    log.error('getUserNotes failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function addUserNote(req, res) {
  try {
    const visibility = req.body.visibility === 'admin_only' ? 'admin_only' : 'staff_only'
    // admin_only notes can carry sensitive judgement calls — restrict to admins.
    if (visibility === 'admin_only' && !isAdmin(req)) {
      return res.status(403).json({ message: 'Only admins can add admin-only notes' })
    }
    const note = await modNotes.add({
      discordUserId: req.params.discordId,
      author: req.user,
      body: req.body.body,
      visibility,
    })
    await activity.log({
      req,
      action: 'moderation.note.add',
      detail: { discordUserId: req.params.discordId, visibility },
    })
    return res.status(201).json(note)
  } catch (err) {
    log.error('addUserNote failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Phase 6c: appeals staff queue ─────────────────────────────────────
// The status filter for the queue: ?status=all → every status, ?status=<one> →
// just that one (if valid), otherwise the default open set.
function appealStatusFilter(req) {
  const s = req.query.status
  if (s === 'all') return APPEAL_STATUSES
  if (APPEAL_STATUSES.includes(s)) return [s]
  return DEFAULT_APPEAL_STATUSES
}

async function getAppeals(req, res) {
  try {
    const { limit, offset } = pageParams(req)
    const statuses = appealStatusFilter(req)
    return res.json(await appeals.queue({ statuses, limit, offset }))
  } catch (err) {
    log.error('getAppeals failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getAppeal(req, res) {
  try {
    const appeal = await appeals.getById(Number(req.params.id))
    if (!appeal) return res.status(404).json({ message: 'Appeal not found' })
    return res.json(appeal)
  } catch (err) {
    log.error('getAppeal failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// Claim a pending appeal → under_review, stamping the claiming staffer. Only a
// still-pending appeal can be claimed (a second claim, or claiming a resolved
// one, is a 409).
async function claimAppeal(req, res) {
  try {
    const appeal = await appeals.getById(Number(req.params.id))
    if (!appeal) return res.status(404).json({ message: 'Appeal not found' })
    if (appeal.status !== 'pending') {
      return res.status(409).json({ message: 'Appeal is not open for claiming' })
    }
    const updated = await appeals.claim(appeal.id, {
      handlerUserId: req.user.id,
      handlerTag: req.user.username,
    })
    await activity.log({
      req,
      action: 'moderation.appeal.claim',
      detail: { appealId: appeal.id, discordUserId: appeal.discord_user_id },
    })
    return res.json(updated)
  } catch (err) {
    log.error('claimAppeal failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// Resolve an appeal (approved | denied). On an APPROVED ban/mute we best-effort
// ask the bot to reverse the Discord action (unban / clear timeout). The bot
// being down never fails the resolution — we record reversal_status='failed'
// and still close the appeal. The response echoes the updated appeal plus a
// `reversal` object describing what was attempted.
async function resolveAppeal(req, res) {
  try {
    const status = req.body.status
    const staffResponse = req.body.staff_response ?? null

    const appeal = await appeals.getById(Number(req.params.id))
    if (!appeal) return res.status(404).json({ message: 'Appeal not found' })
    if (isTerminal(appeal.status)) {
      return res.status(409).json({ message: 'Appeal is already resolved' })
    }

    // Best-effort Discord reversal only for an approved, appealable action.
    const shouldReverse = status === 'approved' && isAppealableType(appeal.action_type)
    let botResult = null
    if (shouldReverse) {
      botResult = await botInternalClient.reverseModAction({
        discordUserId: appeal.discord_user_id,
        actionType: appeal.action_type,
        appealId: appeal.id,
      })
    }

    const reversalStatus = reversalStatusFor({
      status,
      actionType: appeal.action_type,
      botOk: botResult ? botResult.ok : false,
    })

    const updated = await appeals.resolve(appeal.id, {
      status,
      staffResponse,
      handlerUserId: req.user.id,
      handlerTag: req.user.username,
      reversalStatus,
    })

    await activity.log({
      req,
      action: 'moderation.appeal.resolve',
      detail: { appealId: appeal.id, status, reversalStatus },
    })

    // Describe the reversal so the UI can show "unban succeeded / failed / n/a".
    const reversal = {
      attempted: shouldReverse,
      ok: botResult ? botResult.ok : false,
      reversal_status: reversalStatus,
      bot_status: botResult ? botResult.status : null,
      error: botResult && !botResult.ok ? botResult.error || null : null,
    }

    return res.json({ ...updated, reversal })
  } catch (err) {
    log.error('resolveAppeal failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getUserAppeals(req, res) {
  try {
    return res.json(await appeals.listForDiscordUser(req.params.discordId))
  } catch (err) {
    log.error('getUserAppeals failed', { error: err.message })
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

module.exports = {
  getSummary,
  getRecent,
  search,
  getMembers,
  getFilterHits,
  getSpamHits,
  getUser,
  getUserActions,
  getUserNotes,
  addUserNote,
  getAppeals,
  getAppeal,
  claimAppeal,
  resolveAppeal,
  getUserAppeals,
}
