import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
  DeepLinkRouter,
  DeepLinkScreens,
  createBroadcastClient,
  createBroadcastDeepLink,
  createUseBroadcast,
} from './index.js';

const fetchMock = vi.fn();

function jsonResponse(body: unknown, status = 200) {
  return {
    ok: status >= 200 && status < 300,
    status,
    json: () => Promise.resolve(body),
    text: () => Promise.resolve(JSON.stringify(body)),
  };
}

describe('createBroadcastClient', () => {
  beforeEach(() => {
    fetchMock.mockReset();
    vi.stubGlobal('fetch', fetchMock);
  });

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

  it('lists messages with default segment and auth headers', async () => {
    fetchMock.mockResolvedValueOnce(jsonResponse({ messages: [{ id: 'm1' }] }));

    const client = createBroadcastClient({
      baseUrl: 'http://localhost:4003/api',
      productId: 'testapp',
      getAuthToken: () => 'token-123',
      platform: 'web',
      appVersion: '1.2.3',
      osVersion: 'macOS 15',
    });

    const result = await client.listMessages();

    expect(result).toEqual({ messages: [{ id: 'm1' }] });
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/broadcasts',
      expect.objectContaining({
        headers: expect.objectContaining({
          Authorization: 'Bearer token-123',
          'x-product-id': 'testapp',
          'x-platform': 'web',
          'x-app-version': '1.2.3',
          'x-os-version': 'macOS 15',
          'x-user-segments': 'free',
        }),
      })
    );
  });

  it('supports async auth token resolution and optional headers', async () => {
    fetchMock.mockResolvedValueOnce(jsonResponse({ messages: [] }));

    const client = createBroadcastClient({
      baseUrl: 'http://localhost:4003/api',
      productId: 'testapp',
      getAuthToken: async () => 'async-token',
      platform: 'ios',
      appVersion: '2.0.0',
      osVersion: '18.0',
      countryCode: 'US',
      regionCode: 'CA',
      userSegments: ['pro', 'beta'],
    });

    await client.listMessages();

    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/broadcasts',
      expect.objectContaining({
        headers: expect.objectContaining({
          Authorization: 'Bearer async-token',
          'x-country-code': 'US',
          'x-region-code': 'CA',
          'x-user-segments': 'pro,beta',
        }),
      })
    );
  });

  it('throws a descriptive error when the API fails', async () => {
    fetchMock.mockResolvedValueOnce({
      ok: false,
      status: 500,
      text: () => Promise.resolve('boom'),
    });

    const client = createBroadcastClient({
      baseUrl: 'http://localhost:4003/api',
      productId: 'testapp',
      getAuthToken: () => 'token-123',
      platform: 'web',
      appVersion: '1.2.3',
      osVersion: 'macOS 15',
    });

    await expect(client.markRead('message-1')).rejects.toThrow('Broadcast API error: 500 boom');
  });

  it('polls messages and returns a cleanup function', async () => {
    vi.useFakeTimers();
    fetchMock.mockResolvedValue(jsonResponse({ messages: [] }));

    const client = createBroadcastClient({
      baseUrl: 'http://localhost:4003/api',
      productId: 'testapp',
      getAuthToken: () => 'token-123',
      platform: 'web',
      appVersion: '1.2.3',
      osVersion: 'macOS 15',
    });

    const stopPolling = client.pollMessages(1000);
    await vi.advanceTimersByTimeAsync(3000);

    expect(fetchMock).toHaveBeenCalledTimes(3);

    stopPolling();
    await vi.advanceTimersByTimeAsync(2000);

    expect(fetchMock).toHaveBeenCalledTimes(3);
  });

  it('returns the same client from createUseBroadcast', () => {
    const client = createBroadcastClient({
      baseUrl: 'http://localhost:4003/api',
      productId: 'testapp',
      getAuthToken: () => 'token-123',
      platform: 'web',
      appVersion: '1.2.3',
      osVersion: 'macOS 15',
    });

    const useBroadcast = createUseBroadcast(client);

    expect(useBroadcast()).toEqual({ client });
  });
});

describe('DeepLinkRouter', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('parses custom app deep links using the host as the screen', () => {
    const router = new DeepLinkRouter();

    expect(router.parseDeepLink('myapp://broadcast?broadcastId=b1&variant=test')).toEqual({
      screen: 'broadcast',
      params: {
        broadcastId: 'b1',
        variant: 'test',
      },
    });
  });

  it('parses nested deep links from web URLs', () => {
    const router = new DeepLinkRouter();

    expect(
      router.parseDeepLink('https://app.bytelyst.dev/open?dl=myapp%3A%2F%2Fsurvey%3Fid%3Ds1')
    ).toEqual({
      screen: 'survey',
      params: { id: 's1' },
    });
  });

  it('dispatches to registered handlers and falls back when needed', () => {
    const router = new DeepLinkRouter();
    const primaryHandler = vi.fn();
    const fallbackHandler = vi.fn();

    router.register(DeepLinkScreens.BROADCAST_DETAIL, primaryHandler);
    router.setFallback(fallbackHandler);

    expect(router.handle({ screen: DeepLinkScreens.BROADCAST_DETAIL, params: { id: 'b1' } })).toBe(
      true
    );
    expect(primaryHandler).toHaveBeenCalledWith({
      screen: DeepLinkScreens.BROADCAST_DETAIL,
      params: { id: 'b1' },
    });

    expect(router.handle({ screen: 'unknown' })).toBe(true);
    expect(fallbackHandler).toHaveBeenCalledWith({ screen: 'unknown' });
  });

  it('returns false and warns when processing an invalid URL without a fallback', () => {
    const router = new DeepLinkRouter();
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

    expect(router.process('not-a-url')).toBe(false);
    expect(warnSpy).toHaveBeenCalledWith('[DeepLink] Failed to parse: not-a-url');
  });
});

describe('createBroadcastDeepLink', () => {
  it('builds deep links with params and broadcastId', () => {
    expect(
      createBroadcastDeepLink(
        'https://app.bytelyst.dev',
        DeepLinkScreens.ANNOUNCEMENTS,
        { tab: 'latest' },
        'b42'
      )
    ).toBe('https://app.bytelyst.dev/announcements?tab=latest&broadcastId=b42');
  });
});
