/**
 * Piggy's operating history: the queue, the runs, the actions, and the
 * evidence-bearing facts that came out of them.
 *
 * Without the facts the review queue and every provenance tooltip are empty,
 * which hides the thing that makes an agent-written CRM trustworthy: that each
 * claim carries a score, a band, evidence and a source, and that only verified
 * claims apply themselves.
 *
 * Without the runs behind them the facts are worse than empty — they are
 * unattributed. `facts.agent_run_id` exists so a claim can be traced to the
 * execution that produced it, and a demo in which every claim arrives from
 * nowhere argues against the product rather than for it. So this module seeds
 * the whole chain the schema defends: a task was queued, a worker leased it, a
 * run spent tokens against a priced model, an idempotency-keyed action was
 * written, and only then did a fact appear.
 *
 * The mix is deliberate.
 *
 *   Two `applied` facts show what a confident agent writes unprompted, and
 *   four `proposed` show what waits for a human — one of them a near-miss
 *   whose own evidence does not support it, so the review queue is not a row
 *   of obvious approvals.
 *
 *   One task was rate-limited on its first attempt and succeeded on its
 *   second, because an agent with a perfect record is the least believable
 *   thing that could be shown here. Its failed run is kept: cost was spent and
 *   the ledger says so.
 *
 *   One task is still queued with a future `dueAt`, so the queue has a state
 *   other than finished.
 *
 * Every run is one task's work on one subject. That is not decoration: the
 * `pig_record_fact` tool refuses any claim about a record the task did not
 * name, so a run that produced facts about two different accounts could not
 * have happened, and seeding one would teach a reader something false about
 * how the agent is allowed to behave.
 */
import { createHash } from 'node:crypto';
import { and, eq, inArray, like } from 'drizzle-orm';
import { CONSUMING_ALLOCATION_STATUSES, breakEvenPricePerGpuHourCents } from '@pig/core';
import {
  accounts,
  agentActions,
  agentRuns,
  agentTasks,
  allocations,
  capacityCommitments,
  contacts,
  facts,
  users,
} from '../../schema/index';
import type { DemoContext } from './index';

/** The model Piggy runs on by default; see `PIGGY_MODEL`. */
const MODEL = 'nvidia/nemotron-3-nano-30b-a3b';

/**
 * The published price of that model, in cents per million tokens.
 *
 * Deliberately the same numbers as `PIGGY_PRICE_INPUT_CENTS_PER_MTOK` and
 * `PIGGY_PRICE_OUTPUT_CENTS_PER_MTOK`, and deliberately the same arithmetic as
 * `costMicroCents` in the chat server: cents-per-million multiplied by tokens
 * is already micro-cents, so the whole calculation stays in integers instead of
 * rounding a fraction of a cent per run and drifting. A demo cost that does not
 * reproduce from the token counts beside it is worse than no cost at all.
 */
const INPUT_CENTS_PER_MTOK = 5;
const OUTPUT_CENTS_PER_MTOK = 20;

function costMicroCents(inputTokens: number, outputTokens: number): number {
  return Math.round(inputTokens * INPUT_CENTS_PER_MTOK + outputTokens * OUTPUT_CENTS_PER_MTOK);
}

/**
 * Fixed ids, because `agent_runs` and `agent_tasks` have no natural key to
 * dedupe on — a run is an event, and two identical events are two rows. Seeding
 * them with constant ids and `ON CONFLICT DO NOTHING` is what makes a second
 * `db:demo` a no-op rather than a second fortnight of invented history.
 */
const TASK_ID = {
  coreweave: 'a1c0f7e2-0001-4a00-8a00-000000000001',
  nebius: 'a1c0f7e2-0001-4a00-8a00-000000000002',
  crusoe: 'a1c0f7e2-0001-4a00-8a00-000000000003',
  lambda: 'a1c0f7e2-0001-4a00-8a00-000000000004',
  dana: 'a1c0f7e2-0001-4a00-8a00-000000000005',
  runpod: 'a1c0f7e2-0001-4a00-8a00-000000000006',
  renewal: 'a1c0f7e2-0001-4a00-8a00-000000000007',
} as const;

const RUN_ID = {
  coreweave: 'a1c0f7e2-0002-4a00-8a00-000000000001',
  nebius: 'a1c0f7e2-0002-4a00-8a00-000000000002',
  crusoeRateLimited: 'a1c0f7e2-0002-4a00-8a00-000000000003',
  crusoe: 'a1c0f7e2-0002-4a00-8a00-000000000004',
  lambda: 'a1c0f7e2-0002-4a00-8a00-000000000005',
  dana: 'a1c0f7e2-0002-4a00-8a00-000000000006',
  runpod: 'a1c0f7e2-0002-4a00-8a00-000000000007',
} as const;

/**
 * One action row per claim, with a fixed id.
 *
 * The idempotency key cannot serve as that anchor here even though it is
 * unique: it hashes the subject's id, and `--clear` followed by a reseed gives
 * the demo accounts new ids. Keyed only on the digest, every teardown would
 * leave last week's action row behind pointing at a record that no longer
 * exists. The id pins the row; the key is still written exactly as
 * `pig_record_fact` would compute it.
 */
const ACTION_ID = {
  coreweave: 'a1c0f7e2-0003-4a00-8a00-000000000001',
  nebius: 'a1c0f7e2-0003-4a00-8a00-000000000002',
  crusoe: 'a1c0f7e2-0003-4a00-8a00-000000000003',
  lambda: 'a1c0f7e2-0003-4a00-8a00-000000000004',
  dana: 'a1c0f7e2-0003-4a00-8a00-000000000005',
  runpod: 'a1c0f7e2-0003-4a00-8a00-000000000006',
} as const;

type TaskKey = keyof typeof TASK_ID;
/** The tasks that produced a claim; `watch_renewal` has not run yet. */
type FactTaskKey = keyof typeof ACTION_ID;

const minute = 60_000;
const after = (from: Date, minutes: number) => new Date(from.getTime() + minutes * minute);

/** What the task named, resolved to an id before anything is written. */
type Subject =
  | { kind: 'account'; domain: string }
  | { kind: 'contact'; fullName: string };

interface RunSeed {
  id: string;
  /** Minutes after the task's own start, so a retry lands after its failure. */
  startsAfter: number;
  endsAfter: number;
  inputTokens: number;
  outputTokens: number;
  toolCallCount: number;
  summary: string;
  /** Set only on the run that did not finish its work. */
  error?: string;
}

interface WorkSeed {
  task: TaskKey;
  kind: 'enrich_account' | 'enrich_contact' | 'research_supplier';
  subject: Subject;
  reason: string;
  priority: number;
  /** One per lease taken, so a retried task shows two. */
  attempts: number;
  daysAgo: number;
  runs: RunSeed[];
}

/**
 * A fortnight of routine enrichment.
 *
 * Prefixed like every other invented record: a screenshot of a Piggy run
 * summary must not be mistakeable for something the agent really did.
 */
function workSeeds(prefix: string): WorkSeed[] {
  return [
    {
      task: 'coreweave',
      kind: 'research_supplier',
      subject: { kind: 'account', domain: 'coreweave.com' },
      reason: `${prefix}Supply deal opened against an account with no supplier classification.`,
      priority: 5,
      attempts: 1,
      daysAgo: -13,
      runs: [
        {
          id: RUN_ID.coreweave,
          startsAfter: 0,
          endsAfter: 3,
          inputTokens: 9_900,
          outputTokens: 1_240,
          toolCallCount: 3,
          summary:
            `${prefix}Read coreweave.com and two secondary descriptions of the same business. ` +
            'All three describe GPU cloud infrastructure sold as a service, so supplierType=neocloud ' +
            'at 0.96 — verified, and applied without review.',
        },
      ],
    },
    {
      task: 'nebius',
      kind: 'research_supplier',
      subject: { kind: 'account', domain: 'nebius.com' },
      reason: `${prefix}EU-resident demand cannot be quoted against a block whose owner has no jurisdiction on record.`,
      priority: 6,
      attempts: 1,
      daysAgo: -12,
      runs: [
        {
          id: RUN_ID.nebius,
          startsAfter: 0,
          endsAfter: 2,
          inputTokens: 8_700,
          outputTokens: 980,
          toolCallCount: 2,
          summary:
            `${prefix}Confirmed from nebius.com that the Finnish site sits inside the EU ` +
            'data-residency perimeter. jurisdiction=European Union at 0.91 — verified, and applied.',
        },
      ],
    },
    {
      task: 'crusoe',
      kind: 'research_supplier',
      subject: { kind: 'account', domain: 'crusoe.ai' },
      reason: `${prefix}A regulated buyer asked for the SOC 2 position on this block before signing.`,
      priority: 7,
      // Two leases: the first run died on an upstream rate limit, the queue
      // backed off for a minute (`retryBackoffMs(1)`) and the second claimed it.
      attempts: 2,
      daysAgo: -9,
      runs: [
        {
          id: RUN_ID.crusoeRateLimited,
          startsAfter: 0,
          endsAfter: 1,
          inputTokens: 6_300,
          outputTokens: 410,
          toolCallCount: 1,
          summary: `${prefix}Abandoned before any claim was recorded.`,
          error:
            'Inference upstream returned 429: rate limit on the shared key. Two tool calls were ' +
            'already paid for; retrying after backoff.',
        },
        {
          id: RUN_ID.crusoe,
          startsAfter: 2,
          endsAfter: 5,
          inputTokens: 10_400,
          outputTokens: 1_320,
          toolCallCount: 4,
          summary:
            `${prefix}Re-ran once the rate limit cleared. crusoe.ai publishes a trust page citing ` +
            'SOC 2, but states neither the report scope nor the observation window, so the claim is ' +
            '0.72 — probable, and queued for a human rather than applied.',
        },
      ],
    },
    {
      task: 'lambda',
      kind: 'enrich_account',
      subject: { kind: 'account', domain: 'lambda.ai' },
      reason: `${prefix}Supply deal in financial diligence against a thin firmographic record.`,
      priority: 4,
      attempts: 1,
      daysAgo: -7,
      runs: [
        {
          id: RUN_ID.lambda,
          startsAfter: 0,
          endsAfter: 4,
          inputTokens: 11_300,
          outputTokens: 1_460,
          toolCallCount: 4,
          summary:
            `${prefix}Enriched lambda.ai from its own site. It markets both GPU cloud and hardware ` +
            'sold outright, which weakens the neocloud reading to 0.68 — below the bar to apply itself.',
        },
      ],
    },
    {
      task: 'dana',
      kind: 'enrich_contact',
      subject: { kind: 'contact', fullName: 'Dana Whitfield' },
      reason: `${prefix}Two internal documents disagree on the title of the Halcyon decision maker.`,
      priority: 3,
      attempts: 1,
      daysAgo: -5,
      runs: [
        {
          id: RUN_ID.dana,
          startsAfter: 0,
          endsAfter: 2,
          inputTokens: 7_400,
          outputTokens: 890,
          toolCallCount: 3,
          summary:
            `${prefix}Read the Halcyon Research account brief: it names Dana Whitfield as VP ` +
            'Infrastructure where the record says Head of Infrastructure. Neither source is dated, ' +
            'so 0.54 — possible, and queued.',
        },
      ],
    },
    {
      task: 'runpod',
      kind: 'enrich_account',
      subject: { kind: 'account', domain: 'runpod.io' },
      reason: `${prefix}Customer segment was blank on an account with a live burst pool behind it.`,
      priority: 2,
      attempts: 1,
      daysAgo: -3,
      runs: [
        {
          id: RUN_ID.runpod,
          startsAfter: 0,
          endsAfter: 2,
          inputTokens: 6_800,
          outputTokens: 1_540,
          toolCallCount: 3,
          summary:
            `${prefix}Proposed customerSegment=frontier_lab for runpod.io at 0.31 on the strength of ` +
            'marketing copy about large training runs. The excerpt does not name the account as the ' +
            'party doing the training, and the same account is a supplier on this book — the queue is ' +
            'holding a claim that should be rejected.',
        },
      ],
    },
  ];
}

interface FactSeed {
  accountDomain?: string;
  contactName?: string;
  /** The task whose run recorded it; the tool refuses any other subject. */
  task: FactTaskKey;
  runId: string;
  field: string;
  value: string;
  score: string;
  band: 'verified' | 'probable' | 'possible';
  status: 'applied' | 'proposed';
  method: string;
  sourceUrl: string;
  /**
   * Required, not optional. `recordFactInput` rejects a claim without both a
   * source URL and an evidence excerpt, and a seeded fact that could not have
   * been written through that tool is a demo of a rule the product does not
   * have.
   */
  excerpt: string;
  /** Merged into `evidence` beside the excerpt. */
  notes?: Record<string, unknown>;
}

function factSeeds(): FactSeed[] {
  return [
    {
      accountDomain: 'coreweave.com',
      task: 'coreweave',
      runId: RUN_ID.coreweave,
      field: 'supplierType',
      value: 'neocloud',
      score: '0.960',
      band: 'verified',
      status: 'applied',
      method: 'web_search',
      sourceUrl: 'https://www.coreweave.com/',
      excerpt: 'Describes itself as an AI hyperscaler providing GPU cloud infrastructure.',
      notes: { corroboration: 2 },
    },
    {
      accountDomain: 'nebius.com',
      task: 'nebius',
      runId: RUN_ID.nebius,
      field: 'jurisdiction',
      value: 'European Union',
      score: '0.910',
      band: 'verified',
      status: 'applied',
      method: 'web_search',
      sourceUrl: 'https://nebius.com/',
      excerpt: 'Operates a datacentre in Finland, inside the EU data-residency perimeter.',
      notes: { matters: 'Determines eligibility for customers with EU residency requirements.' },
    },
    {
      accountDomain: 'crusoe.ai',
      task: 'crusoe',
      runId: RUN_ID.crusoe,
      field: 'certifications',
      value: 'SOC 2 Type II',
      score: '0.720',
      band: 'probable',
      status: 'proposed',
      method: 'web_search',
      sourceUrl: 'https://crusoe.ai/',
      excerpt:
        'A trust page references SOC 2, but the report scope and observation window are not stated.',
      notes: { caution: 'Scope matters — a report can cover only some products.' },
    },
    {
      accountDomain: 'lambda.ai',
      task: 'lambda',
      runId: RUN_ID.lambda,
      field: 'supplierType',
      value: 'neocloud',
      score: '0.680',
      band: 'probable',
      status: 'proposed',
      method: 'web_search',
      sourceUrl: 'https://lambda.ai/',
      excerpt: 'Markets GPU cloud and on-premises clusters.',
    },
    {
      contactName: 'Dana Whitfield',
      task: 'dana',
      runId: RUN_ID.dana,
      field: 'title',
      value: 'VP Infrastructure',
      score: '0.540',
      band: 'possible',
      status: 'proposed',
      // The source is an invented internal document about an invented company,
      // which is the only kind of document this seed is entitled to quote.
      method: 'document',
      sourceUrl: 'https://demo.pig.invalid/documents/halcyon-research-account-brief',
      excerpt:
        'Account brief, attendee list: "Dana Whitfield, VP Infrastructure, Halcyon Research".',
      notes: {
        conflict: 'The CRM records Head of Infrastructure. Sources disagree, and neither is dated.',
      },
    },
    {
      accountDomain: 'runpod.io',
      task: 'runpod',
      runId: RUN_ID.runpod,
      field: 'customerSegment',
      value: 'frontier_lab',
      score: '0.310',
      band: 'possible',
      status: 'proposed',
      method: 'inference',
      sourceUrl: 'https://runpod.io/',
      // The excerpt does not support the claim, which is the point: the review
      // queue is where a reader learns to check the evidence rather than the
      // score. A weak model produces exactly this shape of near-miss.
      excerpt: 'Marketing copy refers to customers running large training jobs.',
      notes: {
        warning:
          'Weak. Nothing here says this account trains frontier models, and it is a supply-side ' +
          'provider on this book — a reviewer should reject it.',
      },
    },
  ];
}

/**
 * The idempotency key `pig_record_fact` would have written for this claim.
 *
 * Reproduced rather than invented, digest and all, so the mechanism the schema
 * is most careful about is demonstrated by rows a reader can recompute.
 */
function factIdempotencyKey(
  taskId: string,
  input: {
    targetType: 'account' | 'contact';
    targetId: string;
    field: string;
    value: string;
    sourceUrl: string;
    evidenceExcerpt: string;
  },
): string {
  const digest = createHash('sha256')
    .update(
      JSON.stringify([
        input.targetType,
        input.targetId,
        input.field,
        input.value,
        input.sourceUrl,
        input.evidenceExcerpt,
      ]),
    )
    .digest('hex');
  return `piggy:fact:${taskId}:${digest}`;
}

export async function seedFacts(context: DemoContext): Promise<{
  total: number;
  added: number;
  runs: number;
  tasks: number;
  actions: number;
  costMicroCents: number;
}> {
  const { db, prefix, at } = context;

  // The principal every queued task ran on behalf of. Resolved here rather
  // than threaded in, so this module keeps its single-parameter shape.
  const [owner] = await db.select({ id: users.id }).from(users).limit(1);
  const ownerUserId = owner?.id ?? null;

  const work = workSeeds(prefix);
  const subjectIds = new Map<TaskKey, string>();

  for (const item of work) {
    const id =
      item.subject.kind === 'account'
        ? (
            await db
              .select({ id: accounts.id })
              .from(accounts)
              .where(eq(accounts.domain, item.subject.domain))
              .limit(1)
          )[0]?.id
        : (
            await db
              .select({ id: contacts.id })
              .from(contacts)
              .where(eq(contacts.fullName, item.subject.fullName))
              .limit(1)
          )[0]?.id;
    if (id) subjectIds.set(item.task, id);
  }

  /*
   * Which of these rows are already here.
   *
   * The upserts below rewrite rather than skip, because the subject of a task
   * is an account or contact id and `--clear` gives the demo book new ids on
   * every teardown: a row that merely survived would keep pointing at a record
   * that no longer exists. Counting from this snapshot rather than from
   * `RETURNING` is what still distinguishes a fresh seed from a reseed once the
   * write became an update.
   */
  const existingTasks = new Set(
    (
      await db
        .select({ id: agentTasks.id })
        .from(agentTasks)
        .where(inArray(agentTasks.id, Object.values(TASK_ID)))
    ).map((row) => row.id),
  );
  const existingRuns = new Set(
    (
      await db
        .select({ id: agentRuns.id })
        .from(agentRuns)
        .where(inArray(agentRuns.id, Object.values(RUN_ID)))
    ).map((row) => row.id),
  );
  const existingActions = new Set(
    (
      await db
        .select({ id: agentActions.id })
        .from(agentActions)
        .where(inArray(agentActions.id, Object.values(ACTION_ID)))
    ).map((row) => row.id),
  );

  let tasksAdded = 0;
  let runsAdded = 0;
  let spentMicroCents = 0;

  for (const item of work) {
    const subjectId = subjectIds.get(item.task);
    if (!subjectId) continue;

    const queuedAt = at(item.daysAgo);
    const started = after(queuedAt, 5 + item.runs[0]!.startsAfter);
    const lastRun = item.runs[item.runs.length - 1]!;
    const finished = after(queuedAt, 5 + lastRun.endsAfter);

    const payload = { source: 'demo', subjectKind: item.subject.kind };
    await db
      .insert(agentTasks)
      .values({
        id: TASK_ID[item.task],
        kind: item.kind,
        subject: subjectId,
        reason: item.reason,
        payload,
        priority: item.priority,
        budget: 4,
        attempts: item.attempts,
        maxAttempts: 3,
        dueAt: queuedAt,
        // Null on both, because `AgentTaskQueue.succeed` clears the lease when
        // it stamps the outcome. A finished task holding a lease would be a
        // row no worker in this codebase could have written.
        leasedUntil: null,
        leasedBy: null,
        startedAt: started,
        finishedAt: finished,
        outcome: 'succeeded',
        // Also cleared: the queue nulls the previous attempt's error when the
        // task is re-claimed, so the rate-limited attempt survives on its run
        // rather than here.
        error: null,
        requestedByUserId: ownerUserId,
        createdAt: queuedAt,
      })
      .onConflictDoUpdate({
        target: agentTasks.id,
        set: { subject: subjectId, reason: item.reason, payload, requestedByUserId: ownerUserId },
      });
    if (!existingTasks.has(TASK_ID[item.task])) tasksAdded += 1;

    for (const run of item.runs) {
      const cost = costMicroCents(run.inputTokens, run.outputTokens);
      spentMicroCents += cost;
      const input = {
        kind: item.kind,
        subject: subjectId,
        reason: item.reason,
        payload,
      };
      await db
        .insert(agentRuns)
        .values({
          id: run.id,
          agentTaskId: TASK_ID[item.task],
          agent: 'piggy',
          principalUserId: ownerUserId,
          status: run.error ? 'failed' : 'succeeded',
          model: MODEL,
          inputTokens: run.inputTokens,
          outputTokens: run.outputTokens,
          costMicroCents: cost,
          input,
          result: { toolCallCount: run.toolCallCount },
          summary: run.summary,
          error: run.error ?? null,
          startedAt: after(queuedAt, 5 + run.startsAfter),
          finishedAt: after(queuedAt, 5 + run.endsAfter),
        })
        .onConflictDoUpdate({
          target: agentRuns.id,
          set: { input, principalUserId: ownerUserId },
        });
      if (!existingRuns.has(run.id)) runsAdded += 1;
    }
  }

  const seeds = factSeeds();
  let factsAdded = 0;
  let actionsAdded = 0;

  for (const [index, seed] of seeds.entries()) {
    const targetId = subjectIds.get(seed.task);
    if (!targetId) continue;
    const targetType = seed.accountDomain ? 'account' : 'contact';
    const evidence: Record<string, unknown> = {
      excerpt: seed.excerpt,
      taskId: TASK_ID[seed.task],
      taskReason: work.find((item) => item.task === seed.task)?.reason,
      ...seed.notes,
    };

    // Idempotent on the natural key: one claim per subject per field per value.
    const [existing] = await db
      .select({ id: facts.id })
      .from(facts)
      .where(
        and(
          targetType === 'account'
            ? eq(facts.accountId, targetId)
            : eq(facts.contactId, targetId),
          eq(facts.field, seed.field),
          eq(facts.value, seed.value),
        ),
      )
      .limit(1);

    let factId = existing?.id;
    if (existing) {
      // Converges a database seeded before the runs existed: the claim is
      // already there, its provenance is what was missing.
      await db
        .update(facts)
        .set({
          agentRunId: seed.runId,
          sourceUrl: seed.sourceUrl,
          method: seed.method,
          evidence,
        })
        .where(eq(facts.id, existing.id));
    } else {
      const [row] = await db
        .insert(facts)
        .values({
          ...(targetType === 'account' ? { accountId: targetId } : { contactId: targetId }),
          field: seed.field,
          value: seed.value,
          score: seed.score,
          band: seed.band,
          status: seed.status,
          method: seed.method,
          sourceUrl: seed.sourceUrl,
          evidence,
          agentRunId: seed.runId,
          observedAt: at(-1 - (index % 6)),
        })
        .returning({ id: facts.id });
      factId = row?.id;
      factsAdded += 1;
    }

    if (!factId) continue;

    const idempotencyKey = factIdempotencyKey(TASK_ID[seed.task], {
      targetType,
      targetId,
      field: seed.field,
      value: seed.value,
      sourceUrl: seed.sourceUrl,
      evidenceExcerpt: seed.excerpt,
    });
    const metadata = { taskId: TASK_ID[seed.task], field: seed.field, factId };
    await db
      .insert(agentActions)
      .values({
        id: ACTION_ID[seed.task],
        agentRunId: seed.runId,
        type: 'record_fact',
        targetType,
        targetId,
        summary: `${seed.field}: ${seed.value}`.slice(0, 500),
        idempotencyKey,
        status: 'completed',
        externalId: factId,
        metadata,
      })
      // A reseed, like a retried task, must leave one action per claim. The
      // unique key does that job in the product; here the id does it, because
      // the key itself moves when the subject is recreated with a new id.
      .onConflictDoUpdate({
        target: agentActions.id,
        set: { targetId, idempotencyKey, externalId: factId, metadata },
      });
    if (!existingActions.has(ACTION_ID[seed.task])) actionsAdded += 1;
  }

  const watch = await seedRenewalWatch(context, ownerUserId);
  if (watch) tasksAdded += 1;

  return {
    total: seeds.length,
    added: factsAdded,
    runs: runsAdded,
    tasks: tasksAdded,
    actions: actionsAdded,
    costMicroCents: spentMicroCents,
  };
}

/**
 * The one task that has not run yet.
 *
 * Its reason is computed from the ledger rather than written by hand, because
 * the whole argument for `watch_renewal` is that the agent is reading the same
 * arithmetic the margin page shows. A hand-typed sell-through would go stale
 * the moment somebody changed an allocation, and a wrong number here would be a
 * demonstration of the agent being confidently wrong.
 */
async function seedRenewalWatch(
  context: DemoContext,
  ownerUserId: string | null,
): Promise<boolean> {
  const { db, prefix, at } = context;

  // The EU H100 block: the one that is underwater, and therefore the one worth
  // watching to expiry.
  const [block] = await db
    .select({
      id: capacityCommitments.id,
      name: capacityCommitments.name,
      endsAt: capacityCommitments.endsAt,
      totalGpuHours: capacityCommitments.totalGpuHours,
      costPerGpuHourCents: capacityCommitments.costPerGpuHourCents,
    })
    .from(capacityCommitments)
    .innerJoin(accounts, eq(capacityCommitments.accountId, accounts.id))
    .where(
      and(
        eq(accounts.domain, 'nebius.com'),
        eq(capacityCommitments.gpuType, 'H100_80GB'),
        // Scoped to the demo book and ordered, because the base seed and other
        // fixtures put their own blocks on these accounts: an unordered pick
        // would watch a different commitment on a different day.
        like(capacityCommitments.name, `${prefix}%`),
      ),
    )
    .orderBy(capacityCommitments.startsAt)
    .limit(1);
  if (!block) return false;

  const sold = await db
    .select({
      gpuHours: allocations.gpuHours,
      pricePerGpuHourCents: allocations.pricePerGpuHourCents,
    })
    .from(allocations)
    .where(
      and(
        eq(allocations.capacityCommitmentId, block.id),
        inArray(allocations.status, [...CONSUMING_ALLOCATION_STATUSES]),
      ),
    );

  const committedHours = Number(block.totalGpuHours);
  const priced = sold.map((row) => ({
    gpuHours: Number(row.gpuHours),
    pricePerGpuHourCents: row.pricePerGpuHourCents,
  }));
  const soldHours = priced.reduce((sum, row) => sum + row.gpuHours, 0);
  const soldPct = committedHours > 0 ? Math.round((soldHours / committedHours) * 100) : 0;
  const breakEvenCents = breakEvenPricePerGpuHourCents(
    { gpuHours: committedHours, costPerGpuHourCents: block.costPerGpuHourCents ?? 0 },
    priced,
  );
  const daysToExpiry = Math.round((block.endsAt.getTime() - Date.now()) / 86_400_000);

  const breakEven =
    breakEvenCents === null
      ? 'nothing left to price'
      : `the remaining hours must fetch $${(breakEvenCents / 100).toFixed(2)}/GPU-hr to cover the block`;

  const [already] = await db
    .select({ id: agentTasks.id })
    .from(agentTasks)
    .where(eq(agentTasks.id, TASK_ID.renewal))
    .limit(1);

  const reason =
    `${prefix}${block.name.replace(prefix, '')} lapses in ${daysToExpiry} days at ` +
    `${soldPct}% sold; ${breakEven}. Re-check weekly until it is re-let or written down.`;
  const payload = {
    capacityCommitmentId: block.id,
    soldPct,
    daysToExpiry,
    breakEvenPricePerGpuHourCents: breakEvenCents === null ? null : Math.round(breakEvenCents),
  };

  await db
    .insert(agentTasks)
    .values({
      id: TASK_ID.renewal,
      kind: 'watch_renewal',
      subject: block.id,
      reason,
      payload,
      // Above the enrichment work: a block running out of term is time-boxed in
      // a way that a blank firmographic field is not.
      priority: 8,
      budget: 4,
      attempts: 0,
      maxAttempts: 3,
      dueAt: at(2),
      // Unclaimed and unstarted, which is the state the queue leaves a row in
      // until a worker leases it.
      leasedUntil: null,
      leasedBy: null,
      startedAt: null,
      finishedAt: null,
      outcome: null,
      requestedByUserId: ownerUserId,
      createdAt: at(-1),
    })
    .onConflictDoUpdate({
      target: agentTasks.id,
      // The block is re-created with a new id by `--clear`, and the sell-through
      // moves whenever an allocation does. Both belong on the row a worker would
      // read, not on the row that happened to be written first.
      set: { subject: block.id, reason, payload, requestedByUserId: ownerUserId, dueAt: at(2) },
    });

  return !already;
}
