import { check as k6Check, group as k6Group } from "k6";
import { Rate } from "k6/metrics";
import { emitArtifact } from "./artifacts.js";

export const runtimeFailures = new Rate("testkit_runtime_failures");
const failureState = createFailureState();

export function singleIterationOptions(overrides = {}) {
  return {
    iterations: 1,
    thresholds: {
      checks: ["rate==1.0"],
      testkit_runtime_failures: ["rate==0"],
      ...(overrides.thresholds || {}),
    },
    ...overrides,
  };
}

export const defaultOptions = singleIterationOptions();

export function check(value, checks) {
  let allPassed = true;

  for (const [name, predicate] of Object.entries(checks || {})) {
    const checkName = normalizeLabel(name, "unnamed check");
    const passed = k6Check(value, { [checkName]: predicate });
    if (!passed) {
      recordFailureDetail({
        kind: "k6-check",
        key: buildFailureKey(failureState.groupStack, checkName),
        title: checkName,
        checkName,
        groupPath: [...failureState.groupStack],
        phase: failureState.phase,
      });
      allPassed = false;
    }
  }

  return allPassed;
}

export function group(name, fn) {
  const groupName = normalizeLabel(name, "unnamed group");

  return k6Group(groupName, () => {
    failureState.groupStack.push(groupName);
    try {
      return fn();
    } finally {
      failureState.groupStack.pop();
    }
  });
}

export function json(res) {
  return JSON.parse(res.body);
}

export function contains(rows, field, value) {
  return rows.some((row) => row[field] === value);
}

export function allMatch(rows, predicate) {
  return rows.length > 0 && rows.every(predicate);
}

export function isSorted(rows, field, direction = "asc") {
  if (rows.length <= 1) return true;

  for (let index = 1; index < rows.length; index += 1) {
    if (direction === "asc" && rows[index][field] < rows[index - 1][field]) {
      return false;
    }
    if (direction === "desc" && rows[index][field] > rows[index - 1][field]) {
      return false;
    }
  }

  return true;
}

export function recordRuntimeFailure() {
  runtimeFailures.add(1);
}

export function startFailureCollection(phase) {
  failureState.phase = normalizeLabel(phase, "exec");
  failureState.groupStack = [];
  failureState.detailsByKey = new Map();
}

export function emitFailureCollectionArtifact() {
  const failures = [...failureState.detailsByKey.values()]
    .sort((left, right) => left.key.localeCompare(right.key))
    .map((detail) => ({ ...detail }));

  if (failures.length > 0) {
    emitArtifact(
      "failure-details",
      {
        phase: failureState.phase,
        failures,
      },
      {
        kind: "testkit.failure-details",
        summary: `${failures.length} failure detail(s)`,
      }
    );
  }

  startFailureCollection(failureState.phase);
}

export function recordFailureDetail(detail) {
  const normalized = normalizeFailureDetail(detail);
  if (!normalized) return;

  const existing = failureState.detailsByKey.get(normalized.key);
  if (!existing) {
    failureState.detailsByKey.set(normalized.key, normalized);
    return;
  }

  existing.count += normalized.count;
}

function createFailureState() {
  return {
    phase: "exec",
    groupStack: [],
    detailsByKey: new Map(),
  };
}

function normalizeFailureDetail(detail) {
  if (!detail || typeof detail !== "object") return null;

  const kind = normalizeLabel(detail.kind, null);
  const key = normalizeLabel(detail.key, null);
  const title = normalizeLabel(detail.title, null);
  if (!kind || !key || !title) return null;

  const normalized = {
    kind,
    key,
    title,
    count: 1,
  };

  const checkName = normalizeLabel(detail.checkName, null);
  if (checkName) normalized.checkName = checkName;

  const phase = normalizeLabel(detail.phase, null);
  if (phase) normalized.phase = phase;

  const message = normalizeLabel(detail.message, null);
  if (message) normalized.message = message;

  const groupPath = Array.isArray(detail.groupPath)
    ? detail.groupPath.map((entry) => normalizeLabel(entry, null)).filter(Boolean)
    : [];
  if (groupPath.length > 0) normalized.groupPath = groupPath;

  return normalized;
}

function buildFailureKey(groupPath, title) {
  if (!Array.isArray(groupPath) || groupPath.length === 0) return title;
  return [...groupPath, title].join(" > ");
}

function normalizeLabel(value, fallback) {
  if (typeof value !== "string") return fallback;
  const normalized = value.trim();
  return normalized.length > 0 ? normalized : fallback;
}
