/**
 * Motion — the two rules that make this a system, and the one departure that
 * makes it a risk.
 *
 * The departure first. `permissions.ts` states that every read endpoint returns
 * the whole book because no row-level filter exists anywhere in the query
 * layer; motion templates are the first exception, and an exception that is
 * only enforced by the UI is not an exception, it is a leak with a nice screen
 * in front of it. So the first suite renders the actual predicate to SQL and
 * asserts on it. It looks like a test of an implementation detail and is not:
 * the WHERE clause IS the access policy, and the two ways of getting it wrong —
 * dropping the owner comparison, or widening it with `OR owner IS NULL` — both
 * produce a query that returns rows and reports nothing.
 *
 * The rest drive the exported mutation definitions against a scripted
 * transaction, in the register of `activities.test.ts`. The fake answers
 * queries in call order and records what ran; it is deliberately not a
 * database, because a fake that pretends to run SQL is a fake that will one day
 * assert a broken query works.
 *
 * That fake is also why the last suite goes through `createApp` instead. A
 * definition driven directly, or a route mounted by this file's own `mounted`,
 * passes whether or not `app.ts` ever calls `createMotionRoutes` — the state
 * `read-guards.ts` and `learn.ts` were both in while their tests were green.
 */
import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import { getTableName, isSQLWrapper, isTable, type SQL } from 'drizzle-orm';
import { PgDialect } from 'drizzle-orm/pg-core';
import { motionBand, motionScoreBasisPoints } from '@pig/core';
import type { Database, MotionTemplate } from '@pig/db';
import { teamMemberships, users } from '@pig/db';
import { Hono } from 'hono';
import { createApp } from '../src/app';
import { AuthError, type Principal } from '../src/lib/auth';
import type { AuthProvider } from '../src/lib/auth-provider';
import { loadConfig } from '../src/lib/config';
import { executeMutation, MutationError, type ApiEnv } from '../src/lib/mutation';
import {
  createMotionRoutes,
  motionArtifactCreateDefinition,
  motionArtifactPromoteDefinition,
  motionEngagementCreateDefinition,
  motionEngagementUpdateDefinition,
  motionScoreDefinition,
  motionTemplateCreateDefinition,
  motionTemplatePublishDefinition,
  motionTemplateUpdateDefinition,
  motionTemplateVersionDefinition,
} from '../src/routes/motion';
import { MotionService, motionSlug, visibleTemplates } from '../src/services/motion';
import { onTeam, principal } from './helpers/principal';

const OWNER = '00000000-0000-4000-8000-000000000001';
const OTHER = '00000000-0000-4000-8000-0000000000aa';
const DEAL_ID = '00000000-0000-4000-8000-0000000000d1';
const ACCOUNT_ID = '00000000-0000-4000-8000-0000000000ac';
const ENGAGEMENT_ID = '00000000-0000-4000-8000-0000000000e1';
const ARTIFACT_ID = '00000000-0000-4000-8000-0000000000f1';
const TEMPLATE_ID = '00000000-0000-4000-8000-0000000000b1';
const NOW = new Date('2026-08-17T09:00:00.000Z');

/** A demand member: `motion:write`, no `motion:publish`. */
const member = principal(onTeam('demand', 'member'));
/** A demand lead: both, which is the point of the split. */
const lead = principal(onTeam('demand', 'lead'));

// --------------------------------------------------------------- the fixtures

function template(overrides: Partial<MotionTemplate> = {}): MotionTemplate {
  return {
    id: TEMPLATE_ID,
    kind: 'poc',
    slug: 'poc-plan',
    version: 1,
    title: 'POC plan',
    summary: 'What a proof of concept must show.',
    body: '# POC plan',
    fields: null,
    stage: 'poc',
    visibility: 'private',
    ownerUserId: OWNER,
    supersedesId: null,
    originArtifactId: null,
    isSystem: false,
    usageCount: 0,
    archivedAt: null,
    createdAt: NOW,
    updatedAt: NOW,
    ...overrides,
  };
}

const deal = { id: DEAL_ID, accountId: ACCOUNT_ID, name: 'DEMO — Northwind training' };
const engagement = { id: ENGAGEMENT_ID, demandDealId: DEAL_ID, status: 'open' };

function artifact(overrides: Record<string, unknown> = {}) {
  return {
    id: ARTIFACT_ID,
    engagementId: ENGAGEMENT_ID,
    templateId: null,
    kind: 'poc',
    stage: 'poc',
    title: 'Northwind POC plan',
    body: '# What we proved',
    fields: null,
    status: 'final',
    authoredByUserId: OWNER,
    promotedTemplateId: null,
    archivedAt: null,
    createdAt: NOW,
    updatedAt: NOW,
    ...overrides,
  };
}

// ------------------------------------------------------------------ the fake

interface Recorded {
  events: string[];
  inserted: { table: string; row: Record<string, unknown> }[];
  updated: { table: string; values: Record<string, unknown> }[];
}

/**
 * A transaction that answers selects from a script, in call order, and records
 * every write with the table it landed in. Order-dependence is the price of not
 * pretending to be Postgres, and it is what makes "no UPDATE ran" assertable.
 */
function database(script: unknown[][]): { db: Database; log: Recorded } {
  const log: Recorded = { events: [], inserted: [], updated: [] };
  const results = [...script];
  const next = (): unknown[] => results.shift() ?? [];

  const selectChain = {
    from: () => selectChain,
    innerJoin: () => selectChain,
    leftJoin: () => selectChain,
    where: () => selectChain,
    orderBy: () => selectChain,
    groupBy: () => selectChain,
    // `limit` returns the chain rather than a promise so that `.for('update')`
    // can follow it, as it does on every query in this feature that allocates
    // a version number. The chain is a thenable, so `await` still ends it.
    limit: () => selectChain,
    // Recorded, not merely tolerated: two of the rules here are held shut by a
    // row lock, and a lock that quietly stops being taken changes nothing a
    // behavioural assertion can see.
    for: (strength: string) => {
      log.events.push(`for:${strength}`);
      return selectChain;
    },
    then: (resolve: (rows: unknown[]) => unknown) => resolve(next()),
  };

  const name = (table: unknown): string => (isTable(table) ? getTableName(table) : 'unknown');

  const tx = {
    select: () => {
      log.events.push('select');
      return selectChain;
    },
    insert: (table: unknown) => ({
      values: (row: Record<string, unknown>) => {
        log.events.push(`insert:${name(table)}`);
        log.inserted.push({ table: name(table), row });
        const written = [{ id: `${name(table)}-${log.inserted.length}`, ...row }];
        return {
          returning: async () => written,
          then: (resolve: (value: unknown) => unknown) => resolve(undefined),
        };
      },
    }),
    update: (table: unknown) => ({
      set: (values: Record<string, unknown>) => ({
        where: () => {
          log.events.push(`update:${name(table)}`);
          log.updated.push({ table: name(table), values });
          return Object.assign(Promise.resolve(undefined), {
            returning: async () => [{ id: `${name(table)}-updated`, ...values }],
          });
        },
      }),
    }),
  };

  return {
    db: {
      transaction: async (work: (t: unknown) => Promise<unknown>) => {
        log.events.push('transaction');
        return work(tx);
      },
      // The same scripted chain outside a transaction, so a read handler can be
      // driven through the mounted routes rather than only its service.
      select: tx.select,
    } as unknown as Database,
    log,
  };
}

/** The motion routes with a principal already resolved, as `createApp` mounts them. */
function mounted(db: Database, who: Principal): Hono<ApiEnv> {
  const app = new Hono<ApiEnv>();
  app.use('*', async (c, next) => {
    c.set('principal', who);
    await next();
  });
  app.route('/', createMotionRoutes(db));
  return app;
}

function rendered(userId: string, isPlatformAdmin = false) {
  const predicate = visibleTemplates({ userId, isPlatformAdmin });
  return predicate ? new PgDialect().sqlToQuery(predicate) : null;
}

// ------------------------------------------------------------------- the tests

describe('the private library filter is a WHERE clause, not an affordance', () => {
  it('matches shared rows for everyone and private rows only against the viewer id', () => {
    const query = rendered(OWNER);

    assert.ok(query, 'a member must be filtered at all');
    assert.match(query.sql, /"visibility" = \$1/);
    assert.match(query.sql, /"owner_user_id" = \$3/);
    assert.deepEqual(query.params, ['shared', 'private', OWNER]);
  });

  it('never widens to `owner_user_id IS NULL` — an orphaned private draft belongs to nobody', () => {
    const query = rendered(OWNER);

    // `ON DELETE SET NULL` on the owner column can produce a private row with
    // no owner. Matching it here would publish every departed colleague's
    // drafts to the whole workspace, and the query would look like a fix for
    // rows that had "gone missing".
    assert.ok(query);
    assert.doesNotMatch(query.sql, /owner_user_id" is null/i);
  });

  it('is deliberately absent for a platform admin — an admin CAN read a private template', () => {
    // Not a hole. An unfiltered query here is the decision: someone has to be
    // able to answer "what is in this workspace" during an audit or a
    // departure. If this ever starts returning a predicate, that was a choice
    // somebody made, and this test is where they say so.
    assert.equal(rendered(OTHER, true), null);
  });
});

describe('a slug is the identity of a lineage', () => {
  it('strips the combining mark NFKD leaves behind rather than hyphenating through a word', () => {
    // `normalize('NFKD')` splits "é" into "e" plus a combining acute, and the
    // `[^a-z0-9]+` rule that follows turns that mark into a separator — so
    // without the strip, "Café strategy" starts the lineage `caf-e-strategy`
    // and the next person authoring the same title cannot find it.
    assert.equal(motionSlug('Café strategy'), 'cafe-strategy');
    assert.equal(motionSlug('Proposal Blocks'), 'proposal-blocks');
    // Nothing latin survives, and an empty slug would violate the NOT NULL.
    assert.equal(motionSlug('日本語'), 'untitled');
  });
});

describe('a used template is never edited in place', () => {
  it('refuses a PATCH once usage_count is above zero, and names the versions endpoint', async () => {
    const { db, log } = database([[template({ usageCount: 3 })]]);

    await assert.rejects(
      executeMutation(
        db,
        principal(onTeam('demand', 'member')),
        async () => ({ body: '# Rewritten' }),
        motionTemplateUpdateDefinition(),
        { id: TEMPLATE_ID },
      ),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'template_in_use' &&
        error.status === 409 &&
        error.message.includes('/versions'),
    );
    assert.deepEqual(log.updated, [], 'a live engagement must not have its template move underneath it');
  });

  it('edits freely while nobody has instantiated it — an unused template is still a draft', async () => {
    const { db, log } = database([[template({ usageCount: 0 })]]);

    await executeMutation(
      db,
      member,
      async () => ({ title: 'POC plan, tightened' }),
      motionTemplateUpdateDefinition(),
      { id: TEMPLATE_ID },
    );

    assert.equal(log.updated.length, 1);
    assert.equal(log.updated[0]?.table, 'motion_templates');
    assert.equal(log.updated[0]?.values.title, 'POC plan, tightened');
  });

  it('locks the row before it trusts usage_count, so a concurrent instantiation cannot be missed', async () => {
    const { db, log } = database([[template({ usageCount: 0 })]]);

    await executeMutation(
      db,
      member,
      async () => ({ body: '# Tightened' }),
      motionTemplateUpdateDefinition(),
      { id: TEMPLATE_ID },
    );

    // Read without the lock, `usage_count` is a number another transaction is
    // already moving: an instantiation copies the body and increments the
    // count while this PATCH, having seen zero, waits on the row and then
    // rewrites the body anyway — leaving an artefact whose `template_id` names
    // a template that no longer contains what it copied. `no key update`
    // rather than `update` because the row is a foreign-key target.
    assert.deepEqual(log.events.slice(0, 3), ['transaction', 'select', 'for:no key update']);
  });

  it('refuses an edit to somebody else\'s template even when it is shared — publishing is not donating', async () => {
    const { db, log } = database([[template({ visibility: 'shared', ownerUserId: OTHER })]]);

    await assert.rejects(
      executeMutation(
        db,
        member,
        async () => ({ body: '# Mine now' }),
        motionTemplateUpdateDefinition(),
        { id: TEMPLATE_ID },
      ),
      (error: unknown) => error instanceof AuthError && error.code === 'not_owner',
    );
    assert.deepEqual(log.updated, []);
  });
});

describe('publishing is a lead\'s judgement, and the owner\'s', () => {
  it('refuses a member without motion:publish before the row is read at all', async () => {
    const { db, log } = database([[template()]]);

    await assert.rejects(
      executeMutation(db, member, async () => ({}), motionTemplatePublishDefinition(), {
        id: TEMPLATE_ID,
      }),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
    assert.deepEqual(log.events, [], 'permission precedes the transaction, so nothing was queried');
  });

  it('refuses a lead flipping somebody else\'s private template to shared', async () => {
    const { db, log } = database([[template({ ownerUserId: OTHER })]]);

    await assert.rejects(
      executeMutation(db, lead, async () => ({}), motionTemplatePublishDefinition(), {
        id: TEMPLATE_ID,
      }),
      (error: unknown) => error instanceof AuthError && error.code === 'not_owner',
    );
    assert.deepEqual(log.updated, []);
  });

  it('gates creating straight into the shared library on the same capability', async () => {
    // The publish endpoint is not the only door into the shared library, so a
    // member who simply POSTs `visibility: 'shared'` must be refused too.
    const { db, log } = database([]);

    await assert.rejects(
      executeMutation(
        db,
        member,
        async () => ({
          kind: 'poc',
          title: 'POC plan',
          summary: 'What a POC must show.',
          body: '# POC',
          stage: 'poc',
          visibility: 'shared',
        }),
        motionTemplateCreateDefinition(),
      ),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
    assert.deepEqual(log.inserted, []);
  });

  it('creates a private template owned by the author, because an unowned private row is unreadable', async () => {
    const { db, log } = database([]);

    await executeMutation(
      db,
      member,
      async () => ({
        kind: 'poc',
        title: 'POC plan',
        summary: 'What a POC must show.',
        body: '# POC',
        stage: 'poc',
      }),
      motionTemplateCreateDefinition(),
    );

    const written = log.inserted.find((row) => row.table === 'motion_templates');
    assert.equal(written?.row.visibility, 'private');
    assert.equal(written?.row.ownerUserId, member.userId);
    assert.equal(written?.row.version, 1);
    assert.equal(written?.row.slug, 'poc-plan');
  });
});

describe('promotion is the loop', () => {
  function promote(db: Database, input: Record<string, unknown> = {}) {
    return executeMutation(
      db,
      lead,
      async () => input,
      motionArtifactPromoteDefinition(),
      { id: ARTIFACT_ID },
    );
  }

  it('refuses to promote an artifact that is already in the library', async () => {
    const { db, log } = database([[artifact({ promotedTemplateId: TEMPLATE_ID })]]);

    await assert.rejects(
      promote(db),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'already_promoted' &&
        error.status === 409,
    );
    assert.deepEqual(log.inserted, [], 'a second promotion would fork the lineage silently');
  });

  it('refuses to promote a draft — the library is what the next deployment copies', async () => {
    const { db, log } = database([[artifact({ status: 'draft' })]]);

    await assert.rejects(
      promote(db),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'artifact_not_final' &&
        error.status === 409,
    );
    assert.deepEqual(log.inserted, []);
  });

  it('writes a shared version pointing back at the artifact that proved it', async () => {
    const { db, log } = database([[artifact()], [{ engagement, deal }], []]);

    const result = (await promote(db, { slug: 'poc-plan' })) as {
      template: { version: number; visibility: string; originArtifactId: string };
    };

    assert.equal(result.template.version, 1);
    assert.equal(result.template.visibility, 'shared');
    assert.equal(result.template.originArtifactId, ARTIFACT_ID);
    assert.equal(
      log.updated.find((row) => row.table === 'engagement_artifacts')?.values.promotedTemplateId,
      'motion_templates-1',
    );
  });

  it('chains supersedes_id across three promotions of one lineage', async () => {
    let previous: { id: string; version: number } | null = null;
    const chain: { id: string; version: number; supersedesId: string | null }[] = [];

    for (let round = 0; round < 3; round += 1) {
      // The lineage is read twice and on purpose: once unfiltered for the
      // version number, once through the visibility filter for the row the new
      // version may claim to supersede.
      const lineage = previous ? [{ ...template(), ...previous, slug: 'poc-plan' }] : [];
      const { db } = database([
        [artifact({ id: `${ARTIFACT_ID}-${round}` })],
        [{ engagement, deal }],
        lineage,
        lineage,
      ]);

      const result = (await promote(db, { slug: 'poc-plan' })) as {
        template: { id: string; version: number; supersedesId: string | null };
      };
      chain.push({
        id: result.template.id,
        version: result.template.version,
        supersedesId: result.template.supersedesId,
      });
      previous = { id: result.template.id, version: result.template.version };
    }

    assert.deepEqual(
      chain.map((row) => row.version),
      [1, 2, 3],
    );
    assert.equal(chain[0]?.supersedesId, null, 'the first version supersedes nothing');
    assert.equal(chain[1]?.supersedesId, chain[0]?.id);
    assert.equal(chain[2]?.supersedesId, chain[1]?.id);
  });

  it('lands the audit row on the account of the deal that proved it', async () => {
    const { db, log } = database([[artifact()], [{ engagement, deal }], []]);

    await promote(db, { slug: 'poc-plan' });

    const activity = log.inserted.find((row) => row.table === 'activities')?.row;
    assert.equal(activity?.accountId, ACCOUNT_ID);
    assert.equal(activity?.demandDealId, DEAL_ID);
    assert.match(String(activity?.subject), /^Promoted to the library:/);
  });
});

describe('a lineage nobody can read never supplies content to one everybody can', () => {
  /**
   * The version number and the predecessor row are two different questions, and
   * answering both from one unfiltered query is the shape of the bug these pin.
   * Both suites script a lineage whose newest row is private to somebody else:
   * the unfiltered read finds v4, the filtered read finds nothing.
   */
  const PRIVATE_SUMMARY = 'Unreleased pricing: 40% floor, Northwind only.';
  /** Alice's v4, which the unfiltered read finds and the filtered read must not. */
  const hidden = () =>
    template({
      id: '00000000-0000-4000-8000-0000000000c4',
      version: 4,
      ownerUserId: OTHER,
      summary: PRIVATE_SUMMARY,
    });

  it('promotes past a private newest version without copying its summary or naming its id', async () => {
    const { db, log } = database([
      [artifact()],
      [{ engagement, deal }],
      [hidden()],
      [],
    ]);

    await executeMutation(db, lead, async () => ({ slug: 'poc-plan' }), motionArtifactPromoteDefinition(), {
      id: ARTIFACT_ID,
    });

    const written = log.inserted.find((row) => row.table === 'motion_templates')?.row;
    // v5, because the unique constraint is on the whole lineage and a private
    // fork still consumes a number.
    assert.equal(written?.version, 5);
    // But nothing else from that row. The summary fell back to the artifact's
    // own title, and the new version supersedes nothing it cannot show.
    assert.notEqual(written?.summary, PRIVATE_SUMMARY);
    assert.equal(written?.summary, artifact().title);
    assert.equal(written?.supersedesId, null);
  });

  it('forks a shared template without disclosing that private versions of it exist', async () => {
    const source = template({ id: TEMPLATE_ID, visibility: 'shared', ownerUserId: OTHER });
    const { db, log } = database([[source], [hidden()], []]);

    await executeMutation(db, member, async () => ({}), motionTemplateVersionDefinition(), {
      id: TEMPLATE_ID,
    });

    const written = log.inserted.find((row) => row.table === 'motion_templates')?.row;
    assert.equal(written?.version, 5, 'the number is allocated against the whole lineage');
    assert.equal(
      written?.supersedesId,
      source.id,
      'the edge points at the row that was actually forked, not at a private row the forker cannot fetch',
    );
  });

  it('refuses to promote an artifact whose source template is unreadable, rather than 404ing on the artifact', async () => {
    const { db, log } = database([
      [artifact({ templateId: TEMPLATE_ID })],
      [{ engagement, deal }],
      [],
    ]);

    await assert.rejects(
      executeMutation(db, lead, async () => ({}), motionArtifactPromoteDefinition(), {
        id: ARTIFACT_ID,
      }),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'source_template_unreadable' &&
        error.status === 409 &&
        error.message.includes('slug'),
    );
    assert.deepEqual(log.inserted, []);
  });
});

describe('a template id somebody sent is read before it is stored', () => {
  it('refuses a playbook the setter cannot read, so a private id cannot go book-wide on an engagement', async () => {
    const { db, log } = database([[{ engagement, deal }], []]);

    await assert.rejects(
      executeMutation(
        db,
        member,
        async () => ({ playbookTemplateId: TEMPLATE_ID }),
        motionEngagementUpdateDefinition(),
        { id: ENGAGEMENT_ID },
      ),
      (error: unknown) => error instanceof MutationError && error.status === 404,
    );
    assert.deepEqual(log.updated, [], 'an unreadable id must not reach the foreign key either');
  });
});

describe('a UI hint that disagrees with its endpoint is the hint that is wrong', () => {
  it('does not offer publish to a read-only key, which the endpoint would refuse for scope', async () => {
    const readOnlyLead = principal({ ...onTeam('demand', 'lead'), via: 'api_key', scopes: ['read'] });
    const mine = template({ ownerUserId: readOnlyLead.userId });
    const { db } = database([[mine], [mine]]);

    const response = await mounted(db, readOnlyLead).request(`/api/motion/templates/${TEMPLATE_ID}`);
    const body = (await response.json()) as { canPublish: boolean };

    assert.equal(response.status, 200);
    assert.equal(
      body.canPublish,
      false,
      'the button would 403 insufficient_scope, and a button that cannot work must not be offered',
    );
  });

  it('still offers it to the same lead on a full-scope session', async () => {
    const mine = template({ ownerUserId: lead.userId });
    const { db } = database([[mine], [mine]]);

    const response = await mounted(db, lead).request(`/api/motion/templates/${TEMPLATE_ID}`);
    const body = (await response.json()) as { canPublish: boolean };

    assert.equal(body.canPublish, true);
  });
});

describe('a slug collision is the caller\'s to fix, not a 500', () => {
  it('answers 409 naming the versions endpoint when the title derives a slug already in use', async () => {
    // "Proposal Blocks" is one of the nine shipped starter templates, so this
    // is the first thing a new author trips over rather than an edge case.
    const { db, log } = database([[{ version: 1 }]]);

    await assert.rejects(
      executeMutation(
        db,
        member,
        async () => ({
          kind: 'proposal',
          title: 'Proposal Blocks',
          summary: 'Reusable proposal language.',
          body: '# Blocks',
          stage: 'proposal',
        }),
        motionTemplateCreateDefinition(),
      ),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'template_slug_exists' &&
        error.status === 409 &&
        error.message.includes('/versions'),
    );
    assert.deepEqual(log.inserted, []);
  });
});

describe('instantiating a template', () => {
  it('increments usage_count in the same transaction that writes the artifact', async () => {
    const { db, log } = database([
      [{ engagement, deal }],
      [template({ usageCount: 2, visibility: 'shared', ownerUserId: OTHER })],
    ]);

    await executeMutation(
      db,
      member,
      async () => ({ templateId: TEMPLATE_ID }),
      motionArtifactCreateDefinition(),
      { id: ENGAGEMENT_ID },
    );

    // One transaction, both writes: a count that could be committed without the
    // artifact would close the template to edits for a use that never happened.
    assert.equal(log.updated[0]?.table, 'motion_templates');
    // And the count is incremented by the database, not by JS arithmetic on the
    // row that was read a moment ago. Two people instantiating one template
    // under READ COMMITTED both read 2 and both write 3, so a use is lost — and
    // `usage_count` is the only thing holding §7a shut. Asserting the rendered
    // SQL is what makes the difference between the two visible at all: the
    // wrong version writes the literal 3 and passes every behavioural test.
    const increment = log.updated[0]?.values.usageCount;
    assert.ok(
      isSQLWrapper(increment),
      'usage_count must be written as an expression, never as a number computed in JS',
    );
    assert.match(new PgDialect().sqlToQuery(increment as SQL).sql, /"usage_count" \+ \$?1/);
    assert.equal(log.inserted[0]?.table, 'engagement_artifacts');
    assert.equal(log.inserted[0]?.row.templateId, TEMPLATE_ID);
    // The body is copied, not referenced — editing the artifact must not reach
    // back into the library.
    assert.equal(log.inserted[0]?.row.body, template().body);
  });

  it('holds the template row while it copies the body, not only while it counts the use', async () => {
    const { db, log } = database([
      [{ engagement, deal }],
      [template({ visibility: 'shared', ownerUserId: OTHER })],
    ]);

    await executeMutation(
      db,
      member,
      async () => ({ templateId: TEMPLATE_ID }),
      motionArtifactCreateDefinition(),
      { id: ENGAGEMENT_ID },
    );

    // Locking only the PATCH does not close the race: the copy has to hold the
    // row until its own increment commits, or an edit lands between the read
    // that copied the body and the count that was supposed to have shut the
    // template to edits.
    assert.deepEqual(
      log.events.slice(0, 4),
      ['transaction', 'select', 'select', 'for:no key update'],
    );
  });
});

describe('one engagement per deal', () => {
  it('answers 409 with the id of the engagement that already exists', async () => {
    const { db, log } = database([[deal], [engagement]]);

    await assert.rejects(
      executeMutation(
        db,
        member,
        async () => ({ demandDealId: DEAL_ID }),
        motionEngagementCreateDefinition(),
      ),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'engagement_exists' &&
        error.status === 409 &&
        error.message.includes(ENGAGEMENT_ID),
    );
    assert.deepEqual(log.inserted, []);
  });

  it('locks the deal before it looks, so two simultaneous opens queue instead of both inserting', async () => {
    const { db, log } = database([[deal], []]);

    await executeMutation(
      db,
      member,
      async () => ({ demandDealId: DEAL_ID }),
      motionEngagementCreateDefinition(),
    );

    // The race is not expressible against this fake — it runs no SQL and has
    // no concurrency — so what is pinned is the lock that removes it. Without
    // it both requests' checks above see nothing, both insert, and the loser
    // gets `23505` on `engagements_demand_deal_key`, which is not a
    // `MutationError` and so leaves as `500 Internal error` with no way to
    // tell that an engagement now exists. The lock is taken on the deal
    // because the row the loser must wait behind is the engagement that does
    // not exist yet.
    assert.deepEqual(log.events.slice(0, 3), ['transaction', 'select', 'for:update']);
  });
});

describe('qualification scores', () => {
  it('computes the score from the dimensions rather than believing the body', async () => {
    const { db, log } = database([[{ engagement, deal }]]);
    const dimensions = [
      { id: 'budget', weight: 30, score: 4 },
      { id: 'urgency', weight: 20, score: 2 },
      { id: 'fit', weight: 50, score: 3 },
    ];

    await executeMutation(
      db,
      member,
      async () => ({ dimensions }),
      motionScoreDefinition(),
      { id: ENGAGEMENT_ID },
    );

    const written = log.inserted.find((row) => row.table === 'qualification_scores')?.row;
    assert.equal(written?.basisPoints, motionScoreBasisPoints(dimensions));
    // Derived from the score by the shared function, never asserted as a
    // literal here: a band table edited in @pig/core would otherwise be caught
    // by this test rather than by the one that owns the decision.
    assert.equal(written?.band, motionBand(motionScoreBasisPoints(dimensions)).label);
    assert.equal(typeof written?.basisPoints, 'number');
    assert.ok(Number.isInteger(written?.basisPoints), 'a score is an integer, exactly as money is');
  });

  it('refuses a posted basisPoints outright — a score somebody can send is a score somebody can fix', async () => {
    const { db } = database([[{ engagement, deal }]]);

    await assert.rejects(
      executeMutation(
        db,
        member,
        async () => ({ dimensions: [{ id: 'fit', weight: 1, score: 1 }], basisPoints: 10_000 }),
        motionScoreDefinition(),
        { id: ENGAGEMENT_ID },
      ),
      (error: unknown) => error instanceof MutationError && error.code === 'invalid_request',
    );
  });
});

describe('an id that cannot name a row is a row that does not exist', () => {
  /*
   * Measured against a running server before this test existed: every `:id`
   * route answered `500 {"error":"Internal error"}` for an id like `nope`.
   * Nothing was wrong with the code that handled a missing row — that branch
   * was simply never reached, because each id column is a `uuid` and Postgres
   * refuses the cast with `22P02` several layers below it. The reads therefore
   * check the shape before they ask, and the writes do it in `requiredId`.
   *
   * 404 rather than 400 is the load-bearing half. A 400 for a malformed id and
   * a 404 for a well-formed one tells anyone probing which of their guesses
   * are the right shape, and this feature already routes "somebody else's
   * private draft" through the same 404 precisely so that no answer here
   * distinguishes between the reasons a row is not yours to see.
   */
  const viewer = { userId: OWNER, isPlatformAdmin: false };

  it('answers null for a malformed template id without going to the database at all', async () => {
    const { db, log } = database([]);

    assert.equal(await new MotionService(db).template(viewer, 'nope'), null);
    assert.deepEqual(log.events, [], 'a malformed id must not reach Postgres to be refused');
  });

  it('answers null for a malformed engagement id, likewise', async () => {
    const { db, log } = database([]);

    assert.equal(await new MotionService(db).engagement(viewer, 'nope'), null);
    assert.deepEqual(log.events, []);
  });

  it('still reads a well-formed id — the guard is a shape check, not a rejection of unknown ids', async () => {
    const { db, log } = database([[]]);

    assert.equal(await new MotionService(db).template(viewer, ENGAGEMENT_ID), null);
    assert.deepEqual(log.events, ['select'], 'a well-formed id is answered by the database');
  });

  it('refuses a malformed id on a write as not_found, the same answer an unknown one gets', async () => {
    const { db, log } = database([]);

    // A lead, so the write gets past the capability check and reaches the id:
    // authorization deliberately precedes it, and a member would be refused
    // here for the other reason entirely.
    await assert.rejects(
      executeMutation(
        db,
        lead,
        async () => ({}),
        motionTemplatePublishDefinition(),
        { id: 'nope' },
      ),
      (error: unknown) => error instanceof MutationError && error.code === 'not_found',
    );
    // The transaction opens first — `executeMutation` owns that — but nothing
    // is ever asked of it, which is the property that matters: no statement
    // carrying `nope` was sent to Postgres to be refused there.
    assert.deepEqual(log.events, ['transaction'], 'refused on shape, without a query');
  });
});

// --------------------------------------------------- the mount, not the mock

const SUBJECT = 'motion-auth-subject';

/**
 * The whole app, with the cheapest database that can carry an authenticated
 * request through it — the fixture `http-auth.test.ts` uses, which answers by
 * table identity rather than in call order because the queries `loadPrincipal`
 * and the overview make are an implementation detail.
 */
function createdApp(): ReturnType<typeof createApp> {
  const user = {
    id: OWNER,
    email: 'seller@example.com',
    name: 'Seller',
    authSubject: SUBJECT,
    deactivatedAt: null,
    isPlatformAdmin: false,
  };

  function chain(rows: unknown[]): Record<string, unknown> {
    const self: Record<string, unknown> = {
      leftJoin: () => self,
      innerJoin: () => self,
      where: () => self,
      orderBy: () => self,
      groupBy: () => self,
      limit: () => self,
      then: (resolve: (value: unknown[]) => unknown) => resolve(rows),
    };
    return self;
  }

  const db = {
    select: () => ({
      from: (table: unknown) => {
        if (table === users) return chain([user]);
        if (table === teamMemberships) return chain([{ team: 'demand', role: 'member' }]);
        return chain([]);
      },
    }),
    update: () => ({ set: () => ({ where: async () => undefined }) }),
    transaction: async (work: (tx: unknown) => Promise<unknown>) => work({}),
  } as unknown as Database;

  const provider: AuthProvider = {
    name: 'test',
    async verifyAccessToken(token: string) {
      if (token !== 'good-token') throw new Error('bad token');
      return { subject: SUBJECT, email: user.email };
    },
  };

  // A real `loadConfig`, for the reason `http-auth.test.ts` gives: a hand-built
  // Config object would let this pass under one the server refuses to start on.
  const config = loadConfig({
    NODE_ENV: 'test',
    DATABASE_URL: 'postgres://pig:pig@localhost:5432/pig-not-connected',
    PIG_PUBLIC_URL: 'http://localhost:8920',
    PIG_ADMIN_EMAILS: '',
  } as NodeJS.ProcessEnv);

  return createApp(config, db, provider);
}

describe('the routes are mounted in app.ts, not only in this file', () => {
  it('answers a member holding book:read on GET /api/motion', async () => {
    // AGENTS.md §5, verbatim: every other test here mounts the factory itself,
    // so all twelve routes could be dropped from `app.ts` and this file would
    // stay green. The request is authenticated deliberately — `app.use('/api/*')`
    // authenticates ahead of every feature route, so an anonymous GET answers
    // 401 whether or not anything is mounted behind it, and the same assertion
    // would pass against a feature that had been deleted outright.
    const response = await createdApp().request('/api/motion', {
      headers: { authorization: 'Bearer good-token' },
    });

    assert.notEqual(response.status, 404, 'createMotionRoutes is not mounted in createApp');
    assert.notEqual(response.status, 401, 'the request never reached the motion handler');
    assert.notEqual(response.status, 403, 'the read guard answered, so the mount is untested');
  });
});
