import type { MatrixClient } from 'matrix-js-sdk';
import pkg from '../../../package.json';

// Lotus E2EE investigation kit — capture-only console diagnostics.
//
// Installs pass-through wrappers around `console.warn` / `console.error` that
// ring-buffer any log line matching the KE-1..KE-4 bug-cluster signatures
// (E2EE KE-1..4 capture; see LOTUS_TODO.md). It NEVER swallows a log call — the
// original console method is always invoked — and it performs NO network I/O.
// The report metadata is limited to SDK version / device id / user id / sync
// state; the captured log lines themselves are intentional evidence and may
// contain event ids or matrix ids exactly as the SDK logged them.

export type CryptoDiagLevel = 'warn' | 'error';

export type CryptoDiagEntry = {
  /** ISO-8601 UTC timestamp of when the line was captured. */
  ts: string;
  level: CryptoDiagLevel;
  /** Which KE bucket the signature belongs to, e.g. `KE-1`. */
  ke: string;
  /** Human-readable label of the matched signature. */
  signature: string;
  /** The serialized console line (best-effort). */
  message: string;
};

type Signature = {
  ke: string;
  label: string;
  re: RegExp;
};

// Ordered most-specific-first so the recorded label is the tightest match.
const SIGNATURES: Signature[] = [
  { ke: 'KE-1', label: 'already exists', re: /already exists/i },
  { ke: 'KE-2', label: 'missing key at index', re: /missing key at index/i },
  {
    ke: 'KE-2',
    label: 'io.element.call.encryption_keys',
    re: /io\.element\.call\.encryption_keys/,
  },
  { ke: 'KE-2', label: 'MissingKey', re: /MissingKey/ },
  { ke: 'KE-3', label: 'DecryptionError', re: /DecryptionError/ },
  { ke: 'KE-4', label: 'update_delayed_event', re: /update_delayed_event/ },
  { ke: 'KE-4', label: 'delayed event', re: /delayed event/i },
];

const MAX_ENTRIES = 200;

const entries: CryptoDiagEntry[] = [];

let installed = false;
let originalWarn: ((...data: unknown[]) => void) | undefined;
let originalError: ((...data: unknown[]) => void) | undefined;

const stringifyArg = (arg: unknown): string => {
  if (typeof arg === 'string') return arg;
  if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
  try {
    return JSON.stringify(arg);
  } catch {
    return String(arg);
  }
};

const capture = (level: CryptoDiagLevel, args: unknown[]): void => {
  const message = args.map(stringifyArg).join(' ');
  const sig = SIGNATURES.find((s) => s.re.test(message));
  if (!sig) return;

  entries.push({
    ts: new Date().toISOString(),
    level,
    ke: sig.ke,
    signature: sig.label,
    message,
  });
  // Ring-buffer: keep only the most recent MAX_ENTRIES.
  while (entries.length > MAX_ENTRIES) {
    entries.shift();
  }
};

/**
 * Install the capture-only console wrappers. Idempotent — calling it more than
 * once is a no-op. Safe to call as early as possible during app boot.
 */
export const installCryptoDiagLog = (): void => {
  if (installed) return;
  installed = true;

  originalWarn = console.warn.bind(console);
  originalError = console.error.bind(console);

  console.warn = (...args: unknown[]): void => {
    capture('warn', args);
    originalWarn?.(...args);
  };
  console.error = (...args: unknown[]): void => {
    capture('error', args);
    originalError?.(...args);
  };
};

/** A snapshot copy of the current capture buffer (most-recent-last). */
export const getCryptoDiagEntries = (): CryptoDiagEntry[] => entries.slice();

const readSdkVersion = (mx?: MatrixClient): string => {
  // Prefer the value the running client reports; fall back to the declared pin.
  const declared = (pkg.dependencies as Record<string, string> | undefined)?.['matrix-js-sdk'];
  const clientVersion = (mx as unknown as { getSdkVersion?: () => string } | undefined)
    ?.getSdkVersion;
  if (typeof clientVersion === 'function') {
    try {
      return clientVersion.call(mx) || declared || 'unknown';
    } catch {
      // fall through to the declared pin
    }
  }
  return declared ?? 'unknown';
};

/**
 * Build a self-contained JSON diagnostic report string. Contains only the SDK
 * version, device id, user id, sync state, crypto readiness, and the captured
 * KE signature buffer — no message content, tokens, or other PII.
 */
export const buildCryptoDiagReport = (mx?: MatrixClient): string => {
  const buffer = getCryptoDiagEntries();
  const countsByKe: Record<string, number> = {};
  buffer.forEach((entry) => {
    countsByKe[entry.ke] = (countsByKe[entry.ke] ?? 0) + 1;
  });

  const report = {
    kind: 'lotus-crypto-diag',
    generatedAt: new Date().toISOString(),
    sdkVersion: readSdkVersion(mx),
    deviceId: mx?.getDeviceId() ?? null,
    userId: mx?.getUserId() ?? null,
    syncState: mx?.getSyncState() ?? null,
    cryptoReady: Boolean(mx?.getCrypto()),
    entryCount: buffer.length,
    maxEntries: MAX_ENTRIES,
    countsByKe,
    entries: buffer,
  };

  return JSON.stringify(report, null, 2);
};
