/**
 * ADR-025: Pluggable Policy Engine tests.
 *
 * Tests NullPolicyEngine, DeclarativePolicyEngine, OmegaLispPolicyEngine,
 * named presets, factory function, and SecurityRealm integration.
 */
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';

import { NullPolicyEngine } from '../NullPolicyEngine';
import { DeclarativePolicyEngine, type IDeclarativePolicy } from '../DeclarativePolicyEngine';
import { createPolicyEngine, POLICY_PRESETS, type ISecurityConfig } from '../PolicyPresets';
import { POLICY_ALLOW, type IPolicyRequest } from '../IPolicyEngine';
import { SecurityRealm } from '../SecurityRealm';

// ─── Helpers ─────────────────────────────────────────────────────────────

function makeRequest(op: string, sender = 'test.actor'): IPolicyRequest {
  return {
    op,
    payload: {},
    sender,
    meta: { topic: `test.${op}`, ts: Date.now() },
  };
}

// ─── NullPolicyEngine ────────────────────────────────────────────────────

describe('NullPolicyEngine', () => {
  it('always returns allow', () => {
    const engine = new NullPolicyEngine();
    const result = engine.evaluate(makeRequest('anything'));
    assert.equal(result.action, 'allow');
  });

  it('returns the frozen POLICY_ALLOW singleton', () => {
    const engine = new NullPolicyEngine();
    const result = engine.evaluate(makeRequest('test'));
    assert.equal(result, POLICY_ALLOW);
  });

  it('has name "null"', () => {
    assert.equal(new NullPolicyEngine().name, 'null');
  });
});

// ─── DeclarativePolicyEngine ─────────────────────────────────────────────

describe('DeclarativePolicyEngine', () => {
  it('matches op by exact string', () => {
    const policy: IDeclarativePolicy = {
      rules: [{ match: { op: 'dangerous' }, action: 'deny', reason: 'blocked' }],
      default: 'allow',
    };
    const engine = new DeclarativePolicyEngine(policy);
    const result = engine.evaluate(makeRequest('dangerous'));
    assert.equal(result.action, 'deny');
    assert.equal(result.error, 'blocked');
  });

  it('matches op by array (any-of)', () => {
    const policy: IDeclarativePolicy = {
      rules: [{ match: { op: ['a', 'b', 'c'] }, action: 'deny' }],
      default: 'allow',
    };
    const engine = new DeclarativePolicyEngine(policy);
    assert.equal(engine.evaluate(makeRequest('b')).action, 'deny');
    assert.equal(engine.evaluate(makeRequest('d')).action, 'allow');
  });

  it('matches sender field', () => {
    const policy: IDeclarativePolicy = {
      rules: [{ match: { sender: 'evil.actor' }, action: 'deny' }],
      default: 'allow',
    };
    const engine = new DeclarativePolicyEngine(policy);
    assert.equal(engine.evaluate(makeRequest('test', 'evil.actor')).action, 'deny');
    assert.equal(engine.evaluate(makeRequest('test', 'good.actor')).action, 'allow');
  });

  it('AND logic: both op and sender must match', () => {
    const policy: IDeclarativePolicy = {
      rules: [{ match: { op: 'write', sender: 'untrusted' }, action: 'deny' }],
      default: 'allow',
    };
    const engine = new DeclarativePolicyEngine(policy);
    // Both match → deny
    assert.equal(engine.evaluate(makeRequest('write', 'untrusted')).action, 'deny');
    // Only op matches → allow (AND not satisfied)
    assert.equal(engine.evaluate(makeRequest('write', 'trusted')).action, 'allow');
    // Only sender matches → allow
    assert.equal(engine.evaluate(makeRequest('read', 'untrusted')).action, 'allow');
  });

  it('first-match-wins: earlier rule takes precedence', () => {
    const policy: IDeclarativePolicy = {
      rules: [
        { match: { op: 'test' }, action: 'allow' },
        { match: { op: 'test' }, action: 'deny' },
      ],
      default: 'deny',
    };
    const engine = new DeclarativePolicyEngine(policy);
    assert.equal(engine.evaluate(makeRequest('test')).action, 'allow');
  });

  it('default-deny when no rule matches', () => {
    const policy: IDeclarativePolicy = {
      rules: [{ match: { op: 'allowed' }, action: 'allow' }],
      default: 'deny',
      defaultReason: 'not in allowlist',
    };
    const engine = new DeclarativePolicyEngine(policy);
    const result = engine.evaluate(makeRequest('unknown'));
    assert.equal(result.action, 'deny');
    assert.equal(result.error, 'not in allowlist');
  });

  it('default-allow when no rule matches', () => {
    const policy: IDeclarativePolicy = {
      rules: [{ match: { op: 'blocked' }, action: 'deny' }],
      default: 'allow',
    };
    const engine = new DeclarativePolicyEngine(policy);
    assert.equal(engine.evaluate(makeRequest('anything-else')).action, 'allow');
  });

  it('empty rules with default-allow passes everything', () => {
    const engine = new DeclarativePolicyEngine({ rules: [], default: 'allow' });
    assert.equal(engine.evaluate(makeRequest('anything')).action, 'allow');
  });

  it('empty rules with default-deny blocks everything', () => {
    const engine = new DeclarativePolicyEngine({ rules: [], default: 'deny' });
    assert.equal(engine.evaluate(makeRequest('anything')).action, 'deny');
  });

  it('has name "declarative"', () => {
    const engine = new DeclarativePolicyEngine({ rules: [], default: 'allow' });
    assert.equal(engine.name, 'declarative');
  });
});

// ─── Named Presets ───────────────────────────────────────────────────────

describe('Policy Presets', () => {
  it('dev preset allows everything', () => {
    const engine = new DeclarativePolicyEngine(POLICY_PRESETS.dev);
    assert.equal(engine.evaluate(makeRequest('factory.create-from-code')).action, 'allow');
    assert.equal(engine.evaluate(makeRequest('anything')).action, 'allow');
  });

  it('standard preset denies factory.create-from-code', () => {
    const engine = new DeclarativePolicyEngine(POLICY_PRESETS.standard);
    assert.equal(engine.evaluate(makeRequest('factory.create-from-code')).action, 'deny');
    assert.equal(engine.evaluate(makeRequest('$prompt')).action, 'allow');
  });

  it('strict preset denies unknown ops', () => {
    const engine = new DeclarativePolicyEngine(POLICY_PRESETS.strict);
    assert.equal(engine.evaluate(makeRequest('unknown-op')).action, 'deny');
  });

  it('strict preset allows system ops', () => {
    const engine = new DeclarativePolicyEngine(POLICY_PRESETS.strict);
    assert.equal(engine.evaluate(makeRequest('$ping')).action, 'allow');
    assert.equal(engine.evaluate(makeRequest('$introspect')).action, 'allow');
    assert.equal(engine.evaluate(makeRequest('$join')).action, 'allow');
  });

  it('strict preset allows $prompt', () => {
    const engine = new DeclarativePolicyEngine(POLICY_PRESETS.strict);
    assert.equal(engine.evaluate(makeRequest('$prompt')).action, 'allow');
  });

  it('lockdown preset allows only system ops', () => {
    const engine = new DeclarativePolicyEngine(POLICY_PRESETS.lockdown);
    assert.equal(engine.evaluate(makeRequest('$ping')).action, 'allow');
    assert.equal(engine.evaluate(makeRequest('$prompt')).action, 'deny');
    assert.equal(engine.evaluate(makeRequest('factory.create-from-code')).action, 'deny');
  });
});

// ─── Factory ─────────────────────────────────────────────────────────────

describe('createPolicyEngine', () => {
  it('no config returns NullPolicyEngine', () => {
    const engine = createPolicyEngine();
    assert.equal(engine.name, 'null');
  });

  it('policyEngine="null" returns NullPolicyEngine', () => {
    const engine = createPolicyEngine({ policyEngine: 'null' });
    assert.equal(engine.name, 'null');
  });

  it('policyEngine="declarative" with preset', async () => {
    const engine = createPolicyEngine({ policyEngine: 'declarative', preset: 'standard' });
    assert.equal(engine.name, 'declarative');
    // Should deny factory.create-from-code (standard preset)
    const result = await engine.evaluate(makeRequest('factory.create-from-code'));
    assert.equal(result.action, 'deny');
  });

  it('policyEngine="declarative" with custom rules merged before preset', async () => {
    const engine = createPolicyEngine({
      policyEngine: 'declarative',
      preset: 'standard',
      rules: [
        // Custom rule: override standard preset to allow factory.create-from-code
        { match: { op: 'factory.create-from-code' }, action: 'allow' },
      ],
    });
    // Custom rule comes first (first-match-wins), overrides preset's deny
    const result = await engine.evaluate(makeRequest('factory.create-from-code'));
    assert.equal(result.action, 'allow');
  });

  it('throws on unknown preset', () => {
    assert.throws(
      () => createPolicyEngine({
        policyEngine: 'declarative',
        preset: 'nonexistent' as unknown as ISecurityConfig['preset'],
      }),
      /Unknown policy preset/,
    );
  });

  it('throws on unknown engine type', () => {
    assert.throws(
      () => createPolicyEngine({
        policyEngine: 'kafka' as unknown as NonNullable<ISecurityConfig['policyEngine']>,
      }),
      /Unknown policy engine type/,
    );
  });
});

// ─── SecurityRealm Integration ───────────────────────────────────────────

describe('SecurityRealm + IPolicyEngine', () => {
  it('policyEngine is undefined when not provided', () => {
    const realm = new SecurityRealm();
    assert.equal(realm.policyEngine, undefined);
  });

  it('policyEngine is accessible when provided', () => {
    const engine = new NullPolicyEngine();
    const realm = new SecurityRealm(undefined, undefined, undefined, undefined, engine);
    assert.equal(realm.policyEngine, engine);
    assert.equal(realm.policyEngine!.name, 'null');
  });

  it('existing SecurityRealm functionality unaffected', () => {
    const engine = new NullPolicyEngine();
    const realm = new SecurityRealm(undefined, undefined, undefined, undefined, engine);
    // Capability minting still works
    const cap = realm.mintCapability('test.actor', ['invoke']);
    assert.ok(cap);
    assert.ok(realm.isCapabilityValid(cap));
    // Spawn evaluation still works
    const decision = realm.evaluateSpawn({
      componentType: 'TestComponent',
      parentMount: 'test',
      requestor: { type: 'system', id: 'test', trustLevel: 'system' },
    });
    assert.ok(decision.allowed);
  });
});
