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

// SQL for the trusted_devices table. The opaque trust token lives client-side; the
// DB stores only its sha256 hash (token_hash). Mirrors mobileSessions.db — a
// trusted device is the MFA analogue of a live session: it lets a login skip the
// TOTP step, and is revocable per-row.

// Insert a new trusted-device row. expiresAt is a JS Date (or ms epoch). last_used_at
// is seeded to now (the device was just trusted at a successful login).
async function insert({ userId, tokenHash, platform = 'web', deviceName = null, deviceHash = null, userAgent = null, expiresAt }) {
  const res = await query(
    `INSERT INTO trusted_devices (user_id, token_hash, platform, device_name, device_hash, user_agent, expires_at, last_used_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`,
    [userId, tokenHash, platform, deviceName, deviceHash, userAgent, new Date(expiresAt)],
  )
  return res.insertId
}

// Look up a trusted device by token hash only if it is still usable: not revoked
// and not past expiry. Returns the row (incl. user_id) or null. Used by the login
// path to decide whether TOTP can be skipped.
async function findValidByHash(tokenHash) {
  const rows = await query(
    `SELECT * FROM trusted_devices
     WHERE token_hash = ? AND revoked_at IS NULL AND expires_at > NOW()
     LIMIT 1`,
    [tokenHash],
  )
  return rows[0] || null
}

// Stamp last_used_at when a device's trust is honored at login. Idempotent.
async function touchLastUsed(id) {
  const res = await query(
    'UPDATE trusted_devices SET last_used_at = NOW() WHERE id = ?',
    [id],
  )
  return Number(res.affectedRows || 0)
}

// List a user's currently-active (unrevoked, unexpired) trusted devices — newest
// first. Never returns the token hash. Powers the self-service "Trusted Devices"
// list and the admin per-user view. Works for any user id (self or admin target).
async function listActiveForUser(userId) {
  return query(
    `SELECT id, platform, device_name, device_hash, user_agent, created_at, last_used_at, expires_at
       FROM trusted_devices
      WHERE user_id = ? AND revoked_at IS NULL AND expires_at > NOW()
      ORDER BY last_used_at DESC, created_at DESC`,
    [userId],
  )
}

// Count a user's currently-active trusted devices. Used to enforce the per-user cap
// (no silent pruning — the caller refuses an over-cap insert instead).
async function countActiveForUser(userId) {
  const rows = await query(
    'SELECT COUNT(*) AS n FROM trusted_devices WHERE user_id = ? AND revoked_at IS NULL AND expires_at > NOW()',
    [userId],
  )
  return Number(rows[0]?.n || 0)
}

// Revoke one of a user's trusted devices by row id (ownership-scoped, so both self
// and admin-for-target go through the same guarded query). Idempotent; returns
// rows changed.
async function revokeByIdForUser(id, userId) {
  const res = await query(
    'UPDATE trusted_devices SET revoked_at = NOW() WHERE id = ? AND user_id = ? AND revoked_at IS NULL',
    [id, userId],
  )
  return Number(res.affectedRows || 0)
}

// Revoke every active trusted device for a user ("untrust everywhere", and the
// invalidation hook on password change/reset / TOTP disable). Returns rows changed.
async function revokeAllForUser(userId) {
  const res = await query(
    'UPDATE trusted_devices SET revoked_at = NOW() WHERE user_id = ? AND revoked_at IS NULL',
    [userId],
  )
  return Number(res.affectedRows || 0)
}

// Housekeeping: delete rows that are long dead (expired or revoked). Returns rows
// removed. Same opportunistic-prune approach as mobile_refresh_tokens.
async function pruneExpired() {
  const res = await query(
    'DELETE FROM trusted_devices WHERE expires_at < NOW() OR revoked_at IS NOT NULL',
  )
  return Number(res.affectedRows || 0)
}

module.exports = {
  insert,
  findValidByHash,
  touchLastUsed,
  listActiveForUser,
  countActiveForUser,
  revokeByIdForUser,
  revokeAllForUser,
  pruneExpired,
}
