import {
  clearRepoSetup,
  clearRuntimeContext,
  getRepoSetup,
  getRuntimeContext,
  getRuntimeEnv,
  registerRepoSetup,
  registerRuntimeContext,
  runtimeHttp,
  runtimeJson,
} from "./runtime.mjs";

export function defineTestkitSetup(setup) {
  return setup || {};
}

export function defineHttpProfile(profile) {
  return profile || {};
}

export function service(config) {
  return config || {};
}

export function localDatabase(options = {}) {
  return {
    provider: "local",
    ...options,
  };
}

export function commandStep(cmd, options = {}) {
  return {
    kind: "command",
    cmd,
    cwd: options.cwd,
    inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
  };
}

export function sqlFileStep(filePath, options = {}) {
  return {
    kind: "sql-file",
    path: filePath,
    cwd: options.cwd,
    inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
  };
}

export function moduleStep(specifier, options = {}) {
  return {
    kind: "module",
    specifier,
    cwd: options.cwd,
    inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
  };
}

export function goService(options = {}) {
  const cwd = options.cwd || ".";
  const port = requiredNumber(options.port, "goService port");
  const readyPath = options.readyPath || "/health";
  const baseUrl = options.baseUrl || "http://127.0.0.1:{port}";

  return {
    ...service(options),
    local: {
      cwd,
      start: options.start || defaultGoStartCommand(options),
      port,
      baseUrl,
      readyUrl: options.readyUrl || `${baseUrl}${readyPath}`,
      readyTimeoutMs: options.readyTimeoutMs,
      env: options.env || {},
    },
  };
}

export function nextService(options = {}) {
  const cwd = options.cwd || ".";
  const port = requiredNumber(options.port, "nextService port");
  const baseUrl = options.baseUrl || "http://127.0.0.1:{port}";

  return {
    ...service(options),
    local: {
      cwd,
      start: options.start || "./node_modules/.bin/next dev -p {port}",
      port,
      baseUrl,
      readyUrl: options.readyUrl || baseUrl,
      readyTimeoutMs: options.readyTimeoutMs,
      env: options.env || {},
    },
  };
}

export function tsxService(options = {}) {
  const cwd = options.cwd || ".";
  const port = requiredNumber(options.port, "tsxService port");
  const baseUrl = options.baseUrl || "http://127.0.0.1:{port}";
  const entry = options.entry || "src/index.ts";

  return {
    ...service(options),
    local: {
      cwd,
      start: options.start || `./node_modules/.bin/tsx watch ${entry}`,
      port,
      baseUrl,
      readyUrl: options.readyUrl || `${baseUrl}${options.readyPath || "/health"}`,
      readyTimeoutMs: options.readyTimeoutMs,
      env: options.env || {},
    },
  };
}

export function clerkSessionProfile(options = {}) {
  const apiBase = options.apiBase || "https://api.clerk.com/v1";
  const secretKeyEnv = options.secretKeyEnv || "CLERK_SECRET_KEY";
  const needsAuth = options.needsAuth !== false;

  return defineHttpProfile({
    auth: {
      setup() {
        return resolveClerkSession({
          apiBase,
          secretKey: envValue(secretKeyEnv),
          needsAuth,
        });
      },
      headers(setupData) {
        return getClerkAuthHeaders(setupData, { apiBase });
      },
    },
  });
}

export function jsonSessionProfile(options = {}) {
  const loginPath = options.loginPath || "/api/auth/login";
  const cookieName = options.cookieName || "session";
  const body =
    typeof options.body === "function"
      ? options.body
      : () => ({
          username: envValue(options.usernameEnv || "TESTKIT_USERNAME"),
          password: envValue(options.passwordEnv || "TESTKIT_PASSWORD"),
        });

  return defineHttpProfile({
    auth: {
      setup({ env }) {
        const requestBody = body({ env });
        const res = runtimeHttp.post(`${env.BASE}${loginPath}`, JSON.stringify(requestBody), {
          headers: {
            "Content-Type": "application/json",
            ...(env.routeParams || {}),
            ...(options.headers || {}),
          },
        });

        if (res.status !== (options.successStatus || 200)) {
          throw new Error(`Login failed (${res.status}): ${res.body}`);
        }

        const cookie = res.cookies?.[cookieName]?.[0]?.value ?? null;
        if (!cookie) {
          throw new Error(`No ${cookieName} cookie returned from login`);
        }

        return {
          cookie,
          body: safeJson(res),
        };
      },
      headers(session) {
        if (!session?.cookie) return {};
        return {
          Cookie: `${cookieName}=${session.cookie}`,
        };
      },
    },
  });
}

export {
  clearRepoSetup,
  clearRuntimeContext,
  getRepoSetup,
  getRuntimeContext,
  registerRepoSetup,
  registerRuntimeContext,
  runtimeHttp,
  runtimeJson,
};

function defaultGoStartCommand(options) {
  if (options.command) {
    return `go run ${options.command}`;
  }
  if (options.entrypoint) {
    return `go run ${options.entrypoint}`;
  }
  return "go run ./cmd/server";
}

function requiredNumber(value, label) {
  if (!Number.isInteger(value) || value <= 0) {
    throw new Error(`${label} must be a positive integer`);
  }
  return value;
}

function envValue(name) {
  const env = getRuntimeEnv();
  const value = env?.rawEnv?.[name] || env?.[name];
  if (!value) {
    throw new Error(`Missing required env var "${name}" for testkit setup`);
  }
  return value;
}

function safeJson(response) {
  try {
    return runtimeJson(response);
  } catch {
    return null;
  }
}

function resolveClerkSession({ apiBase, secretKey, needsAuth }) {
  if (!needsAuth) {
    return { jwt: null };
  }

  const headers = { Authorization: `Bearer ${secretKey}` };
  const usersRes = runtimeHttp.get(`${apiBase}/users?limit=1&order_by=-created_at`, {
    headers,
  });
  if (usersRes.status !== 200) {
    throw new Error(`Clerk list users failed (${usersRes.status}): ${usersRes.body}`);
  }

  const users = runtimeJson(usersRes);
  if (!users.length) {
    throw new Error("No Clerk users found for testkit Clerk auth profile");
  }

  const userId = users[0].id;
  const sessionsRes = runtimeHttp.get(
    `${apiBase}/sessions?user_id=${userId}&status=active&limit=1`,
    { headers }
  );
  if (sessionsRes.status !== 200) {
    throw new Error(`Clerk list sessions failed (${sessionsRes.status}): ${sessionsRes.body}`);
  }

  const sessions = runtimeJson(sessionsRes);
  if (!sessions?.length) {
    throw new Error("No active Clerk session found for testkit Clerk auth profile");
  }

  const sessionId = sessions[0].id;
  const tokenRes = runtimeHttp.post(`${apiBase}/sessions/${sessionId}/tokens`, null, {
    headers: {
      ...headers,
      "Content-Type": "application/json",
    },
  });
  if (tokenRes.status !== 200) {
    throw new Error(`Clerk create token failed (${tokenRes.status}): ${tokenRes.body}`);
  }

  return {
    jwt: runtimeJson(tokenRes).jwt,
    sessionId,
    secretKey,
  };
}

function getClerkAuthHeaders(setupData, { apiBase }) {
  if (!setupData?.jwt) return {};

  if (!setupData.sessionId || !setupData.secretKey) {
    return {
      Authorization: `Bearer ${setupData.jwt}`,
      "Content-Type": "application/json",
    };
  }

  const tokenRes = runtimeHttp.post(`${apiBase}/sessions/${setupData.sessionId}/tokens`, null, {
    headers: {
      Authorization: `Bearer ${setupData.secretKey}`,
      "Content-Type": "application/json",
    },
  });
  if (tokenRes.status !== 200) {
    return {
      Authorization: `Bearer ${setupData.jwt}`,
      "Content-Type": "application/json",
    };
  }

  const nextJwt = runtimeJson(tokenRes).jwt;
  setupData.jwt = nextJwt;
  return {
    Authorization: `Bearer ${nextJwt}`,
    "Content-Type": "application/json",
  };
}
