import { fail } from "k6";
import {
  defaultOptions,
  emitFailureCollectionArtifact,
  recordFailureDetail,
  recordRuntimeFailure,
  startFailureCollection,
} from "./checks.js";
import {
  createHttpClient,
  emitHttpTraceCollectionArtifact,
  getEnv,
  startHttpTraceCollection,
} from "./http.js";
import {
  clearRuntimeContext,
  registerRuntimeContext,
  resolveHttpProfile,
} from "../../setup/runtime.mjs";

export function defineHttpSuite(configOrRun, maybeRun) {
  const { config, run } = normalizeSuiteArgs(configOrRun, maybeRun);

  return {
    get options() {
      return mergeProfileConfig(config).options || defaultOptions;
    },
    setup() {
      const resolved = resolveRuntimeConfig(config);
      startFailureCollection("setup");
      startHttpTraceCollection("setup");
      try {
        registerRuntimeContext({ env: resolved.env, http: resolved.client.rawHttp || null });
        if (typeof resolved.auth?.setup !== "function") return null;
        return resolved.auth.setup({ env: resolved.env });
      } catch (error) {
        recordFailureDetail(buildRuntimeExceptionDetail("setup", error));
        recordRuntimeFailure();
        fail(formatFatalSuiteError("setup", error));
      } finally {
        emitFailureCollectionArtifact();
        emitHttpTraceCollectionArtifact();
        clearRuntimeContext();
      }
    },
    exec(setupData) {
      const resolved = resolveRuntimeConfig(config);
      startFailureCollection("exec");
      startHttpTraceCollection("exec");
      try {
        registerRuntimeContext({ env: resolved.env, http: resolved.client.rawHttp || null });
        return run({
          env: resolved.env,
          req: resolved.client.request,
          rawReq: resolved.client.raw,
          getWithHeaders: resolved.client.getWithHeaders,
          setupData,
          session: setupData,
        });
      } catch (error) {
        recordFailureDetail(buildRuntimeExceptionDetail("exec", error));
        recordRuntimeFailure();
        fail(formatFatalSuiteError("exec", error));
      } finally {
        emitFailureCollectionArtifact();
        emitHttpTraceCollectionArtifact();
        clearRuntimeContext();
      }
    },
  };
}

function normalizeSuiteArgs(configOrRun, maybeRun) {
  if (typeof configOrRun === "function") {
    return { config: {}, run: configOrRun };
  }
  if (typeof maybeRun !== "function") {
    throw new Error("suite factory requires a run callback");
  }
  return { config: configOrRun || {}, run: maybeRun };
}

function callHeaders(builder, setupData, env) {
  if (typeof builder !== "function") return {};
  return builder(setupData, { env }) || {};
}

function mergeProfileConfig(config) {
  if (!config?.profile) return config || {};

  const profile = resolveHttpProfile(config.profile) || {};
  return {
    ...profile,
    ...config,
    auth: config.auth ?? profile.auth ?? null,
    headers: config.headers ?? profile.headers,
    rawHeaders: config.rawHeaders ?? profile.rawHeaders,
    options: config.options ?? profile.options,
    env: config.env ?? profile.env,
  };
}

function resolveRuntimeConfig(config) {
  const resolvedConfig = mergeProfileConfig(config);
  const env = {
    ...(resolvedConfig.env || getEnv()),
    rawEnv: __ENV,
  };
  const auth = resolvedConfig.auth || null;
  const client = createHttpClient({
    baseUrl: env.BASE,
    routeHeaders: env.routeParams,
    getHeaders(setupData) {
      return {
        ...callHeaders(auth?.headers, setupData, env),
        ...callHeaders(resolvedConfig.headers, setupData, env),
      };
    },
    getRawHeaders(setupData) {
      return callHeaders(resolvedConfig.rawHeaders, setupData, env);
    },
  });

  return {
    resolvedConfig,
    env,
    auth,
    client,
  };
}

function formatFatalSuiteError(phase, error) {
  if (error instanceof Error) {
    return `Uncaught testkit suite error during ${phase}: ${error.message}`;
  }
  return `Uncaught testkit suite error during ${phase}: ${String(error)}`;
}

function buildRuntimeExceptionDetail(phase, error) {
  const message =
    error instanceof Error ? error.message : String(error);
  const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
  const location = extractLocationFromStack(stack);
  return {
    kind: "runtime-exception",
    key: location ? `${location.path}:${location.line}:${location.column}` : `runtime-exception:${phase}:${message}`,
    title: "Uncaught runtime exception",
    message: `Uncaught testkit suite error during ${phase}: ${message}`,
    location,
    stack,
  };
}

function extractLocationFromStack(stack) {
  if (!stack) return null;
  const matches = [...String(stack).matchAll(/(file:\/\/[^\s)]+|\/[^\s):]+):(\d+):(\d+)/g)].map((match) => ({
    path: normalizeStackPath(match[1]),
    line: Number(match[2]),
    column: Number(match[3]),
  }));
  return matches[0] || null;
}

function normalizeStackPath(rawPath) {
  if (typeof rawPath !== "string") return rawPath;
  if (rawPath.startsWith("file://")) {
    return rawPath.slice("file://".length);
  }
  return rawPath;
}
