// ── Invite acceptance (public, token-gated) ────────────────────────────────
//
// The other end of the admin email-invite flow (admin/invites.controller). An
// invitee opens the tokened link, sees their pre-assigned email + role, and sets
// a username + password. Accepting creates their website user AT THE PRESET ROLE
// (bypassing the player_registration gate — the invite is its own authority) and
// logs them straight in. The optional "create game account" step afterwards reuses
// POST /player/shard/account (players only), so it isn't handled here.

const invites = require('../../../model/invites/invites.model')
const users = require('../../../model/users/users.model')
const activity = require('../../../model/activity/activity.model')
const usernamePolicy = require('../../../auth/usernamePolicy')
const { issueSession, HONEYPOT_FIELD } = require('./auth.controller')

const log = require('../../../utils/logger')('auth-invite')

// GET /auth/invite/:token — validate an invite and return what the accept form
// needs (email + role). 404 for anything not currently acceptable so we never
// distinguish "expired" from "revoked" from "never existed".
async function getInvite(req, res) {
  try {
    const row = await invites.findValidByToken(req.params.token)
    if (!row) return res.status(404).json({ message: 'This invitation is invalid or has expired.' })
    return res.json(invites.publicView(row))
  } catch (err) {
    log.error('getInvite', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// POST /auth/invite/:token/accept — create the user at the invite's role and log
// them in. Honeypot + validation mirror register; the invite replaces the
// registration-mode gate.
async function acceptInvite(req, res) {
  // Honeypot: a filled hidden field means a bot.
  if (req.body[HONEYPOT_FIELD]) {
    log.warn('honeypot invite-accept hit', { ip: req.ip })
    return res.status(400).json({ message: 'Registration failed.' })
  }
  try {
    const row = await invites.findValidByToken(req.params.token)
    if (!row) return res.status(404).json({ message: 'This invitation is invalid or has expired.' })

    const check = usernamePolicy.validateUsername(req.body.username)
    if (!check.ok) return res.status(400).json({ message: check.message })

    let user
    try {
      user = await users.createUser({
        username: check.name,
        password: req.body.password,
        email: row.email,
        role: row.role,
        emailVerified: true, // they proved control of the address by using the link
      })
    } catch (err) {
      if (users.isDuplicateUsername(err)) {
        return res.status(409).json({ message: 'That username is already taken.' })
      }
      throw err
    }

    // Consume the invite atomically. If we lost a double-accept race, roll back the
    // user we just created so a spent invite never yields two accounts.
    const won = await invites.accept(row.id, user.id)
    if (!won) {
      await users.remove(user.id).catch(() => {})
      return res.status(409).json({ message: 'This invitation has already been used.' })
    }

    await activity.log({ req, userId: user.id, action: 'invite.accept', detail: { inviteId: row.id, role: row.role } })
    log.info('invite accepted', { inviteId: row.id, userId: user.id, role: row.role, ip: req.ip })
    // New accounts never have TOTP yet — log straight in.
    return issueSession(req, res, user, 'local')
  } catch (err) {
    log.error('acceptInvite', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

module.exports = { getInvite, acceptInvite }
