import { describe, it, expect, vi, afterEach } from 'vitest';
import { createPlatformClient, ApiError } from './index.js';

describe('createPlatformClient', () => {
  const baseConfig = {
    baseUrl: 'http://localhost:4003/api',
    productId: 'testapp',
    getAccessToken: () => 'test-token',
  };

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

  it('should make GET requests with auth header', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve({ items: [] }),
    });
    vi.stubGlobal('fetch', fetchMock);

    const api = createPlatformClient(baseConfig);
    const result = await api.get('/items');

    expect(result).toEqual({ items: [] });
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/items',
      expect.objectContaining({
        method: 'GET',
        headers: expect.objectContaining({
          Authorization: 'Bearer test-token',
          'x-product-id': 'testapp',
        }),
      })
    );
  });

  it('should make POST requests with body', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve({ id: '1' }),
    });
    vi.stubGlobal('fetch', fetchMock);

    const api = createPlatformClient(baseConfig);
    const result = await api.post('/items', { name: 'test' });

    expect(result).toEqual({ id: '1' });
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/items',
      expect.objectContaining({
        method: 'POST',
        body: JSON.stringify({ name: 'test' }),
      })
    );
  });

  it('should handle 204 No Content', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        status: 204,
        json: () => Promise.reject(new Error('no body')),
      })
    );

    const api = createPlatformClient(baseConfig);
    const result = await api.del('/items/1');

    expect(result).toBeUndefined();
  });

  it('should throw ApiError on non-OK response', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: false,
        status: 400,
        json: () => Promise.resolve({ message: 'Bad request' }),
      })
    );

    const api = createPlatformClient(baseConfig);

    await expect(api.get('/items')).rejects.toThrow(ApiError);
    try {
      await api.get('/items');
    } catch (e) {
      expect(e).toBeInstanceOf(ApiError);
      expect((e as ApiError).status).toBe(400);
    }
  });

  it('should attempt token refresh on 401', async () => {
    let callCount = 0;
    vi.stubGlobal(
      'fetch',
      vi.fn().mockImplementation(() => {
        callCount++;
        if (callCount === 1) {
          return Promise.resolve({
            ok: false,
            status: 401,
            json: () => Promise.resolve({ message: 'Unauthorized' }),
          });
        }
        return Promise.resolve({
          ok: true,
          status: 200,
          json: () => Promise.resolve({ refreshed: true }),
        });
      })
    );

    const refreshFn = vi.fn().mockResolvedValue(true);
    const api = createPlatformClient({
      ...baseConfig,
      refreshAccessToken: refreshFn,
    });

    const result = await api.get('/items');
    expect(refreshFn).toHaveBeenCalledOnce();
    expect(result).toEqual({ refreshed: true });
  });

  it('should not include auth header when token is null', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve({}),
    });
    vi.stubGlobal('fetch', fetchMock);

    const api = createPlatformClient({
      ...baseConfig,
      getAccessToken: () => null,
    });
    await api.get('/public/items');

    const headers = fetchMock.mock.calls[0][1].headers as Record<string, string>;
    expect(headers['Authorization']).toBeUndefined();
  });

  it('should include x-request-id header', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      json: () => Promise.resolve({}),
    });
    vi.stubGlobal('fetch', fetchMock);

    const api = createPlatformClient(baseConfig);
    await api.get('/items');

    const headers = fetchMock.mock.calls[0][1].headers as Record<string, string>;
    expect(headers['x-request-id']).toBeDefined();
    expect(headers['x-request-id'].length).toBeGreaterThan(0);
  });
});
