import { getBoolean, getRecord, getString, isRecord, safeJsonParse, type JsonObject } from '../util/guards';

export interface InternalMeta {
  fromRoot: string;
  /** @deprecated Use fromRoot */
  fromRealm?: string;
  incomingProfile: 'matrix3' | 'mxenv1';
  msgId?: string;
  principalKey?: string;
  traceId?: string;
}

export interface InternalRequest {
  correlationId: string;
  replyTo: string; // internal topic string
  params: unknown;
  _mx: InternalMeta;
}

/**
 * Wire-level shape emitted by FederationPeer onto the local bus.
 *
 * - Default mode includes `_mx` (required by the reference component_host).
 * - Integration mode may omit `_mx` if the local component system does not
 *   tolerate unknown fields, or if meta is conveyed out-of-band.
 * - `envelope` may be included for debugging / handler adaptation.
 */
export interface InternalRequestWire {
  correlationId: string;
  replyTo: string;
  params: unknown;
  _mx?: InternalMeta;
  envelope?: unknown;
}

export interface InternalReply {
  correlationId: string;
  ok: boolean;
  body: unknown;
  error?: { code: string; message: string; details?: unknown };
}

export function parseInternalRequest(json: string): InternalRequest | null {
  const parsed = safeJsonParse(json);
  if (!isRecord(parsed)) return null;
  if (typeof parsed.correlationId !== 'string') return null;
  if (typeof parsed.replyTo !== 'string') return null;
  // params may be any JSON value
  if (!isRecord(parsed._mx)) return null;
  // Accept both fromRoot and legacy fromRealm
  const fromRoot = parsed._mx.fromRoot ?? parsed._mx.fromRealm;
  if (typeof fromRoot !== 'string') return null;
  if (parsed._mx.incomingProfile !== 'matrix3' && parsed._mx.incomingProfile !== 'mxenv1') return null;
  return parsed as unknown as InternalRequest;
}

export function parseInternalReply(json: string): InternalReply | null {
  const parsed = safeJsonParse(json);
  if (!isRecord(parsed)) return null;
  if (typeof parsed.correlationId !== 'string') return null;
  if (typeof parsed.ok !== 'boolean') return null;
  return parsed as unknown as InternalReply;
}

function pickBody(obj: JsonObject): unknown {
  if ('body' in obj) return obj.body;
  if ('result' in obj) return obj.result;
  return obj;
}

function pickError(obj: JsonObject): InternalReply['error'] | undefined {
  const er = getRecord(obj, 'error');
  if (!er) return undefined;
  const code = getString(er, 'code');
  const message = getString(er, 'message');
  if (!code || !message) return undefined;
  const details = er['details'];
  return { code, message, details };
}

/**
 * Lenient decoder for internal replies.
 *
 * Goal: minimize handler friction (integration point), without breaking invariants.
 *
 * Accepted forms on the internal reply topic:
 *  1) Full InternalReply: { correlationId, ok, body, error? }
 *  2) "Semi" reply: { ok, body? } or { ok:false, error:{code,message,...} }
 *  3) Simple result object: { result: ... }  (implies ok=true, body=result)
 *  4) Any other JSON value (object/array/string/number/null): implies ok=true, body=value
 *  5) Non-JSON string: implies ok=true, body=raw string
 */
export function normalizeInternalReply(rawPayload: string, fallbackCorrelationId: string): InternalReply {
  let parsed: unknown;
  try {
    parsed = JSON.parse(rawPayload) as unknown;
  } catch {
    return { correlationId: fallbackCorrelationId, ok: true, body: rawPayload };
  }

  // Distinguish JSON null from parse failure (handled above).
  if (!isRecord(parsed)) {
    return { correlationId: fallbackCorrelationId, ok: true, body: parsed };
  }

  const corr = typeof parsed.correlationId === 'string' ? parsed.correlationId : fallbackCorrelationId;
  const ok = getBoolean(parsed, 'ok');
  if (ok !== undefined) {
    const body = pickBody(parsed);
    const err = ok ? undefined : pickError(parsed);
    return { correlationId: corr, ok, body, error: err };
  }

  // If the handler returned {result:...}, treat that as success.
  if ('result' in parsed) {
    return { correlationId: corr, ok: true, body: (parsed as JsonObject).result };
  }

  // "Just an object" -> treat as successful body.
  // If the object also contains an error shape, we do NOT infer failure without ok=false.
  return { correlationId: corr, ok: true, body: parsed };
}

// Helper for tests: detect JSON null payloads that were intentionally sent.
export function isJsonNullPayload(rawPayload: string): boolean {
  return rawPayload.trim() === 'null';
}
