import Link from "next/link";

import { db } from "@/db/client";
import { desiredStates, policyRuns } from "@/db/schema";
import { desc, sql } from "drizzle-orm";

export const dynamic = "force-dynamic";

function formatDate(value: Date | null) {
  return value ? value.toISOString().slice(0, 16).replace("T", " ") : "Open";
}

// Desired state is only half the picture; the work it implies comes from
// comparing it to where files actually are.
//
// Backup is excluded because it mirrors live rather than being a placement of
// its own. Unaired episodes are excluded from the work queue because they
// cannot be acquired yet — they stay in desired_states so gaps stay visible.
//
// This is expressed as a CTE and a hash join on purpose. The obvious version, a
// correlated subquery per row, measured 91 SECONDS for a single evaluation of
// 4,000 rows; this covers the whole library in about 600ms.
const PLACEMENT = sql`
  with actual as (
    select sf.media_item_id mid, se.season_number sn, e.episode_number en,
           array_agg(distinct t.tier) tiers
      from storage_files sf
      join storage_tiers t on t.id = sf.tier_id
      left join episodes e on e.id = sf.episode_id
      left join seasons se on se.id = e.season_id
     where sf.missing_at is null and t.tier <> 'backup'
     group by 1, 2, 3
  ),
  joined as (
    select ds.id, mi.title, mi.media_type, ds.season_number sn, ds.episode_number en,
           ds.storage_tier, ds.reason_codes,
      case
        when a.tiers is null then 'acquire'
        when ds.storage_tier = 'live'    and not ('live' = any(a.tiers)) then 'restore'
        when ds.storage_tier = 'archive' and      ('live' = any(a.tiers)) then 'demote'
        else 'settled'
      end action
    from desired_states ds
    join media_items mi on mi.id = ds.media_item_id
    left join actual a
      on a.mid = ds.media_item_id
     and a.sn is not distinct from ds.season_number
     and a.en is not distinct from ds.episode_number
    where ds.wanted
      and ds.storage_tier is not null
      and not ('unaired' = any(ds.reason_codes))
  )`;

type ActionRow = { id: string; title: string; sn: number | null; en: number | null; action: string };

export default async function AdminPolicyPage() {
  const runs = await db.select().from(policyRuns).orderBy(desc(policyRuns.startedAt)).limit(10);

  const counts = await db.execute<{ action: string; n: number }>(
    sql`${PLACEMENT} select action, count(*)::int n from joined group by 1`,
  );

  // Ranked per action, not a flat LIMIT: a plain "order by action limit 240"
  // is swallowed whole by whichever action sorts first, leaving the other
  // columns rendering empty.
  const samples = await db.execute<ActionRow>(
    sql`${PLACEMENT},
        ranked as (
          select id, title, sn, en, action,
                 row_number() over (partition by action order by title, sn, en) rank
            from joined where action <> 'settled'
        )
        select id, title, sn, en, action from ranked where rank <= 10`,
  );

  const reasonTally = await db.execute<{ code: string; n: number }>(
    sql`select code, count(*)::int n
          from desired_states, unnest(reason_codes) code
         group by 1 order by 2 desc`,
  );

  const [totals] = await db
    .select({
      total: sql<number>`count(*)::int`,
      wanted: sql<number>`count(*) filter (where ${desiredStates.wanted})::int`,
      monitored: sql<number>`count(*) filter (where ${desiredStates.monitored})::int`,
      live: sql<number>`count(*) filter (where ${desiredStates.storageTier} = 'live')::int`,
      archive: sql<number>`count(*) filter (where ${desiredStates.storageTier} = 'archive')::int`,
    })
    .from(desiredStates);

  const countFor = (action: string) => Number(counts.rows.find((row) => row.action === action)?.n ?? 0);

  const ACTIONS = [
    { key: "restore", label: "Restore", blurb: "archive → live", tone: "text-admin-accent" },
    { key: "demote", label: "Demote", blurb: "live → archive", tone: "text-admin-muted" },
    { key: "acquire", label: "Acquire", blurb: "not held on any tier", tone: "text-admin-warn" },
  ] as const;

  function label(row: ActionRow) {
    if (row.sn === null) return row.title;
    return row.title + " S" + String(row.sn).padStart(2, "0") +
      (row.en !== null ? "E" + String(row.en).padStart(2, "0") : "");
  }

  return (
    <div className="space-y-6">
      <div className="flex flex-wrap items-end justify-between gap-4">
        <div>
          <p className="text-xs font-semibold uppercase tracking-[0.32em] text-admin-accent">Policy</p>
          <h1 className="mt-2 font-serif text-3xl font-semibold">Desired State</h1>
          <p className="mt-2 max-w-3xl text-sm text-admin-muted">
            Where every episode and movie <em>should</em> live, computed from the retention rules, compared to where it
            actually is. This is a plan, not an instruction — nothing here moves a file. The mover stays disarmed until
            Ampelos takes over from Sonarr and Radarr.
          </p>
        </div>
        <Link href="/admin/policy/sizes" className="admin-nav-button px-3 py-2 text-sm font-medium">
          Release size limits
        </Link>
      </div>

      <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
        {[
          { label: "Evaluated", value: totals?.total ?? 0 },
          { label: "Wanted", value: totals?.wanted ?? 0 },
          { label: "Monitored for upgrades", value: totals?.monitored ?? 0 },
          { label: "Belongs on live", value: totals?.live ?? 0 },
          { label: "Belongs on archive", value: totals?.archive ?? 0 },
        ].map((tile) => (
          <div key={tile.label} className="admin-panel p-4">
            <p className="text-xs text-admin-muted">{tile.label}</p>
            <p className="mt-1 text-2xl font-semibold">{tile.value}</p>
          </div>
        ))}
      </div>

      <section className="admin-panel overflow-hidden">
        <div className="border-b border-admin-line px-5 py-4">
          <h2 className="font-serif text-xl font-semibold">Placement Work Implied</h2>
          <p className="mt-1 text-sm text-admin-muted">
            {countFor("settled")} items are already where they belong.
          </p>
        </div>
        <div className="grid gap-4 p-5 lg:grid-cols-3">
          {ACTIONS.map((action) => {
            const rows = samples.rows.filter((row) => row.action === action.key);
            const total = countFor(action.key);
            return (
              <div key={action.key} className="rounded-md border border-admin-line bg-admin-subpanel p-4">
                <p className={"text-sm font-semibold " + action.tone}>{action.label}</p>
                <p className="text-xs text-admin-muted">{action.blurb}</p>
                <p className="mt-2 text-3xl font-semibold">{total}</p>
                <ul className="mt-3 space-y-1 text-xs text-admin-muted">
                  {rows.slice(0, 10).map((row) => (
                    <li key={row.id} className="truncate">{label(row)}</li>
                  ))}
                  {total > rows.slice(0, 10).length ? (
                    <li>…and {total - rows.slice(0, 10).length} more</li>
                  ) : null}
                </ul>
              </div>
            );
          })}
        </div>
      </section>

      <section className="admin-panel overflow-hidden">
        <div className="border-b border-admin-line px-5 py-4">
          <h2 className="font-serif text-xl font-semibold">Why</h2>
          <p className="mt-1 text-sm text-admin-muted">Reason codes across every computed decision.</p>
        </div>
        <div className="divide-y divide-admin-line">
          {reasonTally.rows.map((row) => (
            <div key={row.code} className="flex items-center justify-between px-5 py-2 text-sm">
              <span className={row.code === "metadata_missing" ? "text-admin-warn" : ""}>{row.code}</span>
              <span className="text-admin-muted">{row.n}</span>
            </div>
          ))}
          {!reasonTally.rows.length ? (
            <p className="p-5 text-sm text-admin-muted">No desired state computed yet.</p>
          ) : null}
        </div>
      </section>

      <section className="admin-panel overflow-hidden">
        <div className="border-b border-admin-line px-5 py-4">
          <h2 className="font-serif text-xl font-semibold">Recent Policy Runs</h2>
        </div>
        <div className="divide-y divide-admin-line">
          {runs.length ? runs.map((run) => (
            <div key={run.id} className="grid gap-3 px-5 py-4 md:grid-cols-[0.8fr_1fr_1fr_0.7fr_0.7fr] md:items-center">
              <p className="font-medium">{run.trigger}</p>
              <p className="text-sm text-admin-muted">Started {formatDate(run.startedAt)}</p>
              <p className="text-sm text-admin-muted">Completed {formatDate(run.completedAt)}</p>
              <p className="text-sm text-admin-muted">Evaluated {run.itemsEvaluated ?? 0}</p>
              <p className="text-sm text-admin-muted">Changed {run.itemsChanged ?? 0}</p>
            </div>
          )) : <p className="p-5 text-sm text-admin-muted">No policy runs recorded yet.</p>}
        </div>
      </section>
    </div>
  );
}
