import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import {
  AuthError,
  effectivePermissions,
  requireAnyTeamCapability,
  requireCapability,
  requireReadCapability,
} from '../src/lib/auth';
import { onTeam, principal } from './helpers/principal';

describe('capability enforcement', () => {
  it('rejects a role grant from the wrong team', () => {
    assert.throws(
      () => requireCapability(principal(), 'deal:write', 'supply'),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
  });

  it('removes write grants from a read-only API key but keeps its reads', () => {
    const readOnly = principal({ via: 'api_key', scopes: ['read'] });

    // The point of a read-only key. Before read capabilities existed this
    // resolved to nothing at all, which was right then and would now tell the
    // front end that a reader may not read.
    assert.deepEqual(
      effectivePermissions(readOnly).map((grant) => grant.capability),
      ['book:read', 'economics:read', 'team:read'],
    );
    assert.throws(
      () => requireCapability(readOnly, 'deal:write', 'demand'),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_scope',
    );
  });

  it('grants no reads to a write-only credential', () => {
    const writeOnly = principal({ via: 'api_key', scopes: ['write'] });

    assert.throws(
      () => requireReadCapability(writeOnly, 'economics:read'),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_scope',
    );
  });

  it('keeps supplier economics away from research, whatever their rank', () => {
    const researchAdmin = principal(onTeam('research', 'admin'));

    requireReadCapability(researchAdmin, 'book:read');
    assert.throws(
      () => requireReadCapability(researchAdmin, 'economics:read'),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
  });

  it('gives a viewer the book and nothing that writes to it', () => {
    const viewer = principal(onTeam('demand', 'viewer'));

    requireReadCapability(viewer, 'book:read');
    requireReadCapability(viewer, 'team:read');
    for (const capability of ['deal:write', 'activity:write'] as const) {
      assert.throws(
        () => requireCapability(viewer, capability, 'demand'),
        (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
        `viewer should not hold ${capability}`,
      );
    }
  });

  /**
   * The bug this pins: `requireCapability(p, 'data:import')` with no team
   * passed if the principal held it anywhere, so a research-team admin could
   * rewrite the demand pipeline. The overload no longer accepts a team-scoped
   * capability without a team; the any-team question has to be asked by name.
   */
  it('separates "holds it here" from "holds it somewhere"', () => {
    const researchAdmin = principal(onTeam('research', 'admin'));

    requireAnyTeamCapability(researchAdmin, 'data:import');
    assert.throws(
      () => requireCapability(researchAdmin, 'data:import', 'demand'),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
  });

  it('will not let fact review borrow bulk-import authority', () => {
    const demandAdmin = principal(onTeam('demand', 'admin'));

    requireCapability(demandAdmin, 'data:import', 'demand');
    assert.throws(
      () => requireAnyTeamCapability(demandAdmin, 'fact:review'),
      (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
    );
  });
});
