import { HttpException } from '@nestjs/common';
import type { ApiErrorCode } from '@thebet365/shared';
import { isCodedExceptionResponse } from '../../../shared/common/app-error';

export type SmokeTestStep = {
  label: string;
  input?: string;
  expected: string;
  actual: string;
};

let activeSteps: SmokeTestStep[] = [];

export function beginSmokeSteps() {
  activeSteps = [];
}

export function drainSmokeSteps(): SmokeTestStep[] {
  const steps = [...activeSteps];
  activeSteps = [];
  return steps;
}

function formatValue(value: unknown): string {
  if (value === null) return 'null';
  if (value === undefined) return 'undefined';
  if (typeof value === 'bigint') return value.toString();
  if (typeof value === 'object') {
    try {
      return JSON.stringify(value);
    } catch {
      return String(value);
    }
  }
  return String(value);
}

export function recordStep(
  label: string,
  input: unknown,
  expected: unknown,
  actual: unknown,
  ok: boolean,
) {
  activeSteps.push({
    label,
    input: input === undefined ? undefined : formatValue(input),
    expected: formatValue(expected),
    actual: formatValue(actual),
  });
  if (!ok) {
    throw new Error(`${label}: expected ${formatValue(expected)}, got ${formatValue(actual)}`);
  }
}

export function expectEqual<T>(label: string, actual: T, expected: T, input?: unknown) {
  const ok = actual === expected;
  recordStep(label, input, expected, actual, ok);
}

export function expectTrue(label: string, condition: boolean, input?: unknown, failHint?: string) {
  recordStep(label, input, 'true', condition ? 'true' : failHint ?? 'false', condition);
}

export function expectFalse(label: string, condition: boolean, input?: unknown, failHint?: string) {
  recordStep(label, input, 'false', condition ? failHint ?? 'true' : 'false', !condition);
}

function resolveErrorCode(err: unknown): string | null {
  if (err instanceof HttpException) {
    const res = err.getResponse();
    if (isCodedExceptionResponse(res)) return res.code;
  }
  if (typeof err === 'object' && err !== null && 'response' in err) {
    const res = (err as { response: unknown }).response;
    if (isCodedExceptionResponse(res)) return res.code;
  }
  return null;
}

export async function expectThrows(
  label: string,
  fn: () => void | Promise<void>,
  messageIncludes: string,
  input?: unknown,
) {
  try {
    await fn();
    recordStep(label, input, `error contains "${messageIncludes}"`, 'no error thrown', false);
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    recordStep(label, input, `error contains "${messageIncludes}"`, msg, msg.includes(messageIncludes));
  }
}

export async function expectAppErrorThrows(
  label: string,
  fn: () => void | Promise<void>,
  code: ApiErrorCode,
  input?: unknown,
) {
  try {
    await fn();
    recordStep(label, input, `error code ${code}`, 'no error thrown', false);
  } catch (err) {
    const actual = resolveErrorCode(err) ?? (err instanceof Error ? err.message : String(err));
    recordStep(label, input, `error code ${code}`, actual, actual === code);
  }
}

export function formatStepsForResult(steps: SmokeTestStep[]): string[] {
  return steps.map((step, index) => {
    const lines = [`${index + 1}. ${step.label}`];
    if (step.input) lines.push(`   input: ${step.input}`);
    lines.push(`   expected: ${step.expected}`);
    lines.push(`   actual: ${step.actual}`);
    return lines.join('\n');
  });
}
