export type ExperimentVariant = "A" | "B";
export type AssignmentMethod = "deterministic_50_50" | "query_override" | "persisted";

export type ExperimentAssignment = {
  experiment_id: string;
  route: string;
  variant: ExperimentVariant;
  assignment_method: AssignmentMethod;
};

export type ExperimentVariantDefinition = {
  id: string;
  route: string;
  variant: ExperimentVariant;
};

type StoredAssignment = {
  variant: ExperimentVariant;
  origin_method: Exclude<AssignmentMethod, "persisted">;
  assigned_at: string;
};

type AssignmentStore = {
  version: string;
  assignments: Record<string, StoredAssignment>;
};

export type FirstPartyStorage = Pick<Storage, "getItem" | "setItem">;

export const EXPERIMENT_STORAGE_VERSION = "2026-08-31.v1";
export const VISITOR_ID_STORAGE_KEY = "comando_visitor_id_v1";
export const ASSIGNMENTS_STORAGE_KEY = "comando_ab_assignments_v1";

export function normalizeVariant(value?: string | null): ExperimentVariant | undefined {
  const normalized = value?.trim().toUpperCase();
  return normalized === "A" || normalized === "B" ? normalized : undefined;
}

export function experimentIdFor(variants: ExperimentVariantDefinition[]): string | undefined {
  if (variants.length !== 2) return undefined;
  const ids = variants.map((variant) => variant.id.replace(/-[AB]$/, ""));
  return ids[0] && ids[0] === ids[1] ? ids[0] : undefined;
}

/** Stable FNV-1a hash. It is an allocator, not a security or identity primitive. */
export function stableHash(value: string): number {
  let hash = 0x811c9dc5;
  for (let index = 0; index < value.length; index += 1) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193);
  }
  return hash >>> 0;
}

export function deterministicVariant(visitorId: string, experimentId: string): ExperimentVariant {
  return stableHash(`${EXPERIMENT_STORAGE_VERSION}:${experimentId}:${visitorId}`) % 2 === 0 ? "A" : "B";
}

function emptyStore(): AssignmentStore {
  return { version: EXPERIMENT_STORAGE_VERSION, assignments: {} };
}

function readStore(storage: FirstPartyStorage): AssignmentStore {
  try {
    const parsed = JSON.parse(storage.getItem(ASSIGNMENTS_STORAGE_KEY) || "null") as AssignmentStore | null;
    if (parsed?.version !== EXPERIMENT_STORAGE_VERSION || !parsed.assignments || typeof parsed.assignments !== "object") return emptyStore();
    return parsed;
  } catch {
    return emptyStore();
  }
}

function writeStore(storage: FirstPartyStorage, store: AssignmentStore) {
  storage.setItem(ASSIGNMENTS_STORAGE_KEY, JSON.stringify(store));
}

function validVisitorId(value: string | null) {
  return Boolean(value && /^[a-f0-9-]{20,80}$/i.test(value));
}

export function ensureAnonymousVisitorId(storage: FirstPartyStorage, createId: () => string): string {
  const existing = storage.getItem(VISITOR_ID_STORAGE_KEY);
  if (validVisitorId(existing)) return existing!;
  const created = createId();
  if (!validVisitorId(created)) throw new Error("Anonymous visitor ID factory returned an invalid identifier.");
  storage.setItem(VISITOR_ID_STORAGE_KEY, created);
  return created;
}

export function initialOverrideAssignment(route: string, variants: ExperimentVariantDefinition[], requested?: string | null): ExperimentAssignment | undefined {
  const experimentId = experimentIdFor(variants);
  const override = normalizeVariant(requested);
  if (!experimentId || !override) return undefined;
  return { experiment_id: experimentId, route, variant: override, assignment_method: "query_override" };
}

export function resolveAndPersistAssignment({
  route,
  variants,
  requestedVariant,
  storage,
  createVisitorId,
  now = () => new Date().toISOString(),
}: {
  route: string;
  variants: ExperimentVariantDefinition[];
  requestedVariant?: string | null;
  storage: FirstPartyStorage;
  createVisitorId: () => string;
  now?: () => string;
}): ExperimentAssignment | undefined {
  const experimentId = experimentIdFor(variants);
  if (!experimentId) return undefined;

  const visitorId = ensureAnonymousVisitorId(storage, createVisitorId);
  const store = readStore(storage);
  const override = normalizeVariant(requestedVariant);
  if (override) {
    store.assignments[experimentId] = { variant: override, origin_method: "query_override", assigned_at: now() };
    writeStore(storage, store);
    return { experiment_id: experimentId, route, variant: override, assignment_method: "query_override" };
  }

  const persisted = store.assignments[experimentId];
  if (persisted && normalizeVariant(persisted.variant)) {
    return { experiment_id: experimentId, route, variant: persisted.variant, assignment_method: "persisted" };
  }

  const variant = deterministicVariant(visitorId, experimentId);
  store.assignments[experimentId] = { variant, origin_method: "deterministic_50_50", assigned_at: now() };
  writeStore(storage, store);
  return { experiment_id: experimentId, route, variant, assignment_method: "deterministic_50_50" };
}

export function leadExperimentAttribution(assignment?: ExperimentAssignment) {
  return {
    experimentId: assignment?.experiment_id || "",
    experimentVariant: assignment?.variant || "",
    assignmentMethod: assignment?.assignment_method || "",
  };
}

export function dataLayerExperimentAttribution(assignment?: ExperimentAssignment) {
  return {
    experiment_id: assignment?.experiment_id || "none",
    variant: assignment?.variant || "none",
    assignment_method: assignment?.assignment_method || "not_applicable",
  };
}
