/**
 * The write that used to bypass everything.
 *
 * `POST /api/activities` lived inline in app.ts with no capability check at
 * all: any member, and any write-scoped API key, could insert an activity
 * against an arbitrary `accountId` and move that account's `lastActivityAt`.
 * These pin the three things that stopped it, not the SQL that carries them
 * out.
 */
import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import type { Database } from '@pig/db';
import { AuthError } from '../src/lib/auth';
import { executeMutation } from '../src/lib/mutation';
import { createActivityMutationDefinition, type LoggedActivity } from '../src/routes/activities';
import { onTeam, principal } from './helpers/principal';

interface Recorded {
  events: string[];
  inserted: unknown[];
  updated: unknown[];
}

/** A transaction whose account lookup answers with a chosen side. */
function database(accountSide: string | null): { db: Database; log: Recorded } {
  const log: Recorded = { events: [], inserted: [], updated: [] };
  const accountRows = accountSide ? [{ side: accountSide }] : [];

  const tx = {
    select: () => {
      log.events.push('select');
      return { from: () => ({ where: () => ({ limit: async () => accountRows }) }) };
    },
    insert: () => ({
      values: (row: unknown) => {
        log.events.push('insert');
        log.inserted.push(row);
        return { onConflictDoNothing: () => ({ returning: async () => [row] }) };
      },
    }),
    update: () => ({
      set: (values: unknown) => ({
        where: async () => {
          log.events.push('touch-account');
          log.updated.push(values);
        },
      }),
    }),
  };

  return {
    db: {
      transaction: async (work: (t: unknown) => Promise<unknown>) => {
        log.events.push('transaction');
        return work(tx);
      },
    } as unknown as Database,
    log,
  };
}

const body = {
  type: 'call' as const,
  subject: 'Spoke to the CTO',
  accountId: '00000000-0000-4000-8000-0000000000ff',
};

function log(db: Database, actor = principal()) {
  return executeMutation(
    db,
    actor,
    async () => body,
    createActivityMutationDefinition(),
  ) as Promise<LoggedActivity>;
}

describe('logging an activity', () => {
  it('refuses a principal with no activity:write anywhere, before reading the body', async () => {
    const { db, log: recorded } = database('demand');
    let bodyWasRead = false;

    await assert.rejects(
      executeMutation(
        db,
        principal(onTeam('demand', 'viewer')),
        async () => {
          bodyWasRead = true;
          return body;
        },
        createActivityMutationDefinition(),
      ),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
    assert.equal(bodyWasRead, false);
    assert.deepEqual(recorded.events, []);
  });

  /**
   * The escalation the old handler allowed: a research member logging a call
   * against a demand account they have no relationship with, and pushing it to
   * the top of somebody else's account list.
   */
  it('refuses a research member writing against a demand account', async () => {
    const { db, log: recorded } = database('demand');

    await assert.rejects(
      log(db, principal(onTeam('research', 'admin'))),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
    assert.equal(recorded.inserted.length, 0);
    assert.equal(recorded.updated.length, 0);
  });

  it('admits a demand member against a dual-sided account', async () => {
    const { db, log: recorded } = database('both');

    const result = await log(db);

    assert.equal(result.deduplicated, false);
    assert.deepEqual(recorded.events, ['transaction', 'select', 'insert', 'touch-account']);
  });

  it('writes one row, not two — the activity is its own audit event', async () => {
    const { db, log: recorded } = database('demand');

    await log(db);

    assert.equal(
      recorded.inserted.length,
      1,
      'an audit row alongside the activity would double every synced call in the feed',
    );
  });

  it('attributes an API key to the agent, not silently to the person', async () => {
    const { db, log: recorded } = database('demand');

    await log(db, principal({ via: 'api_key', apiKeyId: 'key-1' }));

    assert.deepEqual(
      recorded.inserted[0] as Record<string, unknown>,
      {
        ...(recorded.inserted[0] as Record<string, unknown>),
        actorAgent: 'agent',
        source: 'agent',
      },
    );
  });

  it('keeps the caller\'s timestamp, because sync backfills', async () => {
    const { db, log: recorded } = database('demand');
    const when = '2026-01-05T09:30:00.000Z';

    await executeMutation(
      db,
      principal(),
      async () => ({ ...body, occurredAt: when }),
      createActivityMutationDefinition(),
    );

    const row = recorded.inserted[0] as { occurredAt: Date };
    assert.equal(row.occurredAt.toISOString(), when);
    // And the account stamp follows the event, not the clock, or a backfilled
    // call from March would jump the account to the top of the list today.
    assert.deepEqual(recorded.updated, [{ lastActivityAt: new Date(when) }]);
  });
});
