import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createTelemetryClient } from '../client.js';
import type { TelemetryStorage } from '../types.js';

function createMockStorage(): TelemetryStorage & { store: Map<string, string> } {
  const store = new Map<string, string>();
  return {
    store,
    getItem: (key: string) => store.get(key) ?? null,
    setItem: (key: string, value: string) => store.set(key, value),
  };
}

describe('@bytelyst/telemetry-client', () => {
  let storage: ReturnType<typeof createMockStorage>;

  beforeEach(() => {
    storage = createMockStorage();
    vi.restoreAllMocks();
    vi.useFakeTimers();
    globalThis.fetch = vi.fn().mockResolvedValue({ ok: true });
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('createTelemetryClient', () => {
    it('creates a client with all expected methods', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      expect(client.init).toBeTypeOf('function');
      expect(client.trackEvent).toBeTypeOf('function');
      expect(client.flush).toBeTypeOf('function');
      expect(client.shutdown).toBeTypeOf('function');
      expect(client.getInstallId).toBeTypeOf('function');
      expect(client.getSessionId).toBeTypeOf('function');
    });
  });

  describe('install ID', () => {
    it('generates and persists install ID', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      const id = client.getInstallId();
      expect(id).toBeTruthy();
      expect(storage.store.get('testapp_telemetry_install_id')).toBe(id);
    });

    it('reuses persisted install ID', () => {
      storage.store.set('testapp_telemetry_install_id', 'existing-id');

      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      expect(client.getInstallId()).toBe('existing-id');
    });
  });

  describe('init', () => {
    it('generates a session ID on init', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      expect(client.getSessionId()).toBe('');
      client.init();
      expect(client.getSessionId()).toBeTruthy();
    });

    it('tracks session_started event on init', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      client.init();
      // Flush to see the queued event
      client.flush();

      expect(globalThis.fetch).toHaveBeenCalled();
      const body = JSON.parse((globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].body);
      expect(body.productId).toBe('testapp');
      expect(body.events).toHaveLength(1);
      expect(body.events[0].eventName).toBe('session_started');
      expect(body.events[0].platform).toBe('web');
      expect(body.events[0].channel).toBe('pwa');
    });
  });

  describe('trackEvent', () => {
    it('queues events and flushes via fetch', () => {
      const client = createTelemetryClient({
        productId: 'chronomind',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        transport: 'fetch',
        storage,
      });

      client.trackEvent('info', 'timer', 'timer_created', {
        feature: 'countdown',
        tags: { type: 'alarm' },
        metrics: { duration: 300 },
      });

      client.flush();

      expect(globalThis.fetch).toHaveBeenCalledOnce();
      const [url, opts] = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0];
      expect(url).toBe('http://localhost:4003/api/telemetry/events');
      expect(opts.method).toBe('POST');
      expect(opts.headers['x-product-id']).toBe('chronomind');

      const body = JSON.parse(opts.body);
      expect(body.events).toHaveLength(1);
      const ev = body.events[0];
      expect(ev.eventType).toBe('info');
      expect(ev.module).toBe('timer');
      expect(ev.eventName).toBe('timer_created');
      expect(ev.feature).toBe('countdown');
      expect(ev.tags.type).toBe('alarm');
      expect(ev.metrics.duration).toBe(300);
      expect(ev.occurredAt).toBeTruthy();
    });

    it('auto-flushes when queue reaches maxQueue', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'mobile',
        channel: 'react_native',
        transport: 'fetch',
        maxQueue: 3,
        storage,
      });

      // Don't init to avoid session_started
      client.trackEvent('info', 'a', 'one');
      client.trackEvent('info', 'b', 'two');
      expect(globalThis.fetch).not.toHaveBeenCalled();

      client.trackEvent('info', 'c', 'three');
      expect(globalThis.fetch).toHaveBeenCalledOnce();
    });
  });

  describe('flush', () => {
    it('does nothing when queue is empty', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      client.flush();
      expect(globalThis.fetch).not.toHaveBeenCalled();
    });
  });

  describe('periodic flush', () => {
    it('flushes on interval', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        flushIntervalMs: 10_000,
        storage,
      });

      client.init();
      (globalThis.fetch as ReturnType<typeof vi.fn>).mockClear();

      client.trackEvent('info', 'test', 'event1');

      expect(globalThis.fetch).not.toHaveBeenCalled();

      vi.advanceTimersByTime(10_000);

      expect(globalThis.fetch).toHaveBeenCalledOnce();
    });
  });

  describe('shutdown', () => {
    it('flushes remaining events and stops timer', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      client.init();
      (globalThis.fetch as ReturnType<typeof vi.fn>).mockClear();

      client.trackEvent('info', 'test', 'event1');
      client.shutdown();

      expect(globalThis.fetch).toHaveBeenCalledOnce();

      // After shutdown, periodic flush should not fire
      (globalThis.fetch as ReturnType<typeof vi.fn>).mockClear();
      client.trackEvent('info', 'test', 'event2');
      vi.advanceTimersByTime(60_000);
      expect(globalThis.fetch).not.toHaveBeenCalled();
    });
  });

  describe('userId passthrough', () => {
    it('includes userId in event when provided', () => {
      const client = createTelemetryClient({
        productId: 'testapp',
        baseUrl: 'http://localhost:4003/api',
        platform: 'web',
        channel: 'pwa',
        storage,
      });

      client.trackEvent('info', 'auth', 'login', { userId: 'user-123' });
      client.flush();

      const body = JSON.parse((globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].body);
      expect(body.events[0].userId).toBe('user-123');
    });
  });
});
