/**
 * Shared test fixtures for authorisation.
 *
 * Before this, `Principal` was re-declared as a literal in auth.test.ts,
 * records.test.ts, mutation.test.ts and half a dozen others — ten copies of the
 * same nine fields. Adding a field to `Principal` meant editing every one of
 * them, and the copies had already drifted on `scopes`, which is precisely the
 * field the read/write split now turns on. One factory, overridden per case.
 */
import type { Team, TeamRole } from '@pig/core';
import type { Database } from '@pig/db';
import type { Principal } from '../../src/lib/auth';

/**
 * A demand-team member with a full-scope session: the ordinary user, chosen as
 * the default because it is the case most tests want to vary *away* from.
 */
export function principal(overrides: Partial<Principal> = {}): Principal {
  return {
    userId: '00000000-0000-4000-8000-000000000001',
    email: 'seller@example.com',
    name: 'Seller',
    isPlatformAdmin: false,
    teams: [{ team: 'demand', role: 'member' }],
    via: 'jwt',
    scopes: ['read', 'write'],
    ...overrides,
  };
}

/** One membership, spelled out — the common override, and easy to get wrong. */
export function onTeam(team: Team, role: TeamRole): Partial<Principal> {
  return { teams: [{ team, role }] };
}

export interface FakeDatabaseOptions {
  /** Appended to in call order, so a test can assert what ran and in what order. */
  events?: string[];
  /** Rows handed to `insert().values()`, chiefly the audit activity. */
  inserted?: unknown[];
  /** Rows a `select()` chain resolves to. Defaults to empty. */
  selected?: unknown[];
}

/**
 * The minimum Drizzle surface `executeMutation` touches: a transaction, an
 * insert that records its row, and a select chain that resolves to fixed rows.
 * Deliberately not a database — a test that needs real SQL semantics needs a
 * real Postgres, and pretending otherwise is how a fake starts asserting that
 * broken queries work.
 */
export function fakeDatabase(options: FakeDatabaseOptions = {}): Database {
  const events = options.events ?? [];
  const inserted = options.inserted ?? [];
  const selected = options.selected ?? [];

  const selectChain = {
    from: () => selectChain,
    leftJoin: () => selectChain,
    innerJoin: () => selectChain,
    where: () => selectChain,
    orderBy: () => selectChain,
    limit: async () => selected,
    then: (resolve: (rows: unknown[]) => unknown) => resolve(selected),
  };

  const insertChain = {
    values: (row: unknown) => {
      events.push('insert');
      inserted.push(row);
      return {
        onConflictDoNothing: () => ({ returning: async () => [row] }),
        returning: async () => [row],
        then: (resolve: (value: unknown) => unknown) => resolve(undefined),
      };
    },
  };

  const updateChain = {
    set: () => ({
      where: async () => {
        events.push('update');
      },
    }),
  };

  const tx = {
    select: () => {
      events.push('select');
      return selectChain;
    },
    insert: () => insertChain,
    update: () => updateChain,
  };

  return {
    transaction: async (work: (transaction: unknown) => Promise<unknown>) => {
      events.push('transaction');
      return work(tx);
    },
    select: tx.select,
    insert: tx.insert,
    update: tx.update,
  } as unknown as Database;
}
