/**
 * The demo book's Motion half: eight engagements, one at every open stage.
 *
 * The loop is the only reason Motion exists:
 *
 *     library template --instantiate--> engagement artefact --promote--> v2
 *
 * A screenshot of the library alone shows a folder of documents, which is not
 * the claim being made. So the book is laid out to prove three things a folder
 * cannot. **Every stage is occupied**, because the page that matters asks
 * whether the motion is repeating and a rail of zeroes cannot answer it.
 * **Scores move, and sometimes move down** — a single score is a number and
 * four are a trajectory; a scorecard that only ever rises is a ratchet, so the
 * Verity fine-tuning record deliberately goes 62.5 → 60.5 → 84.0 → 80.8, and
 * nine dimensions regress somewhere in the book. And **one engagement closes
 * the loop**, putting a version 2 into the library carrying `supersedes_id`
 * back to the shipped version 1 and `origin_artifact_id` back to the artefact
 * that proved it, so both halves of the FK cycle are populated on a first run.
 *
 * The artefact bodies are the point. An artefact whose body is the template
 * with the blanks still in it is exactly what this data exists to disprove, so
 * every one of them is written as the customer's own facts — named people, real
 * volumes, the specific thing going wrong. That is also why they live in JSON
 * beside this file rather than in TypeScript literals: forty-four markdown
 * bodies as backtick strings would be a module nobody can review.
 *
 * Every engagement hangs off a demand deal the demand book already created,
 * rather than a deal of its own. An engagement has no independent existence,
 * and inventing deals here would put rows on a pipeline whose counts are quoted
 * in `demo/index.ts`.
 *
 * Scores are computed with `motionScoreBasisPoints` and banded with
 * `motionBand`, never written as literals. Hard-coding 8280 would let the seed
 * and the product disagree about what the same dimensions are worth, and the
 * first place anyone would notice is a demo.
 */
import {
  motionBand,
  motionScoreBasisPoints,
  type ArtifactStatus,
  type DemandStage,
  type EngagementStatus,
  type MotionDimensionScore,
  type MotionKind,
} from '@pig/core';
import { and, eq, like, sql } from 'drizzle-orm';
import {
  demandDeals,
  engagementArtifacts,
  engagements,
  motionTemplates,
  qualificationScores,
  users,
} from '../../schema/index';
import type { DemoContext } from './index';

import aurelianCommittedCapacity from './motion/aurelian-committed-capacity.json' with { type: 'json' };
import halcyonPostTraining from './motion/halcyon-post-training.json' with { type: 'json' };
import northwindBlackwell from './motion/northwind-blackwell.json' with { type: 'json' };
import northwindFleetExpansion from './motion/northwind-fleet-expansion.json' with { type: 'json' };
import quillonEvalHarness from './motion/quillon-eval-harness.json' with { type: 'json' };
import tessellateBurstInference from './motion/tessellate-burst-inference.json' with { type: 'json' };
import verityEuFineTuning from './motion/verity-eu-fine-tuning.json' with { type: 'json' };
import verityEuInference from './motion/verity-eu-inference.json' with { type: 'json' };

interface DemoArtefact {
  readonly templateSlug: string | null;
  /*
   * Carried on the artefact rather than looked up from its template, because
   * six of the forty-four have no template at all — the work a lead does that
   * no library entry covers, which is the honest shape of an engagement. A
   * `kind` derived from the template would have been null for exactly those
   * six, and `engagement_artifacts.kind` is NOT NULL.
   */
  readonly kind: MotionKind;
  readonly stage: DemandStage;
  readonly title: string;
  readonly status: ArtifactStatus;
  readonly authoredDaysAgo: number;
  readonly body: string;
}

interface DemoScore {
  readonly daysAgo: number;
  readonly note: string;
  readonly dimensions: readonly MotionDimensionScore[];
}

interface DemoEngagement {
  readonly key: string;
  readonly dealName: string;
  readonly accountName: string;
  readonly stage: DemandStage;
  readonly status: EngagementStatus;
  readonly summary: string;
  readonly openedDaysAgo: number;
  readonly artefacts: readonly DemoArtefact[];
  readonly scores: readonly DemoScore[];
  readonly promote: { readonly artefactTitle: string; readonly intoSlug: string } | null;
}

/**
 * Ordered oldest-opened first, so the engagement list reads as a book somebody
 * built up rather than eight things that appeared at once.
 */
const DEMO_ENGAGEMENTS = [
  northwindFleetExpansion,
  northwindBlackwell,
  halcyonPostTraining,
  verityEuFineTuning,
  verityEuInference,
  aurelianCommittedCapacity,
  tessellateBurstInference,
  quillonEvalHarness,
] as unknown as readonly DemoEngagement[];

/**
 * Which demo seller owns which account.
 *
 * Assigned by account rather than by engagement, because the two accounts that
 * appear twice (Verity Health AI, Northwind Robotics) are one relationship each
 * — a second engagement handed to a different owner would contradict the
 * continuity the artefacts themselves describe.
 */
const OWNER_BY_ACCOUNT: Readonly<Record<string, string>> = {
  'Quillon AI': 'Wren Abbot',
  'Aurelian Systems': 'Marcus Oyelaran',
  'Verity Health AI': 'Ines Fabre',
  'Tessellate Labs': 'Rosalind Achebe',
  'Halcyon Research': 'Ines Fabre',
  'Northwind Robotics': 'Marcus Oyelaran',
};

async function findDeal(context: DemoContext, name: string): Promise<string | undefined> {
  const [deal] = await context.db
    .select({ id: demandDeals.id })
    .from(demandDeals)
    .where(eq(demandDeals.name, name))
    .limit(1);
  return deal?.id;
}

async function findSeller(context: DemoContext, name: string): Promise<string | null> {
  const [seller] = await context.db
    .select({ id: users.id })
    .from(users)
    .where(eq(users.name, `${context.prefix}${name}`))
    .limit(1);
  return seller?.id ?? null;
}

/** The shipped version 1 of a lineage. Promotion needs its id and its version. */
async function findTemplate(
  context: DemoContext,
  slug: string,
): Promise<{ id: string; version: number; summary: string } | undefined> {
  const [template] = await context.db
    .select({
      id: motionTemplates.id,
      version: motionTemplates.version,
      summary: motionTemplates.summary,
    })
    .from(motionTemplates)
    .where(and(eq(motionTemplates.slug, slug), eq(motionTemplates.isSystem, true)))
    .limit(1);
  return template;
}

export async function seedMotionEngagements(
  context: DemoContext,
): Promise<{
  engagements: number;
  artifacts: number;
  scores: number;
  promoted: number;
  missingDeals: readonly string[];
}> {
  const { db, prefix, at } = context;

  /*
   * The whole section is skipped if any demo engagement already exists.
   *
   * `engagements_demand_deal_key` would refuse a duplicate engagement, but
   * nothing would refuse a second set of artefacts or — worse — a second
   * promotion, and a second promotion writes case-study-frame v3 on top of the
   * v2 this seed already created. `qualification_scores` is append-only by
   * design and so has no constraint to conflict on at all; nineteen more rows
   * on every run would turn every trajectory into noise. An existence check on
   * the parent is the only thing that covers all three.
   */
  const existing = await db
    .select({ id: engagements.id })
    .from(engagements)
    .where(like(engagements.summary, `${prefix}%`))
    .limit(1);
  if (existing.length > 0) {
    return { engagements: 0, artifacts: 0, scores: 0, promoted: 0, missingDeals: [] };
  }

  const playbook = await findTemplate(context, 'strategic-deployment-playbook');

  const templateCache = new Map<string, { id: string; version: number; summary: string }>();
  const templateBySlug = async (slug: string) => {
    if (!templateCache.has(slug)) {
      const found = await findTemplate(context, slug);
      if (found) templateCache.set(slug, found);
    }
    return templateCache.get(slug);
  };

  const usageByTemplate = new Map<string, number>();
  const missingDeals: string[] = [];
  let engagementCount = 0;
  let artifactCount = 0;
  let scoreCount = 0;
  let promotedCount = 0;

  for (const demo of DEMO_ENGAGEMENTS) {
    /*
     * A missing deal is reported, not skipped in silence.
     *
     * Every engagement hangs off a deal the demand book creates, and that book
     * skips an account it has already seen — so a database where the accounts
     * survive but the deals were removed leaves these lookups empty and this
     * whole section a no-op that reports zeroes and looks like a bug in the
     * loader. It was found exactly that way, on a shared development database.
     */
    const dealId = await findDeal(context, demo.dealName);
    if (!dealId) {
      missingDeals.push(demo.dealName);
      continue;
    }

    const ownerName = OWNER_BY_ACCOUNT[demo.accountName.replace(prefix, '')];
    const ownerId = ownerName ? await findSeller(context, ownerName) : null;

    const [engagement] = await db
      .insert(engagements)
      .values({
        demandDealId: dealId,
        playbookTemplateId: playbook?.id ?? null,
        ownerUserId: ownerId,
        status: demo.status,
        summary: demo.summary,
        openedAt: at(-demo.openedDaysAgo),
        closedAt: demo.status === 'won' ? at(-Math.floor(demo.openedDaysAgo / 8)) : null,
      })
      .returning({ id: engagements.id });
    if (!engagement) continue;
    engagementCount += 1;

    const artefactIdByTitle = new Map<string, { id: string; body: string }>();
    for (const artefact of demo.artefacts) {
      const template = artefact.templateSlug ? await templateBySlug(artefact.templateSlug) : undefined;
      const [written] = await db
        .insert(engagementArtifacts)
        .values({
          engagementId: engagement.id,
          templateId: template?.id ?? null,
          kind: artefact.kind,
          stage: artefact.stage,
          title: artefact.title,
          body: artefact.body,
          status: artefact.status,
          authoredByUserId: ownerId,
          createdAt: at(-artefact.authoredDaysAgo),
          updatedAt: at(-artefact.authoredDaysAgo),
        })
        .returning({ id: engagementArtifacts.id });
      if (!written) continue;
      artifactCount += 1;
      artefactIdByTitle.set(artefact.title, { id: written.id, body: artefact.body });
      if (template) {
        usageByTemplate.set(template.id, (usageByTemplate.get(template.id) ?? 0) + 1);
      }
    }

    for (const score of demo.scores) {
      const basisPoints = motionScoreBasisPoints(score.dimensions);
      await db.insert(qualificationScores).values({
        engagementId: engagement.id,
        frameworkTemplateId: (await templateBySlug('trainability-qualification'))?.id ?? null,
        /*
         * Copied, not referenced. `qualification_scores.dimensions` stores the
         * framework AS IT WAS SCORED — weights included — so that re-authoring
         * the scorecard later cannot retroactively change what a past score
         * meant. Handing the same array to every row would make that snapshot
         * a shared object.
         */
        dimensions: score.dimensions.map((dimension) => ({ ...dimension })),
        basisPoints,
        band: motionBand(basisPoints).label,
        note: score.note,
        scoredByUserId: ownerId,
        scoredAt: at(-score.daysAgo),
      });
      scoreCount += 1;
    }

    /*
     * The promotion, written by hand rather than by calling the API service:
     * the seed has no HTTP surface and no principal. It follows the same rules
     * `promoteArtifact` does — version = newest + 1, `supersedesId` to the
     * shipped v1, `originArtifactId` to the artefact, **`body` copied from the
     * artefact verbatim**, `visibility: 'shared'`, `isSystem: false` — because
     * a demo that produced a row the real path could not have produced would
     * teach the wrong shape of the table.
     *
     * `isSystem` is false and the title is prefixed, which is what lets
     * `clear()` find it. It is a version 2 of a shipped lineage, so removing
     * the demo book genuinely returns the library to what ships.
     */
    if (demo.promote) {
      const artefact = artefactIdByTitle.get(demo.promote.artefactTitle);
      const supersedes = await templateBySlug(demo.promote.intoSlug);
      if (artefact && supersedes) {
        const [promoted] = await db
          .insert(motionTemplates)
          .values({
            kind: 'case_study',
            stage: 'expansion',
            slug: demo.promote.intoSlug,
            version: supersedes.version + 1,
            title: demo.promote.artefactTitle,
            summary:
              `Version ${supersedes.version + 1}, promoted out of the ` +
              `${demo.accountName.replace(prefix, '')} expansion. The frame as it was actually ` +
              'filled in, with the numbers the customer approved for named use.',
            body: artefact.body,
            fields: { promotedFrom: demo.promote.artefactTitle, engagement: demo.key },
            visibility: 'shared',
            ownerUserId: ownerId,
            supersedesId: supersedes.id,
            originArtifactId: artefact.id,
            isSystem: false,
          })
          .onConflictDoNothing({ target: [motionTemplates.slug, motionTemplates.version] })
          .returning({ id: motionTemplates.id });

        if (promoted) {
          await db
            .update(engagementArtifacts)
            .set({ promotedTemplateId: promoted.id })
            .where(eq(engagementArtifacts.id, artefact.id));
          promotedCount += 1;
        }
      }
    }
  }

  /*
   * `usage_count` is raised once per template at the end rather than once per
   * artefact inside the loop, because it is the counter that makes a template
   * permanently un-editable and `clear()` gives back exactly what was borrowed.
   * One statement per template keeps the two halves symmetrical.
   */
  for (const [templateId, count] of usageByTemplate) {
    await db
      .update(motionTemplates)
      .set({ usageCount: sql`${motionTemplates.usageCount} + ${count}` })
      .where(eq(motionTemplates.id, templateId));
  }

  return {
    engagements: engagementCount,
    artifacts: artifactCount,
    scores: scoreCount,
    promoted: promotedCount,
    missingDeals,
  };
}
