import Link from 'next/link'
import { db } from '@/lib/db'
import { Card, Caveat, DimensionBar, Label } from '@/components/ui'
import { graderIsWhollyUncalibrated } from '@/lib/rubrics'
import { expireStaleSessions, inflightSession, queuedSessions } from '@/lib/agent-sessions'
import { cadenceDaysFor, cadenceForAll } from '@/lib/cadence'
import { choresEnabled } from '@/lib/chores'
import { GenerateButton, RecomputeButton, SessionBanner } from './controls'

function daysSince(d: Date | null): number | null {
  if (!d) return null
  return Math.floor((Date.now() - d.getTime()) / 86_400_000)
}

export default async function ParentDashboard() {
  const students = await db.student.findMany({
    orderBy: { grade: 'desc' },
    include: {
      sets: { orderBy: { round: 'desc' } },
      states: {
        include: {
          construct: true,
          dimensions: {
            include: { dimension: true },
            orderBy: { dimension: { sortOrder: 'asc' } },
          },
        },
      },
    },
  })

  await expireStaleSessions()
  const running = await inflightSession()
  const queued = await queuedSessions()
  const lastSession = await db.agentSession.findFirst({ orderBy: { startedAt: 'desc' } })
  const agentConfigured = Boolean(process.env.AGENT_TOKEN && process.env.AGENT_CALLBACK_SECRET)

  const cadence = await cadenceForAll()
  const cadenceFor = (id: string, track: 'ACADEMIC' | 'SPIRITUAL') =>
    cadence.find((c) => c.studentId === id && c.track === track)

  const flaggedCount = await db.response.count({ where: { flagged: true } })
  const ungraded = await db.response.count({ where: { gradings: { none: {} } } })

  return (
    <div className="space-y-6">
      {!agentConfigured && (
        <Caveat>
          <strong>The Mac agent isn&rsquo;t configured.</strong> Everything else
          works, but nothing can be generated or graded until{' '}
          <code>AGENT_TOKEN</code> and <code>AGENT_CALLBACK_SECRET</code> are set
          in <code>/home/learn/public_html/.env</code>, the SSH key is in place,
          and <code>agent/run-learn-agent.sh</code> is installed on the mini. See
          <code>agent/README.md</code>.
        </Caveat>
      )}

      {!choresEnabled() && (
        <Card>
          <p className="text-sm text-[var(--color-muted)]">
            Chore points are <strong>off</strong> while the system is being tested
            &mdash; releasing a set won&rsquo;t create anything in the chores app.
            Set <code>CHORES_ENABLED=true</code> in the VPS <code>.env</code> when
            practice sets should start earning the 5 points.
          </p>
        </Card>
      )}

      {queued.length > 0 && (
        <Card className="text-sm">
          <strong>
            {queued.length} request{queued.length === 1 ? '' : 's'} queued
          </strong>{' '}
          &mdash; waiting for the mini to free up, then they go in order. Nothing is
          lost; you don&rsquo;t need to click again.
        </Card>
      )}

      <SessionBanner
        session={
          (running ?? lastSession)
            ? {
                id: (running ?? lastSession)!.id,
                kind: (running ?? lastSession)!.kind,
                status: (running ?? lastSession)!.status,
                queueDepth: (running ?? lastSession)!.queueDepth,
                completed: (running ?? lastSession)!.completed,
                errorMessage: (running ?? lastSession)!.errorMessage,
                logTail: (running ?? lastSession)!.logTail,
                startedAt: (running ?? lastSession)!.startedAt.toISOString(),
              }
            : null
        }
        isRunning={Boolean(running)}
      />

      {graderIsWhollyUncalibrated() && (
        <Caveat>
          <strong>The grader is uncalibrated.</strong> No grade-level anchor
          responses exist yet (<code>src/lib/anchors.ts</code> is empty), so every
          score is stamped <code>-uncalibrated</code> in its audit trail. Rule 3 of{' '}
          <code>notes/07-ai-grading.md</code>: an untuned model applies an
          adult-professional standard to a child&rsquo;s writing and under-scores
          systematically &mdash; which looks exactly like a real deficit. Add real
          annotated CDE samples, and hand-grade ~20% of the first set yourself
          (Rule 8) before treating any of this as data.
        </Caveat>
      )}

      {(flaggedCount > 0 || ungraded > 0) && (
        <Card className="flex flex-wrap items-center justify-between gap-3">
          <div className="text-sm">
            {ungraded > 0 && (
              <span className="mr-4">
                <strong>{ungraded}</strong> response{ungraded === 1 ? '' : 's'} waiting
                to be graded
              </span>
            )}
            {flaggedCount > 0 && (
              <span>
                <strong>{flaggedCount}</strong> waiting for your score &mdash; the two
                graders disagreed on them by more than a point
              </span>
            )}
          </div>
          <RecomputeButton />
        </Card>
      )}

      {students.map((student) => {
        const latest = student.sets.find((s) => s.status !== 'DISCARDED')
        const pending = student.sets.find((s) => s.status === 'PENDING_REVIEW')
        const age = daysSince(latest?.releasedAt ?? latest?.generatedAt ?? null)

        return (
          <Card key={student.id} className="space-y-4">
            <div className="flex flex-wrap items-baseline justify-between gap-2">
              <div>
                <h2 className="text-base font-semibold">
                  {student.name}{' '}
                  <span className="font-normal text-[var(--color-muted)]">
                    &middot; Grade {student.grade}
                    {student.courseNote ? ` \u00b7 ${student.courseNote}` : ''}
                  </span>
                </h2>
                <p className="mt-0.5 text-xs text-[var(--color-muted)]">
                  {latest
                    ? `Last set ${age === 0 ? 'today' : `${age} day${age === 1 ? '' : 's'} ago`} — round ${latest.round}, ${latest.status.toLowerCase().replace('_', ' ')}`
                    : 'No sets yet — the first one will be a baseline'}
                </p>
                {/* One clock per line of learning. Advisory only: it drives the
                    hourly sweep, and the buttons ignore it entirely — a set can
                    be generated for either track at any time. */}
                {(['ACADEMIC', 'SPIRITUAL'] as const).map((track) => {
                  const c = cadenceFor(student.id, track)
                  if (!c) return null
                  const text = c.dueNow
                    ? 'due now'
                    : c.blockedBy === 'set outstanding'
                      ? `waiting on "${c.outstandingSetTitle}"`
                      : `next in ${cadenceDaysFor(track) - (c.daysSince ?? 0)} days`
                  return (
                    <p key={track} className="mt-0.5 text-xs text-[var(--color-muted)]">
                      <span className="inline-block w-20">
                        {track === 'ACADEMIC' ? 'School' : 'Spiritual'}
                      </span>
                      {text}
                    </p>
                  )
                })}
              </div>
              <div className="flex items-center gap-2">
                <Link
                  href={`/parent/results/${student.id}`}
                  className="text-xs text-[var(--color-muted)] underline hover:text-[var(--color-ink)]"
                >
                  Evidence
                </Link>
                <div className="flex flex-col items-end gap-1.5">
                  <GenerateButton
                    studentId={student.id}
                    track="ACADEMIC"
                    label="Generate school set"
                    disabled={!agentConfigured}
                  />
                  <GenerateButton
                    studentId={student.id}
                    track="SPIRITUAL"
                    label="Generate spiritual set"
                    disabled={!agentConfigured}
                  />
                </div>
              </div>
            </div>

            {pending && (
              <Link
                href={`/parent/review/${pending.id}`}
                className="block rounded-lg border border-[var(--color-accent)] bg-[var(--color-accent-soft)] px-3 py-2 text-sm text-[var(--color-accent)]"
              >
                A draft set is waiting for your review &mdash; {pending.title}
              </Link>
            )}

            {student.states.length === 0 ? (
              <p className="text-sm text-[var(--color-muted)]">
                No evidence yet. Nothing can be targeted until a baseline exists.
              </p>
            ) : (
              <div className="space-y-4">
                {student.states.map((state) => (
                  <div key={state.id} className="space-y-2">
                    <div className="flex items-baseline justify-between gap-2">
                      <Label>{state.construct.label}</Label>
                      <span className="text-[11px] text-[var(--color-muted)]">
                        {state.roundsSeen} round{state.roundsSeen === 1 ? '' : 's'}
                      </span>
                    </div>
                    {state.dimensions.map((d) => (
                      <DimensionBar
                        key={d.id}
                        label={d.dimension.label}
                        estimate={d.estimate}
                        itemsObserved={d.itemsObserved}
                        itemsFlagged={d.itemsFlagged}
                        itemsHinted={state.itemsHinted}
                        movement={
                          d.recentEstimate !== null && d.previousEstimate !== null
                            ? Math.round((d.recentEstimate - d.previousEstimate) * 100)
                            : null
                        }
                      />
                    ))}
                  </div>
                ))}
              </div>
            )}
          </Card>
        )
      })}
    </div>
  )
}
