import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getAdminSession } from '@/lib/auth'
import { hostLabel, worst, STALE_AFTER_SEC, type HostView, type Severity } from '@/lib/monitor'

export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'

// GET /api/monitor — current infrastructure status, for the badge and /health.
//
// Staleness is computed here rather than stored, because the interesting case
// is the one where nothing arrives: a host that has stopped reporting can't
// tell us it stopped. Any host whose newest check is older than STALE_AFTER_SEC
// counts as one problem in its own right, on top of whatever its last snapshot
// said.
export async function GET() {
  const session = await getAdminSession()
  if (!session) return NextResponse.json({ error: 'unauthorized' }, { status: 401 })

  const rows = await db.hostCheck.findMany({ orderBy: [{ host: 'asc' }, { order: 'asc' }] })

  const now = Date.now()
  const byHost = new Map<string, typeof rows>()
  for (const r of rows) {
    const list = byHost.get(r.host)
    if (list) list.push(r)
    else byHost.set(r.host, [r])
  }

  const hosts: HostView[] = [...byHost.entries()].map(([host, checks]) => {
    const newest = Math.max(...checks.map(c => c.updatedAt.getTime()))
    const reportingAgeSec = Math.round((now - newest) / 1000)
    const stale = reportingAgeSec > STALE_AFTER_SEC

    let status: Severity = 'OK'
    let problems = 0
    for (const c of checks) {
      // An acknowledged check still reports what is true — the row keeps its
      // status and its detail — but it stops colouring the card and stops
      // counting as a problem. Eric has read it and decided; carrying on
      // shouting is how a dashboard trains someone to ignore it.
      if (c.ackedAt) continue
      // Informational state tiles (`sensor.*`, e.g. a window's Open/Closed) are
      // colour-coded for the glance — amber when open — but they are not faults:
      // they must not roll the host badge amber or raise the problem count, or an
      // open daytime window would make the whole house (and the global badge)
      // read "unhealthy" all day. A CRIT one is the exception: an unreadable
      // sensor is a genuine problem and still rolls up.
      if (c.name.startsWith('sensor.') && c.status !== 'CRIT') continue
      status = worst(status, c.status)
      if (c.status !== 'OK') problems++
    }
    if (stale) {
      status = worst(status, 'STALE')
      problems++
    }

    return {
      host,
      label: hostLabel(host),
      status,
      stale,
      reportingAgeSec,
      problems,
      checks: checks.map(c => ({
        name: c.name,
        label: c.label,
        status: c.status,
        value: c.value,
        unit: c.unit,
        detail: c.detail,
        since: c.since.toISOString(),
        ageSec: Math.round((now - c.updatedAt.getTime()) / 1000),
        meta: c.meta ?? undefined,
        hit: c.hit,
        acked: c.ackedAt ? { at: c.ackedAt.toISOString(), note: c.ackNote } : null,
      })),
    }
  })

  const problems = hosts.reduce((n, h) => n + h.problems, 0)
  const overall = hosts.reduce<Severity>((s, h) => worst(s, h.status), 'OK')

  // Digests ride along on this request rather than getting their own. /health
  // already polls here every few seconds; a second endpoint would double that
  // traffic to show a card that changes once a day.
  //
  // Only the most recent few, and bodies included: they are a couple of KB and
  // the whole point is being able to re-read one without another round trip.
  const digests = await db.digest.findMany({
    orderBy: { createdAt: 'desc' },
    take: 7,
    select: {
      id: true, kind: true, title: true, summary: true, body: true,
      createdAt: true, readAt: true, notable: true,
      // Oldest first: a thread reads downward.
      replies: {
        orderBy: { createdAt: 'asc' },
        select: { id: true, prompt: true, status: true, reply: true, error: true, createdAt: true },
      },
    },
  })

  // Buyers the quail loop has stopped on, waiting for a call only Eric can
  // make. A SEPARATE number from `problems`, for the same reason the digests
  // are: nothing is broken. Someone is waiting. Those want the same colour —
  // both are "this needs you" — but not the same counter, because a number
  // that means two things cannot be acted on without first asking which.
  const quailWaiting = await db.quailDecision.count({ where: { state: 'OPEN' } })

  return NextResponse.json({
    hosts,
    problems,
    quailWaiting,
    status: hosts.length ? overall : 'OK',
    configured: hosts.length > 0,
    // Deliberately NOT folded into `problems`. An unread digest is information,
    // not a fault, and letting it raise the same counter that means "a host is
    // broken" would make that number mean two different things at once.
    digests: digests.map(d => ({
      id: d.id,
      kind: d.kind,
      title: d.title,
      summary: d.summary,
      body: d.body,
      createdAt: d.createdAt.toISOString(),
      read: d.readAt !== null,
      notable: d.notable,
      replies: d.replies.map(r => ({
        id: r.id,
        prompt: r.prompt,
        status: r.status,
        reply: r.reply,
        error: r.error,
        createdAt: r.createdAt.toISOString(),
      })),
    })),
  })
}
