import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import { z } from 'zod';
import { AuthError } from '../src/lib/auth';
import { apiError, executeMutation, MutationError } from '../src/lib/mutation';
import { fakeDatabase, onTeam, principal as makePrincipal } from './helpers/principal';

const principal = makePrincipal();

function db(events: string[], inserted: unknown[] = []) {
  return fakeDatabase({ events, inserted });
}

describe('mutation convention', () => {
  it('checks capability before reading attacker-controlled input', async () => {
    const events: string[] = [];
    const forbidden = makePrincipal(onTeam('supply', 'admin'));

    await assert.rejects(
      executeMutation(db(events), forbidden, async () => {
        events.push('body');
        return {};
      }, {
        schema: z.object({ name: z.string() }),
        permission: { capability: 'deal:write', team: 'demand' },
        invalidMessage: 'Invalid deal.',
        async mutate() {
          events.push('mutate');
          return { data: {}, activity: { type: 'note', subject: 'Changed' } };
        },
      }),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
    assert.deepEqual(events, []);
  });

  it('rejects invalid ontology input before opening a transaction', async () => {
    const events: string[] = [];
    const stages = ['qualification', 'legal'] as const;

    await assert.rejects(
      executeMutation(db(events), principal, async () => ({ stage: 'invented' }), {
        schema: z.object({ stage: z.enum(stages) }),
        permission: { capability: 'deal:write', team: 'demand' },
        invalidMessage: 'Invalid transition.',
        async mutate() {
          events.push('mutate');
          return { data: {}, activity: { type: 'stage_change', subject: 'Changed' } };
        },
      }),
      (error: unknown) =>
        error instanceof MutationError &&
        error.code === 'invalid_request' &&
        apiError(error.code, error.message, error.issues).issues?.length === 1,
    );
    assert.deepEqual(events, []);
  });

  it('writes mutation evidence in the same transaction with framework attribution', async () => {
    const events: string[] = [];
    const rows: unknown[] = [];
    const result = await executeMutation(
      db(events, rows),
      principal,
      async () => ({ stage: 'legal' }),
      {
        schema: z.object({ stage: z.literal('legal') }),
        permission: { capability: 'deal:write', team: 'demand' },
        invalidMessage: 'Invalid transition.',
        async mutate() {
          events.push('mutate');
          const data = { id: 'deal-1' };
          return {
            data,
            activity: {
              type: 'stage_change',
              subject: 'Moved to legal',
              demandDealId: data.id,
            },
          };
        },
      },
    );

    assert.deepEqual(result, { id: 'deal-1' });
    assert.deepEqual(events, ['transaction', 'mutate', 'insert']);
    assert.deepEqual(rows, [
      {
        type: 'stage_change',
        subject: 'Moved to legal',
        demandDealId: 'deal-1',
        actorUserId: principal.userId,
        actorAgent: null,
        source: 'manual',
        occurredAt: (rows[0] as { occurredAt: Date }).occurredAt,
      },
    ]);
    assert.ok((rows[0] as { occurredAt: unknown }).occurredAt instanceof Date);
  });
});
