/**
 * Demo dataset — a plausible book of business, for development and demos.
 *
 * Separate from `../index.ts` (which seeds publicly-sourced, cited people)
 * because this is **invented**. It exists so the product is legible before
 * anyone has entered real data: margin that moves, blocks at different
 * utilisation, deals spread across both pipelines, contracts with real
 * structure.
 *
 * Two integrity rules, and they are not fussiness:
 *
 *   **Every record is prefixed `DEMO —`.** A screenshot of this must never be
 *   mistakeable for real business.
 *
 *   **Demand-side customers are fictional.** Suppliers are real companies —
 *   they are public, and naming the actual market is the point — but their
 *   commitments are labelled and the prices are illustrative. Inventing
 *   *customers* with invented contract values against real named companies
 *   would be fabricating commercial records about real businesses, which is a
 *   different thing entirely and not worth the realism.
 *
 * Remove it all with `pnpm db:demo -- --clear`.
 *
 * The numbers are chosen to teach. The book as a whole clears a modest margin —
 * roughly what this industry actually earns once capacity cost is charged
 * honestly — while individual blocks tell different stories:
 *
 *   the large H200 block carries the book;
 *   the EU H100 block is UNDERWATER at 55% sold, because a 28% markup needs
 *     ~78% sold to break even at all;
 *   the community A100 pool has a large hold that has not converted, so it
 *     shows as reserved-but-unsold — the distinction between "sold" and "held"
 *     made visible rather than theoretical.
 *
 * A demo that opens on a healthy total and reveals the problems on drill-down
 * is more useful than one that opens on a loss, which reads as a broken
 * product rather than an under-utilised book.
 *
 * ---
 *
 * This file is the orchestrator and the home of everything the slices share.
 * The book itself is one module per slice — supply, demand, contracts,
 * activities, compliance, agent facts, learn, calendar, teardown — because it
 * was a single 1,400-line file that several people needed to edit at once, and
 * every edit collided. The order of the inserts below is the order it has
 * always run in; sections depend on ids the earlier ones return.
 */
import { quarterBoundsFor } from '@pig/core';
import { sql } from 'drizzle-orm';
import { createDatabase, type Database } from '../../client';
import { capacityCommitments, demandDeals, supplyDeals, users } from '../../schema/index';
import { seedSupplyActivities } from './activities';
import { seedFacts } from './agent';
import { seedCalendar } from './calendar';
import { seedCompliance } from './compliance';
import { seedDemandPaper } from './contracts';
import { seedDemand } from './demand';
import { seedHostedLearn } from './learn';
import { seedMotionEngagements } from './motion';
import { seedSupply } from './supply';

const db = createDatabase();
const PREFIX = 'DEMO — ';

const day = 86_400_000;
const now = Date.now();
const at = (days: number) => new Date(now + days * day);

/**
 * Dates are placed by QUARTER, and deterministically.
 *
 * This file used to scatter close dates with `at(20 + Math.random() * 60)`,
 * which put the whole book in one arbitrary bucket, differently on every run —
 * so the quarterly view could not be demonstrated and the CI seed-idempotency
 * gate was one unlucky reseed away from a false failure. Placement is now
 * deliberate: something in the quarter just gone, several in the one we are
 * in, and a couple in the next, so the calendar has all three states to show.
 */
const thisQuarter = quarterBoundsFor(new Date(now));

function quarterAt(offset: -1 | 0 | 1, fraction: number): Date {
  const bounds =
    offset === 0
      ? thisQuarter
      : quarterBoundsFor(
          new Date(
            offset < 0 ? thisQuarter.from.getTime() - 1 : thisQuarter.to.getTime(),
          ),
        );
  const span = bounds.to.getTime() - bounds.from.getTime();
  return new Date(bounds.from.getTime() + Math.round(span * fraction));
}

/** GPU-hours for a block, allowing for a maintenance/ramp haircut. */
const hours = (gpus: number, days: number, efficiency = 0.94) =>
  String(Math.round(gpus * 24 * days * efficiency));

/**
 * What every module of the demo seed is handed, and why it is a parameter
 * rather than an import.
 *
 * The modules under `demo/` deliberately import no *value* from this file. If
 * they did, ES module evaluation would run their bodies before this one, so a
 * constant declared at module scope with `${prefix}` in it would read PREFIX
 * before it was initialised and the whole seed would die in the temporal dead
 * zone — a failure that would appear only when someone added an innocent
 * top-level constant to one of the slices. Passing the shared pieces down makes
 * that impossible to reintroduce, whichever module a later change lands in.
 */
export interface DemoContext {
  readonly db: Database;
  /** `DEMO — `. Every invented record carries it; `clear()` matches on it. */
  readonly prefix: string;
  /** A date relative to the instant the seed started, fixed for the whole run. */
  readonly at: (days: number) => Date;
  /** GPU-hours for a block, allowing for a maintenance/ramp haircut. */
  readonly hours: (gpus: number, days: number, efficiency?: number) => string;
  /** A point inside the previous, current or next quarter. See `quarterAt`. */
  readonly quarterAt: (offset: -1 | 0 | 1, fraction: number) => Date;
}

/**
 * The one context the commands run against, and therefore the one connection
 * pool. Built here rather than per module so that importing two slices cannot
 * open two pools against the same database.
 */
export const demoContext: DemoContext = { db, prefix: PREFIX, at, hours, quarterAt };

export async function seedDemo(context: DemoContext): Promise<void> {
  console.log('Seeding the demo book…\n');

  // The id map every later section joins against: one capacity commitment per
  // supplier domain, which is how the demand book names the block it draws on.
  const commitmentIds = await seedSupply(context);

  await seedDemand(context, commitmentIds);

  // Customer paper is written after the demand book, because it reads the
  // accounts, deals and allocations that pass creates. Without it the renewal
  // signals the Growth page is built around have nothing to fire on.
  await seedDemandPaper(context);

  const compliance = await seedCompliance(context);

  // The supply-side timeline, the closed deals' histories, and the sweep that
  // restamps lastActivityAt from the activities themselves. It must run after
  // every section that creates an account or a deal, because it both rescues
  // records that would otherwise have an empty timeline and restamps
  // lastActivityAt from what it can see. Compliance creates three demand
  // accounts of its own, so running before it left those three with no history
  // at all on a first run — and a second run of the demo seed then added the
  // missing 21 activities, which is why the book only settled after two runs.
  // Compliance neither reads nor writes activities, so nothing moves the other
  // way.
  await seedSupplyActivities(context);

  // Resolved once, here, because the calendar and the learn library must agree
  // on who owns their rows; two lookups would be two chances to disagree.
  // Ordered by creation, not arbitrary: the demand book now seeds its own
  // sellers, and an unordered limit(1) would hand the calendar and the learn
  // library to whichever of them Postgres returned first.
  const [owner] = await context.db
    .select({ id: users.id })
    .from(users)
    .orderBy(users.createdAt)
    .limit(1);
  const ownerUserId = owner?.id ?? null;

  const calendar = await seedCalendar(context, ownerUserId);

  // The self-hosted set, which is real rather than invented — see the long
  // note on `seedHostedLearn`. Folded into the demo seed so one command gives
  // a complete Learn page, but kept in its own function with its own flags
  // because it is not demo data and must not be removed with `--clear`.
  const hosted = await seedHostedLearn(context);

  // After the demand book, which owns the deals both engagements hang off, and
  // after the base seed has put the starter library in place — an engagement
  // whose artifacts came from nothing would demonstrate the folder rather than
  // the loop. Run `pnpm db:seed` before `pnpm db:demo`, as the README says.
  const motion = await seedMotionEngagements(context);

  const facts = await seedFacts(context);

  /*
   * Counted, not asserted. These lines were hardcoded when the book was split
   * into modules, and they drifted the moment the seed grew: the summary
   * claimed 12 demand deals and 5 commitments against a database holding 13
   * and 6. A seed that misreports what it wrote teaches an operator to
   * distrust the only feedback the command gives them.
   */
  const [written] = await context.db
    .select({
      commitments: sql<number>`(select count(*)::int from ${capacityCommitments})`,
      demandDeals: sql<number>`(select count(*)::int from ${demandDeals})`,
      supplyDeals: sql<number>`(select count(*)::int from ${supplyDeals})`,
      demandStages: sql<number>`(select count(distinct stage)::int from ${demandDeals})`,
    })
    .from(sql`(select 1) as one`);
  const book = written ?? { commitments: 0, demandDeals: 0, supplyDeals: 0, demandStages: 0 };

  console.log(`  ${book.commitments} capacity commitments, with sites, MSAs and negotiated SLAs`);
  console.log(`  ${facts.total} agent-derived facts (${facts.added} new) — 2 applied, 4 awaiting review`);
  console.log(
    `  ${facts.tasks} agent tasks and ${facts.runs} Piggy runs, ${facts.actions} idempotency-keyed actions, ` +
      `${(facts.costMicroCents / 1_000_000).toFixed(4)} cents of model spend`,
  );
  console.log(
    `  ${book.demandDeals} demand deals across ${book.demandStages} stages — including a won parent with its ` +
      `expansion child, and a loss with a reason — and ${book.supplyDeals} supply deals`,
  );
  console.log('  Allocations including one unconverted hold and internal research burn');
  console.log(
    '  Close dates placed deliberately in the previous, current and next quarter',
  );
  console.log(
    `  ${compliance.authorizations.total} export authorisations (${compliance.authorizations.added} new) — ` +
      `one lapsed, one expiring this quarter, ${compliance.artifacts.total} compliance artefacts, ` +
      `${compliance.decisions.total} export-control decisions (allow, needs_review, block), ` +
      `${calendar.total} calendar entries (${calendar.added} new)`,
  );
  console.log(
    `  ${motion.engagements} Motion engagement(s) with ${motion.artifacts} artefact(s) and ` +
      `${motion.scores} qualification score(s) — ${motion.promoted} artefact promoted back into ` +
      'the library as a version 2',
  );
  if (motion.missingDeals.length > 0) {
    console.warn(
      `  ${motion.missingDeals.length} Motion engagement(s) had no deal to hang off and were ` +
        `SKIPPED: ${motion.missingDeals.join(', ')}. Run \`pnpm db:demo -- --clear\` and reseed ` +
        'to rebuild the demand book.',
    );
  }
  console.log(
    `  ${hosted.present} PIG-hosted learn videos (${hosted.added} new)` +
      `${hosted.missing > 0 ? `, ${hosted.missing} manifest entries with no file yet` : ''}`,
  );
  console.log('\nEverything is prefixed "DEMO — ". Remove it with: pnpm db:demo -- --clear');
  console.log('The PIG-hosted rows are NOT prefixed and survive that. Remove them with: pnpm db:demo -- --clear-hosted');
}

export { clear } from './clear';
export { HOSTED_LEARN_MANIFEST, clearHostedLearn, seedHostedLearn } from './learn';
