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

// SQL for the mobile SSO authorization bridge (mobile_auth_sessions +
// mobile_auth_codes). The authorization code is stored as a sha256 hash only
// (hashing is done in the .model layer, mirroring mobileSessions); the PKCE
// code_challenge is a hash by construction and is stored as-supplied. Both
// tables self-prune on expiry (pruneExpired). See docs BACKEND_DESIGN §3/§4.

// ── mobile_auth_sessions ───────────────────────────────────────────────────

// Insert a pending bridge session. expiresAt is a JS Date (or ms epoch).
async function insertSession({ sessionId, provider, codeChallenge, redirectUri, state, expiresAt }) {
  const res = await query(
    `INSERT INTO mobile_auth_sessions (session_id, provider, code_challenge, redirect_uri, state, expires_at)
     VALUES (?, ?, ?, ?, ?, ?)`,
    [sessionId, provider, codeChallenge, redirectUri, state, new Date(expiresAt)],
  )
  return res.insertId
}

// Look up a bridge session by its opaque session_id. Returns the row or null.
async function getSession(sessionId) {
  const rows = await query('SELECT * FROM mobile_auth_sessions WHERE session_id = ? LIMIT 1', [sessionId])
  return rows[0] || null
}

// Mark a still-pending, unexpired session `completed` and attach the resolved
// user. Guards on status + expiry so a replayed callback can't re-complete a
// consumed/expired session. Returns rows changed (0 = not eligible).
async function completeSession(sessionId, userId) {
  const res = await query(
    `UPDATE mobile_auth_sessions
        SET status = 'completed', user_id = ?
      WHERE session_id = ? AND status = 'pending' AND expires_at > NOW()`,
    [userId, sessionId],
  )
  return Number(res.affectedRows || 0)
}

// Record that the user asked to trust this device on the Custom Tab TOTP form.
// Guarded on status + expiry for the same reason completeSession is: a replayed
// TOTP post must not re-arm a session that has already been consumed. Stores a
// boolean only — the trust token is minted at /exchange and never lands here.
async function setTrustDevice(sessionId) {
  const res = await query(
    `UPDATE mobile_auth_sessions
        SET trust_device = 1
      WHERE session_id = ? AND status = 'pending' AND expires_at > NOW()`,
    [sessionId],
  )
  return Number(res.affectedRows || 0)
}

// Mark a session `consumed` after a successful token exchange (stamps used_at).
async function consumeSession(sessionId) {
  const res = await query(
    "UPDATE mobile_auth_sessions SET status = 'consumed', used_at = NOW() WHERE session_id = ?",
    [sessionId],
  )
  return Number(res.affectedRows || 0)
}

// ── mobile_auth_codes ──────────────────────────────────────────────────────

// Insert a freshly minted authorization code (by hash).
async function insertCode({ codeHash, userId, sessionId, expiresAt }) {
  const res = await query(
    `INSERT INTO mobile_auth_codes (code_hash, user_id, session_id, expires_at)
     VALUES (?, ?, ?, ?)`,
    [codeHash, userId, sessionId, new Date(expiresAt)],
  )
  return res.insertId
}

// Return an authorization-code row only if it is still redeemable: not used and
// not expired. Returns the row (incl. user_id, session_id) or null.
async function findValidCode(codeHash) {
  const rows = await query(
    `SELECT * FROM mobile_auth_codes
      WHERE code_hash = ? AND used_at IS NULL AND expires_at > NOW()
      LIMIT 1`,
    [codeHash],
  )
  return rows[0] || null
}

// Atomically mark a code used (single-use gate). Only affects a not-yet-used row,
// so a concurrent double-redeem sees affectedRows = 0 on the loser. Returns rows
// changed.
async function markCodeUsed(codeHash) {
  const res = await query(
    'UPDATE mobile_auth_codes SET used_at = NOW() WHERE code_hash = ? AND used_at IS NULL',
    [codeHash],
  )
  return Number(res.affectedRows || 0)
}

// Housekeeping: drop long-dead rows from both bridge tables (expired, or codes
// already used). Keeps the tables from growing without bound. Returns rows removed.
async function pruneExpired() {
  const a = await query('DELETE FROM mobile_auth_codes WHERE expires_at < NOW() OR used_at IS NOT NULL')
  const b = await query("DELETE FROM mobile_auth_sessions WHERE expires_at < NOW() OR status = 'consumed'")
  return Number(a.affectedRows || 0) + Number(b.affectedRows || 0)
}

module.exports = {
  insertSession,
  getSession,
  completeSession,
  setTrustDevice,
  consumeSession,
  insertCode,
  findValidCode,
  markCodeUsed,
  pruneExpired,
}
