const { query } = require('../../utils/db')

const PUBLIC_COLS =
  'id, username, role, status, email, email_verified, totp_enabled, created_at, last_login_at'

// passwordHash may be null (SSO-provisioned players who have not set one yet).
// email/status/emailVerified are optional so existing admin-create callers are
// unaffected.
async function insertUser({
  username,
  passwordHash = null,
  role = 'admin',
  email = null,
  status = 'active',
  emailVerified = false,
}) {
  const res = await query(
    'INSERT INTO users (username, password_hash, role, email, status, email_verified) VALUES (?, ?, ?, ?, ?, ?)',
    [username, passwordHash, role, email, status, emailVerified ? 1 : 0],
  )
  return res.insertId
}

async function findByUsername(username) {
  const rows = await query('SELECT * FROM users WHERE username = ? LIMIT 1', [username])
  return rows[0] || null
}

async function findById(id) {
  const rows = await query('SELECT * FROM users WHERE id = ? LIMIT 1', [id])
  return rows[0] || null
}

// All ACTIVE accounts on an email address. Email is intentionally non-unique
// (SSO emails may repeat), so a reset request can legitimately match several
// accounts; the caller issues one reset link per row. Case-insensitive to match
// however the address was stored. Excludes disabled/banned accounts.
async function findActiveByEmail(email) {
  return query(
    "SELECT * FROM users WHERE email = ? AND status = 'active'",
    [email],
  )
}

async function listUsers() {
  return query(`SELECT ${PUBLIC_COLS} FROM users ORDER BY id ASC`)
}

async function updateUser(id, fields) {
  const cols = []
  const params = []
  for (const [key, val] of Object.entries(fields)) {
    cols.push(`${key} = ?`)
    params.push(val)
  }
  if (cols.length === 0) return
  params.push(id)
  await query(`UPDATE users SET ${cols.join(', ')} WHERE id = ?`, params)
}

async function deleteUser(id) {
  return query('DELETE FROM users WHERE id = ?', [id])
}

async function countUsers() {
  const rows = await query('SELECT COUNT(*) AS c FROM users')
  return Number(rows[0].c)
}

async function countAdmins() {
  const rows = await query("SELECT COUNT(*) AS c FROM users WHERE role = 'admin'")
  return Number(rows[0].c)
}

async function touchLastLogin(id, ip = null) {
  return query('UPDATE users SET last_login_at = NOW(), last_login_ip = ? WHERE id = ?', [ip, id])
}

// Move the "tokens valid after" cutoff to now, invalidating every session token
// issued before this instant (password change / log out everywhere). requireAuth
// compares each session's issued-at against this column.
async function bumpTokensValidAfter(id) {
  return query('UPDATE users SET tokens_valid_after = NOW() WHERE id = ?', [id])
}

// Set the cutoff to an explicit instant. Used when re-issuing the caller's own
// session right after a password change: the bump above revokes everything at
// NOW(), and requireAuth's cutoff test is inclusive (createdAt <= cutoff), so a
// freshly-minted token sharing that same wall-clock second would be revoked too.
// Rewinding the cutoff a hair below the new token's issued-at lets it survive
// while still revoking every older session.
async function setTokensValidAfter(id, when) {
  return query('UPDATE users SET tokens_valid_after = ? WHERE id = ?', [when, id])
}

// Store a (not-yet-enabled) TOTP secret for a user. Enabling is a separate step
// so a secret is never trusted until the user has confirmed one code.
async function setTotpSecret(id, secret) {
  return query('UPDATE users SET totp_secret = ?, totp_enabled = 0 WHERE id = ?', [secret, id])
}

async function enableTotp(id) {
  return query('UPDATE users SET totp_enabled = 1 WHERE id = ?', [id])
}

async function disableTotp(id) {
  return query('UPDATE users SET totp_secret = NULL, totp_enabled = 0 WHERE id = ?', [id])
}

module.exports = {
  insertUser,
  findByUsername,
  findById,
  findActiveByEmail,
  listUsers,
  updateUser,
  deleteUser,
  countUsers,
  countAdmins,
  touchLastLogin,
  bumpTokensValidAfter,
  setTokensValidAfter,
  setTotpSecret,
  enableTotp,
  disableTotp,
}
