import { randomUUID } from "node:crypto";

function parsePositiveInteger(value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) return fallback;
  return Math.min(max, Math.max(min, Math.trunc(parsed)));
}

function parseJsonObject(value, fallback = {}) {
  if (!value) return fallback;
  try {
    const parsed = JSON.parse(String(value));
    return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : fallback;
  } catch {
    return fallback;
  }
}

export function resolveAuthorityE2eBaseUrl(value, env = process.env) {
  const normalized = String(value || "").replace(/\/+$/g, "");
  if (/^https?:\/\//i.test(normalized)) return normalized;
  const origin = String(env.AUTHORITY_E2E_ORIGIN || "").replace(/\/+$/g, "");
  if (origin && normalized.startsWith("/")) return `${origin}${normalized}`;
  throw new Error(
    "AUTHORITY_E2E_BASE_URL must be absolute, or set AUTHORITY_E2E_ORIGIN for relative plugin paths",
  );
}

export function createAuthorityE2eHeaderProvider(env = process.env) {
  const staticHeaders = parseJsonObject(env.AUTHORITY_E2E_HEADER_JSON, {});
  const token = String(env.AUTHORITY_E2E_TOKEN || "").trim();
  const cookie = String(env.AUTHORITY_E2E_COOKIE || "").trim();
  return () => ({
    ...staticHeaders,
    ...(token ? { Authorization: /^Bearer\s+/i.test(token) ? token : `Bearer ${token}` } : {}),
    ...(cookie ? { Cookie: cookie } : {}),
  });
}

export function createAuthorityE2eFetchWithTimeout(
  timeoutMs,
  baseFetchImpl = typeof fetch === "function" ? fetch.bind(globalThis) : null,
) {
  if (typeof baseFetchImpl !== "function") {
    throw new Error("Authority E2E fetch unavailable");
  }
  return async (url, options = {}) => {
    const controller = new AbortController();
    const timer = setTimeout(() => {
      controller.abort(new Error(`Authority E2E request timeout after ${timeoutMs}ms`));
    }, timeoutMs);
    try {
      return await baseFetchImpl(url, {
        ...options,
        signal: controller.signal,
      });
    } finally {
      clearTimeout(timer);
    }
  };
}

export function createAuthorityE2eContext(options = {}) {
  const env =
    options.env && typeof options.env === "object" && !Array.isArray(options.env)
      ? options.env
      : process.env;
  const skipMessage = String(
    options.skipMessage ||
      "authority E2E skipped: set AUTHORITY_E2E_BASE_URL to run against a real Authority server",
  );
  const baseUrl = String(env.AUTHORITY_E2E_BASE_URL || "").trim();
  if (!baseUrl) {
    return {
      skip: true,
      skipMessage,
      env,
    };
  }
  const resolvedBaseUrl = resolveAuthorityE2eBaseUrl(baseUrl, env);
  const timeoutMs = parsePositiveInteger(env.AUTHORITY_E2E_TIMEOUT_MS, 15000, 1000, 300000);
  const jobWaitTimeoutMs = parsePositiveInteger(
    env.AUTHORITY_E2E_JOB_WAIT_TIMEOUT_MS,
    15000,
    1000,
    300000,
  );
  const fetchImpl = createAuthorityE2eFetchWithTimeout(
    timeoutMs,
    options.fetchImpl || (typeof fetch === "function" ? fetch.bind(globalThis) : null),
  );
  const headerProvider = createAuthorityE2eHeaderProvider(env);
  const runId = String(
    env.AUTHORITY_E2E_RUN_ID || `authority-e2e-${Date.now()}-${randomUUID().slice(0, 8)}`,
  ).replace(/[^A-Za-z0-9._:-]+/g, "-");
  const chatId = String(env.AUTHORITY_E2E_CHAT_ID || `st-bme-${runId}`);
  const namespace = String(env.AUTHORITY_E2E_NAMESPACE || `st-bme-e2e-${runId}`);
  const collectionId = String(env.AUTHORITY_E2E_COLLECTION_ID || `${namespace}::${chatId}`);
  const blobPath = String(env.AUTHORITY_E2E_BLOB_PATH || `st-bme/e2e/${runId}/contract.json`);
  const embeddingApiUrl = String(env.AUTHORITY_E2E_EMBEDDING_API_URL || "").trim();
  const embeddingApiKey = String(env.AUTHORITY_E2E_EMBEDDING_API_KEY || "").trim();
  const embeddingModel = String(env.AUTHORITY_E2E_EMBEDDING_MODEL || "").trim();
  return {
    skip: false,
    env,
    baseUrl: resolvedBaseUrl,
    timeoutMs,
    jobWaitTimeoutMs,
    fetchImpl,
    headerProvider,
    runId,
    chatId,
    namespace,
    collectionId,
    blobPath,
    embeddingApiUrl,
    embeddingApiKey,
    embeddingModel,
  };
}

export function createAuthorityE2eContractNode(id, title, nowMs) {
  return {
    id,
    type: "fact",
    fields: {
      title,
      summary: `${title} generated by Authority server-primary E2E contract smoke`,
    },
    seqRange: [1, 1],
    scope: {
      layer: "global",
      ownerType: "system",
      ownerId: "authority-e2e",
      bucket: "contract",
      regionKey: "authority-e2e-region",
    },
    storySegmentId: "authority-e2e-segment",
    importance: 0.8,
    archived: false,
    updatedAt: nowMs,
  };
}

export function createAuthorityE2eContractGraph(chatId, runId, options = {}) {
  const nowMs = Number(options.nowMs || Date.now());
  const revision = Number.isFinite(Number(options.revision))
    ? Math.max(1, Math.trunc(Number(options.revision)))
    : 1;
  const nodeA = createAuthorityE2eContractNode(`${runId}-node-a`, "Authority E2E Alpha", nowMs);
  const nodeB = createAuthorityE2eContractNode(`${runId}-node-b`, "Authority E2E Beta", nowMs);
  return {
    meta: {
      schemaVersion: 1,
      chatId,
      deviceId: "authority-e2e",
      revision,
      lastModified: nowMs,
      nodeCount: 2,
      edgeCount: 1,
      tombstoneCount: 0,
    },
    nodes: [nodeA, nodeB],
    edges: [
      {
        id: `${runId}-edge-a-b`,
        fromId: nodeA.id,
        toId: nodeB.id,
        relation: "related",
        type: "semantic",
        strength: 0.7,
        updatedAt: nowMs,
      },
    ],
    tombstones: [],
    state: {
      lastProcessedFloor: 1,
      extractionCount: 1,
    },
  };
}

export function buildAuthorityE2eVectorEntries(graph = null) {
  const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
  return nodes.map((node, index) => ({
    nodeId: node.id,
    index,
    hash: `${node.id}:hash`,
    text: `${node.fields?.title || node.id}. ${node.fields?.summary || ""}`,
    vector: [1, index + 1, String(node.fields?.title || node.id || "").length / 100],
  }));
}

export async function runAuthorityE2eStep(name, fn) {
  const startedAt = Date.now();
  try {
    const result = await fn();
    const durationMs = Date.now() - startedAt;
    console.log(`authority E2E ${name}: ok (${durationMs}ms)`);
    return { name, ok: true, durationMs, result };
  } catch (error) {
    const durationMs = Date.now() - startedAt;
    console.error(`authority E2E ${name}: failed (${durationMs}ms)`);
    console.error(error?.stack || error?.message || String(error));
    throw error;
  }
}
