import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { MockHybxClient } from './hybx/MockHybxClient';
import { loadHybxConfig } from './hybx/config';
import { MockComplianceDecisionEngine } from './compliance/ComplianceDecisionEngine';
import { createAuditEvent, redactMetadata } from './audit/AuditEvent';
import { deriveIdempotencyKey } from './resilience/idempotency';
import {
  validateCanonicalFinancialEvent,
  type CanonicalFinancialEvent,
} from './iso20022/CanonicalFinancialEvent';
import { assertTenantIsolation } from './entities/RegulatedEntity';
import { verifyWebhookSignature, computeWebhookSignature } from './webhooks/verifySignature';
import { HttpHybxClient } from './hybx/HttpHybxClient';
import { MockReconciliationEngine } from './reconciliation/ReconciliationStatus';
import { CircuitBreaker } from './resilience/circuitBreaker';

describe('MockHybxClient', () => {
  it('returns typed mock payment response', async () => {
    const client = new MockHybxClient('sandbox');
    const res = await client.initiatePayment({
      idempotencyKey: 'key-001',
      entityId: 'ent-1',
      tenantId: 'tenant-a',
      amount: '100.00',
      currency: 'USD',
      debtorAccountRef: 'debtor-1',
      creditorAccountRef: 'creditor-1',
    });
    assert.equal(res.status, 'accepted');
    assert.ok(res.paymentId.startsWith('mock-pay-'));
    assert.ok(res.uetr);
  });
});

describe('loadHybxConfig', () => {
  it('loads sandbox placeholders', () => {
    const cfg = loadHybxConfig({
      testMode: true,
      env: {
        HYBX_ENVIRONMENT: 'sandbox',
        HYBX_BASE_URL: 'https://hybx-sandbox.example.invalid',
        HYBX_CLIENT_ID: 'placeholder',
        HYBX_CLIENT_SECRET: 'placeholder',
        HYBX_API_KEY: 'placeholder',
        HYBX_WEBHOOK_SECRET: 'placeholder',
      },
    });
    assert.equal(cfg.environment, 'sandbox');
  });

  it('rejects production in test mode', () => {
    assert.throws(() =>
      loadHybxConfig({
        testMode: true,
        env: { HYBX_ENVIRONMENT: 'production', HYBX_BASE_URL: 'https://x' },
      })
    );
  });
});

describe('MockComplianceDecisionEngine', () => {
  it('blocks sanctions hits', async () => {
    const engine = new MockComplianceDecisionEngine();
    const res = await engine.evaluateTransaction({
      entityId: 'e1',
      tenantId: 't1',
      amount: '10',
      currency: 'USD',
      transactionType: 'payment',
      riskHints: ['sanctions_hit'],
    });
    assert.equal(res.decision, 'block');
  });

  it('reviews PEP matches', async () => {
    const engine = new MockComplianceDecisionEngine();
    const res = await engine.evaluateTransaction({
      entityId: 'e1',
      tenantId: 't1',
      amount: '10',
      currency: 'USD',
      transactionType: 'payment',
      riskHints: ['pep_match'],
    });
    assert.equal(res.decision, 'review');
  });
});

describe('audit redaction', () => {
  it('removes secret-like and PII-like fields', () => {
    const redacted = redactMetadata({
      api_key: 'sk-test-dummy',
      email: 'user@example.com',
      amount: '100',
    });
    assert.equal(redacted.api_key, '[REDACTED]');
    assert.equal(redacted.email, '[REDACTED]');
    assert.equal(redacted.amount, '100');
  });

  it('creates audit event with redacted metadata', () => {
    const evt = createAuditEvent({
      audit_event_id: 'a1',
      actor_type: 'service',
      actor_id: 'svc-1',
      tenant_id: 't1',
      entity_id: 'e1',
      action: 'payment.initiated',
      resource_type: 'payment',
      resource_id: 'p1',
      metadata: { client_secret: 'dummy-secret' },
    });
    assert.equal(evt.metadata_redacted.client_secret, '[REDACTED]');
  });
});

describe('idempotency', () => {
  it('returns stable keys for identical inputs', () => {
    const input = {
      tenantId: 't1',
      entityId: 'e1',
      operation: 'payment',
      payload: { amount: '10' },
    };
    assert.equal(deriveIdempotencyKey(input), deriveIdempotencyKey(input));
  });

  it('returns distinct keys for distinct inputs', () => {
    const a = deriveIdempotencyKey({
      tenantId: 't1',
      entityId: 'e1',
      operation: 'payment',
    });
    const b = deriveIdempotencyKey({
      tenantId: 't2',
      entityId: 'e1',
      operation: 'payment',
    });
    assert.notEqual(a, b);
  });
});

describe('CanonicalFinancialEvent', () => {
  const valid: CanonicalFinancialEvent = {
    schema_version: '1.0',
    message_id: 'msg-1',
    end_to_end_id: 'e2e-1',
    debtor: { id: 'd1' },
    creditor: { id: 'c1' },
    amount: '100.00',
    currency: 'USD',
    source_system: 'hybx',
    target_system: 'omnl',
    entity_id: 'e1',
    tenant_id: 't1',
  };

  it('validates required fields', () => {
    assert.equal(validateCanonicalFinancialEvent(valid).valid, true);
    assert.equal(validateCanonicalFinancialEvent({}).valid, false);
  });
});

describe('tenant isolation', () => {
  it('throws on tenant mismatch', () => {
    assert.throws(() => assertTenantIsolation('t1', 't2'));
  });
});

describe('HttpHybxClient', () => {
  it('refuses production environment', () => {
    assert.throws(
      () =>
        new HttpHybxClient({
          testMode: false,
          config: {
            environment: 'production',
            baseUrl: 'https://hybx.example.invalid',
            clientId: 'p',
            clientSecret: 'p',
            apiKey: 'p',
            webhookSecret: 'p',
            requestTimeoutMs: 1000,
            maxRetries: 1,
          },
        }),
      /refuses production/
    );
  });

  it('parses webhook payload shape', () => {
    const client = new HttpHybxClient({
      testMode: true,
      config: {
        environment: 'sandbox',
        baseUrl: 'https://hybx-sandbox.example.invalid',
        clientId: 'placeholder',
        clientSecret: 'placeholder',
        apiKey: 'placeholder',
        webhookSecret: 'placeholder',
        requestTimeoutMs: 1000,
        maxRetries: 1,
      },
    });
    const parsed = client.parseWebhookPayload(
      JSON.stringify({ eventType: 'settlement.completed', eventId: 'evt-1', data: { amount: '100' } })
    );
    assert.equal(parsed.eventType, 'settlement.completed');
    assert.equal(parsed.eventId, 'evt-1');
    assert.equal((parsed.data as { amount: string }).amount, '100');
  });
});

describe('MockReconciliationEngine', () => {
  it('detects missing fineract refs', async () => {
    const engine = new MockReconciliationEngine();
    const snap = await engine.reconcileTripleState({
      tenantId: 't1',
      entityId: 'e1',
      hybxRefs: ['ref-1'],
      fineractRefs: [],
      chainRefs: ['ref-1'],
    });
    assert.equal(snap.status, 'breaks_found');
    assert.ok(snap.breaks.length >= 1);
  });
});

describe('CircuitBreaker', () => {
  it('opens after threshold failures', async () => {
    const cb = new CircuitBreaker({ failureThreshold: 2, resetTimeoutMs: 60_000 });
    const fail = () => cb.execute(async () => { throw new Error('fail'); });
    await assert.rejects(fail);
    await assert.rejects(fail);
    assert.equal(cb.getState(), 'open');
  });
});

describe('webhook verification', () => {
  it('verifies HMAC signature', () => {
    const secret = 'test-webhook-secret-dummy';
    const payload = '{"event":"payment.settled"}';
    const sig = computeWebhookSignature(secret, payload);
    assert.equal(
      verifyWebhookSignature({ secret, payload, signature: sig }),
      true
    );
    assert.equal(
      verifyWebhookSignature({ secret, payload, signature: 'bad' }),
      false
    );
  });

  it('verifies sha256= prefixed OMNL-style signatures', () => {
    const secret = 'omnl-webhook-secret';
    const payload = '{"event":"ReserveCommitted"}';
    const sig = `sha256=${computeWebhookSignature(secret, payload)}`;
    assert.equal(verifyWebhookSignature({ secret, payload, signature: sig }), true);
  });
});
