import { describe, expect, it, vi, beforeEach } from 'vitest';
import { createApiClient } from '../index.js';

// Mock globalThis.fetch
const mockFetch = vi.fn();
globalThis.fetch = mockFetch;

function jsonResponse(data: unknown, status = 200) {
  return {
    ok: status >= 200 && status < 300,
    status,
    statusText: status === 200 ? 'OK' : 'Error',
    json: () => Promise.resolve(data),
  };
}

describe('createApiClient', () => {
  beforeEach(() => {
    mockFetch.mockReset();
  });

  it('returns an object with fetch and safeFetch', () => {
    const api = createApiClient({ baseUrl: 'http://localhost:4003' });
    expect(typeof api.fetch).toBe('function');
    expect(typeof api.safeFetch).toBe('function');
  });

  describe('fetch', () => {
    it('calls correct URL with base + path', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ users: [] }));
      const api = createApiClient({ baseUrl: 'http://localhost:4003/api' });

      await api.fetch('/users');

      expect(mockFetch).toHaveBeenCalledWith(
        'http://localhost:4003/api/users',
        expect.objectContaining({
          headers: expect.objectContaining({ 'Content-Type': 'application/json' }),
        })
      );
    });

    it('returns parsed JSON on success', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ id: '1', name: 'Test' }));
      const api = createApiClient({ baseUrl: '/api' });

      const result = await api.fetch<{ id: string; name: string }>('/users/1');
      expect(result).toEqual({ id: '1', name: 'Test' });
    });

    it('throws on HTTP error', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ error: 'Not found' }, 404));
      const api = createApiClient({ baseUrl: '/api' });

      await expect(api.fetch('/users/999')).rejects.toThrow('Not found');
    });

    it('injects auth token from getToken', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ ok: true }));
      const api = createApiClient({
        baseUrl: '/api',
        getToken: () => 'my-jwt-token',
      });

      await api.fetch('/protected');

      expect(mockFetch).toHaveBeenCalledWith(
        '/api/protected',
        expect.objectContaining({
          headers: expect.objectContaining({
            Authorization: 'Bearer my-jwt-token',
          }),
        })
      );
    });

    it('skips auth header when getToken returns null', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ ok: true }));
      const api = createApiClient({
        baseUrl: '/api',
        getToken: () => null,
      });

      await api.fetch('/public');

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

    it('merges defaultHeaders', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ ok: true }));
      const api = createApiClient({
        baseUrl: '/api',
        defaultHeaders: { 'X-Custom': 'value' },
      });

      await api.fetch('/test');

      const headers = mockFetch.mock.calls[0][1].headers as Record<string, string>;
      expect(headers['X-Custom']).toBe('value');
      expect(headers['Content-Type']).toBe('application/json');
    });
  });

  describe('safeFetch', () => {
    it('returns { data, error: null } on success', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ id: '1' }));
      const api = createApiClient({ baseUrl: '/api' });

      const result = await api.safeFetch<{ id: string }>('/items/1');
      expect(result.data).toEqual({ id: '1' });
      expect(result.error).toBeNull();
    });

    it('returns { data: null, error } on HTTP error', async () => {
      mockFetch.mockResolvedValue(jsonResponse({ error: 'Forbidden' }, 403));
      const api = createApiClient({ baseUrl: '/api' });

      const result = await api.safeFetch('/secret');
      expect(result.data).toBeNull();
      expect(result.error).toBe('Forbidden');
    });

    it('returns { data: null, error } on network error', async () => {
      mockFetch.mockRejectedValue(new Error('Network error'));
      const api = createApiClient({ baseUrl: '/api' });

      const result = await api.safeFetch('/unreachable');
      expect(result.data).toBeNull();
      expect(result.error).toBe('API unavailable');
    });
  });
});
