/**
 * A/B Testing (Experiments) module — unit tests.
 */

import { describe, it, expect } from 'vitest';
import { CreateExperimentSchema, UpdateExperimentSchema } from './types.js';
import { assignVariant } from './repository.js';

// ── Schema Validation ────────────────────────────────────────

describe('CreateExperimentSchema', () => {
  const validInput = {
    key: 'onboarding_v2',
    name: 'Onboarding Flow V2',
    variants: [
      { key: 'control', weight: 50 },
      { key: 'variant_a', weight: 50 },
    ],
  };

  it('accepts valid input with defaults', () => {
    const result = CreateExperimentSchema.parse(validInput);
    expect(result.key).toBe('onboarding_v2');
    expect(result.description).toBe('');
    expect(result.targetSegments).toEqual([]);
    expect(result.trafficPercent).toBe(100);
  });

  it('rejects key with invalid chars', () => {
    expect(() => CreateExperimentSchema.parse({ ...validInput, key: 'BAD-KEY' })).toThrow();
  });

  it('rejects fewer than 2 variants', () => {
    expect(() =>
      CreateExperimentSchema.parse({
        ...validInput,
        variants: [{ key: 'only', weight: 100 }],
      })
    ).toThrow();
  });

  it('rejects variant weights not summing to 100', () => {
    expect(() =>
      CreateExperimentSchema.parse({
        ...validInput,
        variants: [
          { key: 'a', weight: 40 },
          { key: 'b', weight: 40 },
        ],
      })
    ).toThrow(/sum to 100/);
  });

  it('accepts 3 variants summing to 100', () => {
    const result = CreateExperimentSchema.parse({
      ...validInput,
      variants: [
        { key: 'control', weight: 34 },
        { key: 'a', weight: 33 },
        { key: 'b', weight: 33 },
      ],
    });
    expect(result.variants).toHaveLength(3);
  });

  it('accepts trafficPercent between 1 and 100', () => {
    const result = CreateExperimentSchema.parse({ ...validInput, trafficPercent: 50 });
    expect(result.trafficPercent).toBe(50);
  });

  it('rejects trafficPercent of 0', () => {
    expect(() => CreateExperimentSchema.parse({ ...validInput, trafficPercent: 0 })).toThrow();
  });
});

describe('UpdateExperimentSchema', () => {
  it('accepts partial updates', () => {
    const result = UpdateExperimentSchema.parse({ name: 'New Name' });
    expect(result.name).toBe('New Name');
    expect(result.status).toBeUndefined();
  });

  it('accepts status transition', () => {
    const result = UpdateExperimentSchema.parse({ status: 'running' });
    expect(result.status).toBe('running');
  });

  it('rejects invalid status', () => {
    expect(() => UpdateExperimentSchema.parse({ status: 'invalid' })).toThrow();
  });
});

// ── Deterministic Assignment ─────────────────────────────────

describe('assignVariant', () => {
  const variants = [
    { key: 'control', weight: 50, description: '' },
    { key: 'variant_a', weight: 50, description: '' },
  ];

  it('assigns deterministically — same input = same output', () => {
    const v1 = assignVariant('exp-1', 'user-a', variants);
    const v2 = assignVariant('exp-1', 'user-a', variants);
    expect(v1).toBe(v2);
  });

  it('distributes across variants for different users', () => {
    const assignments = new Set<string>();
    for (let i = 0; i < 100; i++) {
      assignments.add(assignVariant('exp-1', `user-${i}`, variants));
    }
    // With 50/50 split and 100 users, both variants should appear
    expect(assignments.size).toBe(2);
  });

  it('respects uneven weights', () => {
    const unevenVariants = [
      { key: 'control', weight: 90, description: '' },
      { key: 'variant_a', weight: 10, description: '' },
    ];
    let controlCount = 0;
    for (let i = 0; i < 1000; i++) {
      if (assignVariant('exp-2', `u-${i}`, unevenVariants) === 'control') controlCount++;
    }
    // Should be roughly 90% control (allow wide margin for hash distribution)
    expect(controlCount).toBeGreaterThan(700);
    expect(controlCount).toBeLessThan(990);
  });

  it('different experiments give different assignments for same user', () => {
    // Not guaranteed but statistically likely with enough samples
    let sameCount = 0;
    for (let i = 0; i < 50; i++) {
      const v1 = assignVariant(`exp-a-${i}`, 'user-x', variants);
      const v2 = assignVariant(`exp-b-${i}`, 'user-x', variants);
      if (v1 === v2) sameCount++;
    }
    // Should not be all the same (statistically near-impossible)
    expect(sameCount).toBeLessThan(50);
  });
});
