/**
 * Layer 4: RPC Unit Tests - Internal Message Parsing
 *
 * Tests for the internal message format parsers and normalizers.
 * These form the foundation of the RPC layer.
 */
import test from 'node:test';
import assert from 'node:assert/strict';
import {
  parseInternalRequest,
  parseInternalReply,
  normalizeInternalReply,
  isJsonNullPayload,
  type InternalRequest,
  type InternalReply,
} from '@open-matrix/federation/runtime/internal_messages';

// ============================================================================
// parseInternalRequest Tests
// ============================================================================

test('parseInternalRequest: valid mxenv1 request', () => {
  const json = JSON.stringify({
    correlationId: 'corr-123',
    replyTo: '_local.replies.emits.corr-123',
    params: { key: 'value' },
    _mx: { fromRealm: 'flowpad', incomingProfile: 'mxenv1' },
  });

  const result = parseInternalRequest(json);

  assert.ok(result !== null);
  assert.equal(result.correlationId, 'corr-123');
  assert.equal(result.replyTo, '_local.replies.emits.corr-123');
  assert.deepEqual(result.params, { key: 'value' });
  assert.equal(result._mx.fromRealm, 'flowpad');
  assert.equal(result._mx.incomingProfile, 'mxenv1');
});

test('parseInternalRequest: valid matrix3 request', () => {
  const json = JSON.stringify({
    correlationId: 'corr-456',
    replyTo: 'flowpad._local.replies.emits.corr-456',
    params: [1, 2, 3],
    _mx: { fromRealm: 'daemon@matrix-3', incomingProfile: 'matrix3' },
  });

  const result = parseInternalRequest(json);

  assert.ok(result !== null);
  assert.equal(result.correlationId, 'corr-456');
  assert.equal(result._mx.fromRealm, 'daemon@matrix-3');
  assert.equal(result._mx.incomingProfile, 'matrix3');
  assert.deepEqual(result.params, [1, 2, 3]);
});

test('parseInternalRequest: params can be any JSON value', () => {
  // null params
  const json1 = JSON.stringify({
    correlationId: 'c1',
    replyTo: 'reply/topic',
    params: null,
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  assert.ok(parseInternalRequest(json1) !== null);

  // string params
  const json2 = JSON.stringify({
    correlationId: 'c2',
    replyTo: 'reply/topic',
    params: 'just a string',
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  const result2 = parseInternalRequest(json2);
  assert.ok(result2 !== null);
  assert.equal(result2.params, 'just a string');

  // number params
  const json3 = JSON.stringify({
    correlationId: 'c3',
    replyTo: 'reply/topic',
    params: 42,
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  const result3 = parseInternalRequest(json3);
  assert.ok(result3 !== null);
  assert.equal(result3.params, 42);

  // boolean params
  const json4 = JSON.stringify({
    correlationId: 'c4',
    replyTo: 'reply/topic',
    params: false,
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  const result4 = parseInternalRequest(json4);
  assert.ok(result4 !== null);
  assert.equal(result4.params, false);
});

test('parseInternalRequest: rejects invalid JSON', () => {
  assert.equal(parseInternalRequest('not json'), null);
  assert.equal(parseInternalRequest('{malformed'), null);
  assert.equal(parseInternalRequest(''), null);
});

test('parseInternalRequest: rejects non-object JSON', () => {
  assert.equal(parseInternalRequest('"string"'), null);
  assert.equal(parseInternalRequest('123'), null);
  assert.equal(parseInternalRequest('null'), null);
  assert.equal(parseInternalRequest('true'), null);
  assert.equal(parseInternalRequest('[]'), null);
});

test('parseInternalRequest: rejects missing correlationId', () => {
  const json = JSON.stringify({
    replyTo: 'reply/topic',
    params: {},
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects non-string correlationId', () => {
  const json = JSON.stringify({
    correlationId: 123,
    replyTo: 'reply/topic',
    params: {},
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects missing replyTo', () => {
  const json = JSON.stringify({
    correlationId: 'c',
    params: {},
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects non-string replyTo', () => {
  const json = JSON.stringify({
    correlationId: 'c',
    replyTo: { url: 'topic' },
    params: {},
    _mx: { fromRealm: 'r', incomingProfile: 'mxenv1' },
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects missing _mx', () => {
  const json = JSON.stringify({
    correlationId: 'c',
    replyTo: 'reply/topic',
    params: {},
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects non-object _mx', () => {
  const json = JSON.stringify({
    correlationId: 'c',
    replyTo: 'reply/topic',
    params: {},
    _mx: 'not an object',
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects missing _mx.fromRealm', () => {
  const json = JSON.stringify({
    correlationId: 'c',
    replyTo: 'reply/topic',
    params: {},
    _mx: { incomingProfile: 'mxenv1' },
  });
  assert.equal(parseInternalRequest(json), null);
});

test('parseInternalRequest: rejects invalid _mx.incomingProfile', () => {
  const json = JSON.stringify({
    correlationId: 'c',
    replyTo: 'reply/topic',
    params: {},
    _mx: { fromRealm: 'r', incomingProfile: 'invalid' },
  });
  assert.equal(parseInternalRequest(json), null);
});

// ============================================================================
// parseInternalReply Tests
// ============================================================================

test('parseInternalReply: valid success reply', () => {
  const json = JSON.stringify({
    correlationId: 'corr-123',
    ok: true,
    body: { data: 'result' },
  });

  const result = parseInternalReply(json);

  assert.ok(result !== null);
  assert.equal(result.correlationId, 'corr-123');
  assert.equal(result.ok, true);
  assert.deepEqual(result.body, { data: 'result' });
});

test('parseInternalReply: valid error reply', () => {
  const json = JSON.stringify({
    correlationId: 'corr-456',
    ok: false,
    body: null,
    error: { code: 'MX.TIMEOUT', message: 'Request timed out' },
  });

  const result = parseInternalReply(json);

  assert.ok(result !== null);
  assert.equal(result.correlationId, 'corr-456');
  assert.equal(result.ok, false);
  assert.deepEqual(result.error, { code: 'MX.TIMEOUT', message: 'Request timed out' });
});

test('parseInternalReply: rejects invalid JSON', () => {
  assert.equal(parseInternalReply('not json'), null);
  assert.equal(parseInternalReply('{malformed'), null);
});

test('parseInternalReply: rejects non-object JSON', () => {
  assert.equal(parseInternalReply('"string"'), null);
  assert.equal(parseInternalReply('123'), null);
  assert.equal(parseInternalReply('null'), null);
  assert.equal(parseInternalReply('[]'), null);
});

test('parseInternalReply: rejects missing correlationId', () => {
  const json = JSON.stringify({ ok: true, body: {} });
  assert.equal(parseInternalReply(json), null);
});

test('parseInternalReply: rejects non-string correlationId', () => {
  const json = JSON.stringify({ correlationId: 123, ok: true, body: {} });
  assert.equal(parseInternalReply(json), null);
});

test('parseInternalReply: rejects missing ok', () => {
  const json = JSON.stringify({ correlationId: 'c', body: {} });
  assert.equal(parseInternalReply(json), null);
});

test('parseInternalReply: rejects non-boolean ok', () => {
  const json = JSON.stringify({ correlationId: 'c', ok: 'true', body: {} });
  assert.equal(parseInternalReply(json), null);
});

// ============================================================================
// normalizeInternalReply Tests (Tolerant Reader)
// ============================================================================

test('normalizeInternalReply: full InternalReply format', () => {
  const raw = JSON.stringify({
    correlationId: 'corr-full',
    ok: true,
    body: { value: 42 },
  });

  const result = normalizeInternalReply(raw, 'fallback');

  assert.equal(result.correlationId, 'corr-full'); // Uses actual, not fallback
  assert.equal(result.ok, true);
  assert.deepEqual(result.body, { value: 42 });
});

test('normalizeInternalReply: semi reply with ok:true and body', () => {
  const raw = JSON.stringify({
    ok: true,
    body: { semi: 'reply' },
  });

  const result = normalizeInternalReply(raw, 'fallback-corr');

  assert.equal(result.correlationId, 'fallback-corr'); // Uses fallback
  assert.equal(result.ok, true);
  assert.deepEqual(result.body, { semi: 'reply' });
});

test('normalizeInternalReply: semi reply with ok:false and error', () => {
  const raw = JSON.stringify({
    ok: false,
    error: { code: 'ERR', message: 'Something failed' },
  });

  const result = normalizeInternalReply(raw, 'fallback-corr');

  assert.equal(result.ok, false);
  // Implementation includes details: undefined when not provided
  assert.deepEqual(result.error, { code: 'ERR', message: 'Something failed', details: undefined });
});

test('normalizeInternalReply: simple result object { result: ... }', () => {
  const raw = JSON.stringify({
    result: [1, 2, 3],
  });

  const result = normalizeInternalReply(raw, 'fallback-corr');

  assert.equal(result.correlationId, 'fallback-corr');
  assert.equal(result.ok, true);
  assert.deepEqual(result.body, [1, 2, 3]);
});

test('normalizeInternalReply: when no ok field, result field triggers success path', () => {
  // When ok is NOT present but result IS present, the implementation
  // uses the dedicated 'result' handler path which returns result directly
  // (doesn't go through pickBody which would prefer body)
  const raw = JSON.stringify({
    result: 'from-result',
    body: 'from-body',  // This is ignored when no 'ok' field
  });

  const result = normalizeInternalReply(raw, 'fb');

  // Per implementation: if (!'ok' in parsed && 'result' in parsed) => body = result
  assert.equal(result.body, 'from-result');
});

test('normalizeInternalReply: arbitrary object is treated as successful body', () => {
  const raw = JSON.stringify({
    foo: 'bar',
    num: 123,
  });

  const result = normalizeInternalReply(raw, 'fallback');

  assert.equal(result.ok, true);
  assert.deepEqual(result.body, { foo: 'bar', num: 123 });
});

test('normalizeInternalReply: object with error shape but no ok:false is still success', () => {
  // Per the implementation comment: "we do NOT infer failure without ok=false"
  const raw = JSON.stringify({
    error: { code: 'X', message: 'Y' },
  });

  const result = normalizeInternalReply(raw, 'fb');

  assert.equal(result.ok, true);
  // The whole object becomes the body since no ok field
  assert.deepEqual(result.body, { error: { code: 'X', message: 'Y' } });
});

test('normalizeInternalReply: non-object JSON values', () => {
  // Array
  const r1 = normalizeInternalReply('[1,2,3]', 'fb');
  assert.equal(r1.ok, true);
  assert.deepEqual(r1.body, [1, 2, 3]);

  // Number
  const r2 = normalizeInternalReply('42', 'fb');
  assert.equal(r2.ok, true);
  assert.equal(r2.body, 42);

  // String
  const r3 = normalizeInternalReply('"hello"', 'fb');
  assert.equal(r3.ok, true);
  assert.equal(r3.body, 'hello');

  // Boolean
  const r4 = normalizeInternalReply('true', 'fb');
  assert.equal(r4.ok, true);
  assert.equal(r4.body, true);

  // JSON null
  const r5 = normalizeInternalReply('null', 'fb');
  assert.equal(r5.ok, true);
  assert.equal(r5.body, null);
});

test('normalizeInternalReply: invalid JSON treated as raw string body', () => {
  const result = normalizeInternalReply('not valid json at all', 'fb');

  assert.equal(result.correlationId, 'fb');
  assert.equal(result.ok, true);
  assert.equal(result.body, 'not valid json at all');
});

test('normalizeInternalReply: error details are preserved', () => {
  const raw = JSON.stringify({
    ok: false,
    error: {
      code: 'MX.AUTH.FORBIDDEN',
      message: 'Access denied',
      details: { requiredScope: 'admin', actualScope: 'user' },
    },
  });

  const result = normalizeInternalReply(raw, 'fb');

  assert.equal(result.ok, false);
  assert.deepEqual(result.error, {
    code: 'MX.AUTH.FORBIDDEN',
    message: 'Access denied',
    details: { requiredScope: 'admin', actualScope: 'user' },
  });
});

test('normalizeInternalReply: incomplete error (missing code/message) is not picked', () => {
  const raw = JSON.stringify({
    ok: false,
    error: { details: 'no code or message' },
  });

  const result = normalizeInternalReply(raw, 'fb');

  assert.equal(result.ok, false);
  assert.equal(result.error, undefined); // Error not picked due to missing required fields
});

// ============================================================================
// isJsonNullPayload Tests
// ============================================================================

test('isJsonNullPayload: detects JSON null', () => {
  assert.equal(isJsonNullPayload('null'), true);
  assert.equal(isJsonNullPayload('  null  '), true); // With whitespace
  assert.equal(isJsonNullPayload('\tnull\n'), true);
});

test('isJsonNullPayload: rejects non-null values', () => {
  assert.equal(isJsonNullPayload('true'), false);
  assert.equal(isJsonNullPayload('false'), false);
  assert.equal(isJsonNullPayload('0'), false);
  assert.equal(isJsonNullPayload('""'), false);
  assert.equal(isJsonNullPayload('{}'), false);
  assert.equal(isJsonNullPayload('[]'), false);
  assert.equal(isJsonNullPayload('"null"'), false); // String "null", not JSON null
  assert.equal(isJsonNullPayload('undefined'), false);
});

// ============================================================================
// Protocol Compliance Tests
// ============================================================================

test('protocol: InternalRequest must include _mx for routing metadata', () => {
  // This test verifies the protocol requirement that all internal requests
  // must carry routing metadata via the _mx field
  const validRequest: InternalRequest = {
    correlationId: 'test-123',
    replyTo: '_local.replies.emits.test-123',
    params: { action: 'test' },
    _mx: {
      fromRealm: 'source-realm',
      incomingProfile: 'mxenv1',
      msgId: 'optional-msg-id',
      traceId: 'optional-trace-id',
    },
  };

  const json = JSON.stringify(validRequest);
  const parsed = parseInternalRequest(json);

  assert.ok(parsed !== null);
  assert.equal(parsed._mx.fromRealm, 'source-realm');
  assert.equal(parsed._mx.incomingProfile, 'mxenv1');
  assert.equal(parsed._mx.msgId, 'optional-msg-id');
  assert.equal(parsed._mx.traceId, 'optional-trace-id');
});

test('protocol: InternalReply error shape follows MATRIX-PROTOCOL-6', () => {
  // Per MATRIX-PROTOCOL-6 Section 52, errors must have code and message
  const errorReply: InternalReply = {
    correlationId: 'err-001',
    ok: false,
    body: null,
    error: {
      code: 'MX.TIMEOUT',
      message: 'Request timed out after 5000ms',
      details: { timeoutMs: 5000, target: 'realm/component.accepts.method' },
    },
  };

  const json = JSON.stringify(errorReply);
  const parsed = parseInternalReply(json);

  assert.ok(parsed !== null);
  assert.equal(parsed.ok, false);
  assert.ok(parsed.error !== undefined);

  // Verify normalized version also preserves error structure
  const normalized = normalizeInternalReply(json, 'fb');
  assert.equal(normalized.ok, false);
  assert.equal(normalized.error?.code, 'MX.TIMEOUT');
  assert.equal(normalized.error?.message, 'Request timed out after 5000ms');
});
