import 'server-only'

/**
 * Administration conditions.
 *
 * notes/05-caveats.md contains a real example from these boys' own data of a
 * test invalidated by a bad session, and CLAUDE.md says a rushed test is worse
 * than no test. So pacing is recorded and surfaced next to every result rather
 * than left for someone to wonder about afterwards.
 */

export interface Pacing {
  totalMs: number | null
  medianMsPerItem: number | null
  fastestMs: number | null
  itemsTimed: number
  /** Items answered so fast the response can't reflect real work. */
  suspiciouslyFast: number
  /** Plain-language verdict for the parent. Null when there is nothing to say. */
  warning: string | null
}

/**
 * Under this, a constructed response was not thought about. Deliberately
 * generous — the point is to catch a boy clicking through, not to police speed.
 */
const RUSHED_MS = 25_000

export function summarisePacing(
  responses: { timeSpentMs: number | null; format: string }[],
  setStartedAt: Date | null,
  setCompletedAt: Date | null,
): Pacing {
  const timed = responses
    .map((r) => r.timeSpentMs)
    .filter((v): v is number => typeof v === 'number' && v > 0)

  const totalMs =
    setStartedAt && setCompletedAt
      ? setCompletedAt.getTime() - setStartedAt.getTime()
      : timed.length
        ? timed.reduce((a, b) => a + b, 0)
        : null

  let median: number | null = null
  if (timed.length) {
    const sorted = [...timed].sort((a, b) => a - b)
    const mid = Math.floor(sorted.length / 2)
    median =
      sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2)
  }

  // Multiple choice is legitimately quick; only written work is judged on pace.
  const written = responses.filter((r) => r.format !== 'MULTIPLE_CHOICE')
  const suspiciouslyFast = written.filter(
    (r) => typeof r.timeSpentMs === 'number' && r.timeSpentMs > 0 && r.timeSpentMs < RUSHED_MS,
  ).length

  let warning: string | null = null
  if (suspiciouslyFast > 0 && written.length > 0) {
    const share = suspiciouslyFast / written.length
    if (share >= 0.5) {
      warning = `${suspiciouslyFast} of ${written.length} written items were answered in under ${Math.round(RUSHED_MS / 1000)} seconds. This looks like a rushed sitting, and their own testing history contains a result invalidated exactly this way — treat these scores as unreliable rather than as a finding.`
    } else {
      warning = `${suspiciouslyFast} of ${written.length} written items were answered in under ${Math.round(RUSHED_MS / 1000)} seconds. Worth reading those responses before trusting their scores.`
    }
  }

  return {
    totalMs,
    medianMsPerItem: median,
    fastestMs: timed.length ? Math.min(...timed) : null,
    itemsTimed: timed.length,
    suspiciouslyFast,
    warning,
  }
}

export function formatDuration(ms: number | null): string {
  if (ms === null || !Number.isFinite(ms) || ms <= 0) return '—'
  const s = Math.round(ms / 1000)
  if (s < 60) return `${s}s`
  const m = Math.floor(s / 60)
  const rem = s % 60
  if (m < 60) return rem ? `${m}m ${rem}s` : `${m}m`
  const h = Math.floor(m / 60)
  return `${h}h ${m % 60}m`
}
