import { act, renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import * as analyticsModule from '@/lib/analytics';
import { EXPERIMENT_EXPOSURE } from '@/lib/event-catalog';

import { useExperiment } from './useExperiment';

type MockPosthog = {
  capture: ReturnType<typeof vi.fn>;
  identify: ReturnType<typeof vi.fn>;
  getFeatureFlag: ReturnType<typeof vi.fn>;
  onFeatureFlags: ReturnType<typeof vi.fn>;
};

describe('useExperiment', () => {
  let mockPosthog: MockPosthog;
  let trackEventSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    mockPosthog = {
      capture: vi.fn(),
      identify: vi.fn(),
      getFeatureFlag: vi.fn(),
      onFeatureFlags: vi.fn(),
    };
    (window as unknown as { posthog: MockPosthog }).posthog = mockPosthog;
    trackEventSpy = vi.spyOn(analyticsModule, 'trackEvent').mockImplementation(() => {});
  });

  afterEach(() => {
    vi.restoreAllMocks();
    delete (window as unknown as { posthog?: unknown }).posthog;
  });

  it('returns the variant string when PostHog flag resolves immediately', () => {
    mockPosthog.getFeatureFlag.mockReturnValue('variant_a');

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBe('variant_a');
  });

  it('returns null when PostHog is not available', () => {
    delete (window as unknown as { posthog?: unknown }).posthog;

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBeNull();
  });

  it('returns null when flag is undefined (not enrolled in experiment)', () => {
    mockPosthog.getFeatureFlag.mockReturnValue(undefined);

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBeNull();
  });

  it('returns null when flag is null', () => {
    mockPosthog.getFeatureFlag.mockReturnValue(null);

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBeNull();
  });

  it('tracks an EXPERIMENT_EXPOSURE event when variant first resolves', () => {
    mockPosthog.getFeatureFlag.mockReturnValue('control');

    renderHook(() => useExperiment('hero_headline'));

    expect(trackEventSpy).toHaveBeenCalledOnce();
    expect(trackEventSpy).toHaveBeenCalledWith(EXPERIMENT_EXPOSURE, {
      experiment_key: 'hero_headline',
      variant: 'control',
    });
  });

  it('resolves variant via onFeatureFlags callback when flags are not yet loaded', () => {
    mockPosthog.getFeatureFlag
      .mockReturnValueOnce(undefined) // first call: not yet loaded
      .mockReturnValue('variant_b'); // subsequent calls: loaded
    let flagsCallback: (() => void) | undefined;
    mockPosthog.onFeatureFlags.mockImplementation((cb: () => void) => {
      flagsCallback = cb;
    });

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBeNull();
    expect(trackEventSpy).not.toHaveBeenCalled();

    act(() => {
      flagsCallback?.();
    });

    expect(result.current).toBe('variant_b');
    expect(trackEventSpy).toHaveBeenCalledOnce();
    expect(trackEventSpy).toHaveBeenCalledWith(EXPERIMENT_EXPOSURE, {
      experiment_key: 'hero_headline',
      variant: 'variant_b',
    });
  });

  it('does not track exposure more than once per component mount', () => {
    mockPosthog.getFeatureFlag.mockReturnValue('control');

    const { rerender } = renderHook(() => useExperiment('hero_headline'));
    rerender();
    rerender();

    expect(trackEventSpy).toHaveBeenCalledOnce();
  });

  it('does not track exposure when not enrolled (flag returns undefined on deferred load)', () => {
    mockPosthog.getFeatureFlag.mockReturnValue(undefined);
    let flagsCallback: (() => void) | undefined;
    mockPosthog.onFeatureFlags.mockImplementation((cb: () => void) => {
      flagsCallback = cb;
    });

    const { result } = renderHook(() => useExperiment('hero_headline'));

    act(() => {
      flagsCallback?.();
    });

    expect(result.current).toBeNull();
    expect(trackEventSpy).not.toHaveBeenCalled();
  });

  it('maps boolean true flag to "control" variant', () => {
    mockPosthog.getFeatureFlag.mockReturnValue(true);

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBe('control');
    expect(trackEventSpy).toHaveBeenCalledWith(EXPERIMENT_EXPOSURE, {
      experiment_key: 'hero_headline',
      variant: 'control',
    });
  });

  it('returns null for boolean false flag (user not in experiment)', () => {
    mockPosthog.getFeatureFlag.mockReturnValue(false);

    const { result } = renderHook(() => useExperiment('hero_headline'));

    expect(result.current).toBeNull();
    expect(trackEventSpy).not.toHaveBeenCalled();
  });

  it('uses the provided experimentKey to call getFeatureFlag', () => {
    mockPosthog.getFeatureFlag.mockReturnValue('variant_a');

    renderHook(() => useExperiment('primary_cta_copy'));

    expect(mockPosthog.getFeatureFlag).toHaveBeenCalledWith('primary_cta_copy');
    expect(trackEventSpy).toHaveBeenCalledWith(EXPERIMENT_EXPOSURE, {
      experiment_key: 'primary_cta_copy',
      variant: 'variant_a',
    });
  });
});
