export type RuntimeMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";

export interface RuntimeHeaders {
  [key: string]: string;
}

export interface RuntimeCookie {
  value: string;
}

export interface RuntimeResponse {
  __testkit?: {
    httpTrace?: RuntimeHttpTrace;
  };
  body: string;
  cookies?: Record<string, RuntimeCookie[]>;
  headers?: Record<string, string | string[] | undefined>;
  status: number;
  timings?: {
    duration: number;
  };
}

export interface RuntimeHttpTrace {
  id: string;
  requestId: string;
  startedAt: string;
  finishedAt?: string;
  durationMs?: number;
  method: RuntimeMethod;
  path: string;
  url: string;
  requestHeaders: Record<string, string | string[] | null>;
  response?: {
    status: number | null;
    contentType: string | string[] | null;
    bodyPreview: string | null;
    bodyTruncated: boolean;
  };
}

export interface RuntimeOptions {
  [key: string]: unknown;
  thresholds?: Record<string, unknown>;
}

export interface RuntimeArtifactOptions {
  contentType?: string;
  kind?: string;
  summary?: string;
}

export interface WaitForOptions {
  description?: string;
  intervalSeconds?: number;
}

export interface RuntimeEnv {
  BASE: string;
  MACHINE_ID?: string;
  routeParams: RuntimeHeaders;
}

export interface TestkitRuntimeContext {
  active: boolean;
  leaseId: string | null;
  namespace: string;
  rawEnv: Record<string, string | undefined>;
  runtimeId: string | null;
}

export interface RuntimeDb {
  exec(sql: string): unknown;
  query<T = Record<string, unknown>>(sql: string): T[];
}

export interface RuntimeDalContext {
  db: RuntimeDb;
  truncate(...tables: string[]): void;
}

export interface HttpRequestParams {
  headers?: RuntimeHeaders;
  redirects?: number;
}

export interface RuntimeHttpClient {
  del(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
  file(data: unknown, filename?: string, contentType?: string): unknown;
  get(url: string, params?: HttpRequestParams): RuntimeResponse;
  patch(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
  post(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
  put(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
}

export interface Metric {
  add(value: number): void;
}

export declare class Rate implements Metric {
  constructor(name: string, isTime?: boolean);
  add(value: number): void;
}

export declare class Trend implements Metric {
  constructor(name: string, isTime?: boolean);
  add(value: number): void;
}

export interface HttpClientConfig<TSetup = unknown> {
  baseUrl: string;
  defaultHeaders?: RuntimeHeaders;
  getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void;
  getRawHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void;
  routeHeaders?: RuntimeHeaders;
}

export interface HttpClient<TSetup = unknown> {
  delete(path: string, setupData?: TSetup | null, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  get(path: string, setupData?: TSetup | null, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  getWithHeaders(
    path: string,
    setupData?: TSetup | null,
    extraHeaders?: RuntimeHeaders
  ): RuntimeResponse;
  patch(
    path: string,
    setupData?: TSetup | null,
    body?: unknown,
    extraHeaders?: RuntimeHeaders
  ): RuntimeResponse;
  post(
    path: string,
    setupData?: TSetup | null,
    body?: unknown,
    extraHeaders?: RuntimeHeaders
  ): RuntimeResponse;
  put(
    path: string,
    setupData?: TSetup | null,
    body?: unknown,
    extraHeaders?: RuntimeHeaders
  ): RuntimeResponse;
  raw(
    method: RuntimeMethod,
    path: string,
    body?: unknown,
    extraHeaders?: RuntimeHeaders
  ): RuntimeResponse;
  rawDelete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  rawGet(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  rawPatch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  rawPost(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  rawPut(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
  request(
    method: RuntimeMethod,
    path: string,
    setupData?: TSetup | null,
    body?: unknown,
    extraHeaders?: RuntimeHeaders
  ): RuntimeResponse;
}

export declare const check: <T>(
  value: T,
  checks: Record<string, (value: T) => boolean>
) => boolean;
export declare const evaluateCheck: <T>(
  value: T,
  checkName: string,
  predicate: (value: T) => boolean,
  detailFactory?: (() => Record<string, unknown>) | null
) => boolean;
export declare const fail: (message: string) => never;
export declare const group: (name: string, fn: () => void) => void;
export declare const sleep: (seconds?: number) => void;

export declare const http: RuntimeHttpClient;

export declare function file(data: unknown, filename?: string, contentType?: string): unknown;
export declare function json<T = unknown>(response: Pick<RuntimeResponse, "body">): T;
export declare function safeJson<T = unknown>(
  response: Pick<RuntimeResponse, "body">
): { ok: true; value: T } | { ok: false; error: string };
export declare function remainingTimeSeconds(): number;
export declare function waitFor<T>(
  read: () => T,
  isReady: (value: T) => boolean,
  options?: WaitForOptions
): T;
export declare function emitArtifact(
  name: string,
  data: unknown,
  options?: RuntimeArtifactOptions
): void;
export declare function contains<T extends Record<string, unknown>>(
  rows: T[],
  field: keyof T | string,
  value: unknown
): boolean;
export declare function allMatch<T>(
  rows: T[],
  predicate: (row: T) => boolean
): boolean;
export declare function isSorted<T extends Record<string, unknown>>(
  rows: T[],
  field: keyof T | string,
  direction?: "asc" | "desc"
): boolean;

export declare function singleIterationOptions(overrides?: RuntimeOptions): RuntimeOptions;
export declare const defaultOptions: RuntimeOptions;
export declare const httpDefaultOptions: RuntimeOptions;

export declare function createDalContext(db?: RuntimeDb): RuntimeDalContext;
export declare function openDb(): RuntimeDb;
export declare function truncate(db: RuntimeDb, ...tables: string[]): void;
export declare function getTestkitContext(): TestkitRuntimeContext;

export declare function getEnv(): RuntimeEnv;
export declare function getHttpTrace(response: RuntimeResponse): RuntimeHttpTrace | null;
export declare function summarizeHttpTrace(response: RuntimeResponse): RuntimeHttpTrace | null;
export declare function toBodyPreview(response: RuntimeResponse): string | null;
export declare function createHttpClient<TSetup = unknown>(
  config: HttpClientConfig<TSetup>
): HttpClient<TSetup>;
export declare function makeReq<TSetup = unknown>(
  baseUrl: string,
  routeHeaders?: RuntimeHeaders,
  getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
): HttpClient<TSetup>["request"];
export declare function makeRawReq(
  baseUrl: string,
  routeHeaders?: RuntimeHeaders,
  getRawHeaders?: (setupData?: never) => RuntimeHeaders | void
): HttpClient["raw"];
export declare function makeGetWithHeaders<TSetup = unknown>(
  baseUrl: string,
  routeHeaders?: RuntimeHeaders,
  getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
): HttpClient<TSetup>["getWithHeaders"];

export declare function expectStatus(
  response: RuntimeResponse,
  expected: number,
  label?: string | null
): boolean;
export declare function expectStatusOneOf(
  response: RuntimeResponse,
  expectedValues: number[],
  label?: string | null
): boolean;
export declare function expectNotStatus(
  response: RuntimeResponse,
  unexpected: number,
  label?: string | null
): boolean;
export declare function expectJson<T = unknown>(
  response: RuntimeResponse,
  predicate: (value: T) => boolean,
  label?: string | null
): boolean;
export declare function expectJsonPath(
  response: RuntimeResponse,
  path: string,
  predicate: (value: unknown) => boolean,
  label?: string | null
): boolean;

declare global {
  const __ENV: Record<string, string | undefined>;
}

export {};
