/**
 * A/B Testing (Experiments) repository — Cosmos DB CRUD.
 */

import { getRegisteredContainer } from '@bytelyst/cosmos';
import crypto from 'node:crypto';
import type {
  ExperimentDoc,
  ExperimentAssignmentDoc,
  CreateExperimentInput,
  UpdateExperimentInput,
} from './types.js';

function getContainer() {
  return getRegisteredContainer('experiments');
}

function getAssignmentContainer() {
  return getRegisteredContainer('experiment_assignments');
}

// ── FNV-1a hash for deterministic assignment ──────────────────

function fnv1a(str: string): number {
  let hash = 0x811c9dc5;
  for (let i = 0; i < str.length; i++) {
    hash ^= str.charCodeAt(i);
    hash = (hash * 0x01000193) >>> 0;
  }
  return hash;
}

export function assignVariant(
  experimentId: string,
  userId: string,
  variants: ExperimentDoc['variants']
): string {
  const hash = fnv1a(`${experimentId}:${userId}`);
  const bucket = hash % 100;
  let cumulative = 0;
  for (const v of variants) {
    cumulative += v.weight;
    if (bucket < cumulative) return v.key;
  }
  return variants[variants.length - 1].key;
}

// ── Experiment CRUD ──────────────────────────────────────────

export async function listExperiments(productId: string): Promise<ExperimentDoc[]> {
  const container = getContainer();
  const { resources } = await container.items
    .query<ExperimentDoc>({
      query: 'SELECT * FROM c WHERE c.productId = @pid ORDER BY c.createdAt DESC',
      parameters: [{ name: '@pid', value: productId }],
    })
    .fetchAll();
  return resources;
}

export async function getExperiment(id: string): Promise<ExperimentDoc | null> {
  try {
    const { resource } = await getContainer().item(id, id).read<ExperimentDoc>();
    return resource ?? null;
  } catch {
    return null;
  }
}

export async function createExperiment(
  productId: string,
  input: CreateExperimentInput
): Promise<ExperimentDoc> {
  const now = new Date().toISOString();
  const doc: ExperimentDoc = {
    id: `exp_${crypto.randomUUID()}`,
    productId,
    key: input.key,
    name: input.name,
    description: input.description ?? '',
    status: 'draft',
    variants: input.variants.map(v => ({
      key: v.key,
      weight: v.weight,
      description: v.description ?? '',
    })),
    targetSegments: input.targetSegments ?? [],
    trafficPercent: input.trafficPercent ?? 100,
    startedAt: null,
    endedAt: null,
    createdAt: now,
    updatedAt: now,
  };
  await getContainer().items.create(doc);
  return doc;
}

export async function updateExperiment(
  id: string,
  updates: UpdateExperimentInput
): Promise<ExperimentDoc | null> {
  const existing = await getExperiment(id);
  if (!existing) return null;

  const now = new Date().toISOString();
  const updated: ExperimentDoc = {
    ...existing,
    ...updates,
    updatedAt: now,
  };

  if (updates.status === 'running' && !existing.startedAt) {
    updated.startedAt = now;
  }
  if (updates.status === 'completed' && !existing.endedAt) {
    updated.endedAt = now;
  }

  await getContainer().item(id, id).replace(updated);
  return updated;
}

export async function deleteExperiment(id: string): Promise<boolean> {
  try {
    await getContainer().item(id, id).delete();
    return true;
  } catch {
    return false;
  }
}

// ── Assignments ──────────────────────────────────────────────

export async function getAssignment(
  experimentId: string,
  userId: string
): Promise<ExperimentAssignmentDoc | null> {
  const id = `${experimentId}:${userId}`;
  try {
    const { resource } = await getAssignmentContainer()
      .item(id, experimentId)
      .read<ExperimentAssignmentDoc>();
    return resource ?? null;
  } catch {
    return null;
  }
}

export async function getOrCreateAssignment(
  experiment: ExperimentDoc,
  userId: string
): Promise<ExperimentAssignmentDoc> {
  const existing = await getAssignment(experiment.id, userId);
  if (existing) return existing;

  const variantKey = assignVariant(experiment.id, userId, experiment.variants);
  const doc: ExperimentAssignmentDoc = {
    id: `${experiment.id}:${userId}`,
    productId: experiment.productId,
    experimentId: experiment.id,
    userId,
    variantKey,
    assignedAt: new Date().toISOString(),
  };
  await getAssignmentContainer().items.create(doc);
  return doc;
}

export async function listAssignmentsForExperiment(
  experimentId: string
): Promise<ExperimentAssignmentDoc[]> {
  const { resources } = await getAssignmentContainer()
    .items.query<ExperimentAssignmentDoc>({
      query: 'SELECT * FROM c WHERE c.experimentId = @eid',
      parameters: [{ name: '@eid', value: experimentId }],
    })
    .fetchAll();
  return resources;
}
