import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

// Mock cookieConsent module — consent granted by default
const mockGetCategoryConsent = vi.fn().mockReturnValue(true);
vi.mock('@/lib/cookieConsent', () => ({
  getCategoryConsent: (...args: unknown[]) => mockGetCategoryConsent(...args),
}));

import {
  checkAnalyticsHealth,
  identifyUser,
  trackEcommerceEvent,
  trackEvent,
  trackFilterApplied,
  trackProductEvent,
  trackTypedEvent,
  USER_TRAIT_SCHEMA_VERSION,
} from './analytics';
import type { TypedAnalyticsEvent } from './analytics-types';

beforeEach(() => {
  mockGetCategoryConsent.mockReturnValue(true);
});

afterEach(() => {
  delete window.gtag;
  delete window.posthog;
  delete (window as unknown as Record<string, unknown>).__GA_ID;
});

describe('trackEvent', () => {
  it('calls window.gtag when available and consent granted', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('test_event', { step: 'welcome' });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'test_event', { step: 'welcome' });
  });

  it('does nothing when gtag is not available', () => {
    delete window.gtag;

    // Should not throw
    expect(() => trackEvent('test_event')).not.toThrow();
  });

  it('passes params correctly', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('onboarding_step_viewed', { step: 'connect_gsc', step_number: 2, is_first: true });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'onboarding_step_viewed', {
      step: 'connect_gsc',
      step_number: 2,
      is_first: true,
    });
  });

  it('does not call gtag when analytics consent is denied', () => {
    mockGetCategoryConsent.mockReturnValue(false);
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('test_event', { step: 'welcome' });

    expect(gtagSpy).not.toHaveBeenCalled();
  });

  it('forwards events to PostHog when available', () => {
    const captureSpy = vi.fn();
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };
    window.gtag = vi.fn();

    trackEvent('test_event', { step: 'welcome' });

    expect(captureSpy).toHaveBeenCalledWith('test_event', { step: 'welcome' });
  });

  it('does not forward to PostHog when consent is denied', () => {
    mockGetCategoryConsent.mockReturnValue(false);
    const captureSpy = vi.fn();
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };

    trackEvent('test_event');

    expect(captureSpy).not.toHaveBeenCalled();
  });
});

describe('LEGACY_EVENT_MAP remapping in trackEvent', () => {
  it('remaps onboarding_step_completed → onboarding_completed before sending to GA4', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('onboarding_step_completed', { step: '1' });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'onboarding_completed', { step: '1' });
  });

  it('remaps trial_nudge_shown → trial_banner_shown', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('trial_nudge_shown');

    expect(gtagSpy).toHaveBeenCalledWith('event', 'trial_banner_shown', undefined);
  });

  it('passes through canonical event names unchanged', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('onboarding_completed', { step: 'done' });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'onboarding_completed', { step: 'done' });
  });

  it('forwards the remapped name to PostHog, not the legacy name', () => {
    const captureSpy = vi.fn();
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };
    window.gtag = vi.fn();

    trackEvent('onboarding_wizard_completed');

    expect(captureSpy).toHaveBeenCalledWith('onboarding_completed', undefined);
  });
});

describe('trackProductEvent', () => {
  it('fires both GA4 and PostHog events without double-firing PostHog', () => {
    const gtagSpy = vi.fn();
    const captureSpy = vi.fn();
    window.gtag = gtagSpy;
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };
    // PostHog capture is via window.posthog (no separate __posthog handle)

    trackProductEvent('dashboard_viewed', { site_id: 1 });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'dashboard_viewed', { site_id: 1 });
    expect(captureSpy).toHaveBeenCalledTimes(1);
    expect(captureSpy).toHaveBeenCalledWith('dashboard_viewed', { site_id: 1 });

    delete window.gtag;
    delete window.posthog;
  });

  it('works when only GA4 is available', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackProductEvent('analysis_initiated', { site_id: 2 });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'analysis_initiated', { site_id: 2 });

    delete window.gtag;
  });
});

describe('identifyUser', () => {
  it('calls gtag set user_properties when gtag is available', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    identifyUser(42, { plan_tier: 'pro', site_count: 3, days_since_signup: 14 });

    expect(gtagSpy).toHaveBeenCalledWith('set', 'user_properties', {
      plan_tier: 'pro',
      site_count: 3,
      days_since_signup: 14,
    });
  });

  it('calls gtag config with user_id when __GA_ID is set', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;
    (window as unknown as Record<string, unknown>).__GA_ID = 'G-TEST123';

    identifyUser(42, { plan_tier: 'free', site_count: 1, days_since_signup: 0 });

    expect(gtagSpy).toHaveBeenCalledWith('config', 'G-TEST123', { user_id: '42' });
  });

  it('does nothing when gtag is not available', () => {
    delete window.gtag;

    expect(() =>
      identifyUser(1, { plan_tier: 'free', site_count: 0, days_since_signup: 0 }),
    ).not.toThrow();
  });

  it('does not call gtag when analytics consent is denied', () => {
    mockGetCategoryConsent.mockReturnValue(false);
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    identifyUser(42, { plan_tier: 'pro', site_count: 3, days_since_signup: 14 });

    expect(gtagSpy).not.toHaveBeenCalled();
  });

  it('calls posthog.identify when PostHog is available', () => {
    const identifySpy = vi.fn();
    window.posthog = { capture: vi.fn(), identify: identifySpy, getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };
    window.gtag = vi.fn();

    identifyUser(42, { plan_tier: 'pro', site_count: 3, days_since_signup: 14 });

    expect(identifySpy).toHaveBeenCalledWith('42', {
      plan_tier: 'pro',
      site_count: 3,
      days_since_signup: 14,
      trait_schema_version: USER_TRAIT_SCHEMA_VERSION,
    });
  });

  it('does not call posthog.identify when consent is denied', () => {
    mockGetCategoryConsent.mockReturnValue(false);
    const identifySpy = vi.fn();
    window.posthog = { capture: vi.fn(), identify: identifySpy, getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };

    identifyUser(42, { plan_tier: 'pro', site_count: 3, days_since_signup: 14 });

    expect(identifySpy).not.toHaveBeenCalled();
  });
});

describe('trackFilterApplied', () => {
  it('fires filter_applied event via trackProductEvent', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackFilterApplied('action_type', 'content_rewrite');

    expect(gtagSpy).toHaveBeenCalledWith('event', 'filter_applied', {
      filter_name: 'action_type',
      filter_value: 'content_rewrite',
    });
  });

  it('does nothing when gtag is unavailable', () => {
    delete window.gtag;
    expect(() => trackFilterApplied('status', 'applied')).not.toThrow();
  });
});

describe('trackEcommerceEvent', () => {
  it('fires ecommerce event to both GA4 and PostHog without double-firing PostHog', () => {
    const gtagSpy = vi.fn();
    const captureSpy = vi.fn();
    window.gtag = gtagSpy;
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };
    // PostHog capture is via window.posthog (no separate __posthog handle)

    trackEcommerceEvent(
      'purchase',
      { item_id: 'pro_plan', item_name: 'Pro Plan', price: 29 },
      29,
      'USD',
    );

    expect(gtagSpy).toHaveBeenCalledWith('event', 'purchase', {
      currency: 'USD',
      value: 29,
      items: [
        {
          item_id: 'pro_plan',
          item_name: 'Pro Plan',
          price: 29,
          quantity: 1,
        },
      ],
    });

    expect(captureSpy).toHaveBeenCalledTimes(1);
    expect(captureSpy).toHaveBeenCalledWith('purchase', {
      currency: 'USD',
      value: 29,
      item_id: 'pro_plan',
      item_name: 'Pro Plan',
      price: 29,
    });

    delete window.gtag;
    delete window.posthog;
  });
});

describe('trackTypedEvent', () => {
  it('routes the typed event name and properties to GA4', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    const payload: TypedAnalyticsEvent = {
      event: 'dashboard_viewed',
      properties: { site_id: '42' },
    };

    trackTypedEvent(payload);

    expect(gtagSpy).toHaveBeenCalledWith('event', 'dashboard_viewed', { site_id: '42' });
  });

  it('enforces required properties for multi-prop events (recommendation_applied)', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    const payload: TypedAnalyticsEvent = {
      event: 'recommendation_applied',
      properties: { site_id: '1', recommendation_id: '10', action_type: 'content_rewrite' },
    };

    trackTypedEvent(payload);

    expect(gtagSpy).toHaveBeenCalledWith('event', 'recommendation_applied', {
      site_id: '1',
      recommendation_id: '10',
      action_type: 'content_rewrite',
    });
  });

  it('forwards typed events to PostHog', () => {
    const captureSpy = vi.fn();
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };
    window.gtag = vi.fn();

    trackTypedEvent({ event: 'dashboard_viewed', properties: { site_id: '5' } });

    expect(captureSpy).toHaveBeenCalledWith('dashboard_viewed', { site_id: '5' });
  });

  it('does not call gtag when analytics consent is denied', () => {
    mockGetCategoryConsent.mockReturnValue(false);
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackTypedEvent({ event: 'dashboard_viewed', properties: { site_id: '1' } });

    expect(gtagSpy).not.toHaveBeenCalled();
  });

  it('handles events with numeric and boolean properties (editor_session_end)', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    const payload: TypedAnalyticsEvent = {
      event: 'editor_session_end',
      properties: {
        session_id: 'test-session-uuid',
        draft_id: 7,
        duration_ms: 120000,
        final_score: 82,
        published: false,
        serp_analysis_triggered: true,
      },
    };

    trackTypedEvent(payload);

    expect(gtagSpy).toHaveBeenCalledWith('event', 'editor_session_end', {
      session_id: 'test-session-uuid',
      draft_id: 7,
      duration_ms: 120000,
      final_score: 82,
      published: false,
      serp_analysis_triggered: true,
    });
  });
});

describe('GA4 param name sanitization (sanitizeForGa4 — tested indirectly via trackEvent)', () => {
  it('passes through valid param names unchanged', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('test_event', { valid_name: 1, _underscore: 2 });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'test_event', { valid_name: 1, _underscore: 2 });
  });

  it('replaces special characters in param names with underscores', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('test_event', { 'invalid-name': 1 });

    // Hyphens replaced with underscores
    expect(gtagSpy).toHaveBeenCalledWith('event', 'test_event', { invalid_name: 1 });
  });

  it('replaces dots in param names with underscores', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('test_event', { 'foo.bar': 42 });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'test_event', { foo_bar: 42 });
  });

  it('prepends underscore to param names starting with a digit', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    trackEvent('test_event', { '1invalid': 99 });

    expect(gtagSpy).toHaveBeenCalledWith('event', 'test_event', { _1invalid: 99 });
  });

  it('truncates param names longer than 40 characters', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    const longName = 'a'.repeat(50);
    trackEvent('test_event', { [longName]: 7 });

    const expectedKey = 'a'.repeat(40);
    expect(gtagSpy).toHaveBeenCalledWith('event', 'test_event', { [expectedKey]: 7 });
  });

  it('drops param names that sanitize to a bare underscore', () => {
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;

    // A single special char sanitizes to '_' which is dropped (sanitizeGa4ParamName returns null)
    trackEvent('test_event', { '-': 5, valid_key: 1 });

    const callArgs = gtagSpy.mock.calls[0][2] as Record<string, number>;
    expect(callArgs).not.toHaveProperty('-');
    expect(callArgs).not.toHaveProperty('_');
    expect(callArgs.valid_key).toBe(1);
  });
});

describe('checkAnalyticsHealth', () => {
  it('does not throw when called', () => {
    vi.useFakeTimers();
    expect(() => checkAnalyticsHealth(100)).not.toThrow();
    vi.useRealTimers();
  });

  it('sends PostHog event when gtag unavailable after timeout', () => {
    vi.useFakeTimers();
    delete window.gtag;
    const captureSpy = vi.fn();
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };

    checkAnalyticsHealth(100);
    vi.advanceTimersByTime(150);

    expect(captureSpy).toHaveBeenCalledWith('analytics_health_check_failed', {
      reason: 'gtag_unavailable',
      timeout_ms: 100,
    });

    vi.useRealTimers();
    delete window.posthog;
  });

  it('does not fire when gtag is available', () => {
    vi.useFakeTimers();
    const gtagSpy = vi.fn();
    window.gtag = gtagSpy;
    const captureSpy = vi.fn();
    window.posthog = { capture: captureSpy, identify: vi.fn(), getFeatureFlag: vi.fn(), onFeatureFlags: vi.fn() };

    checkAnalyticsHealth(100);
    vi.advanceTimersByTime(150);

    expect(captureSpy).not.toHaveBeenCalled();

    vi.useRealTimers();
    delete window.gtag;
    delete window.posthog;
  });
});
