import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { searchItems } from './api';

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

// Mock localStorage
const mockLocalStorage = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
};
Object.defineProperty(window, 'localStorage', {
  value: mockLocalStorage,
  writable: true,
});

describe('searchItems', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockLocalStorage.getItem.mockReturnValue('mock-token');
  });

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

  describe('Query validation', () => {
    it('should return empty array for empty query', async () => {
      const result = await searchItems('');
      expect(result).toEqual([]);
      expect(mockFetch).not.toHaveBeenCalled();
    });

    it('should return empty array for single character query', async () => {
      const result = await searchItems('a');
      expect(result).toEqual([]);
      expect(mockFetch).not.toHaveBeenCalled();
    });

    it('should return empty array for null query', async () => {
      const result = await searchItems(null as any);
      expect(result).toEqual([]);
      expect(mockFetch).not.toHaveBeenCalled();
    });

    it('should return empty array for undefined query', async () => {
      const result = await searchItems(undefined as any);
      expect(result).toEqual([]);
      expect(mockFetch).not.toHaveBeenCalled();
    });

    it('should call API for query with 2 characters', async () => {
      const mockItems = [
        {
          _id: '1',
          itemCode: 'AB123',
          itemName: 'Test Item',
          description: null,
          brand: null,
          barcode: null,
          countryOfOrigin: null,
          blend: null,
          grainType: null,
          varietyType: null,
          processType: null,
          unit: null,
          bagWeightKg: null,
          packing: null,
          variant: null,
          riceName: null,
          category: null,
          status: null,
        },
      ];

      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify(mockItems),
      });

      const result = await searchItems('ab');
      expect(result).toEqual(mockItems);
      expect(mockFetch).toHaveBeenCalledTimes(1);
    });
  });

  describe('API call behavior', () => {
    it('should call correct endpoint with encoded query', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems('test query');

      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining('/items/search?q=test%20query'),
        expect.objectContaining({
          headers: expect.objectContaining({
            Authorization: 'Bearer mock-token',
          }),
        })
      );
    });

    it('should encode special characters in query', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems('test&query=value');

      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining('/items/search?q=test%26query%3Dvalue'),
        expect.any(Object)
      );
    });

    it('should include authorization header when token exists', async () => {
      mockLocalStorage.getItem.mockReturnValue('test-token-123');
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems('test');

      expect(mockFetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          headers: expect.objectContaining({
            Authorization: 'Bearer test-token-123',
          }),
        })
      );
    });

    it('should not include authorization header when token is missing', async () => {
      mockLocalStorage.getItem.mockReturnValue(null);
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems('test');

      const callArgs = mockFetch.mock.calls[0];
      const headers = callArgs[1]?.headers || {};
      expect(headers.Authorization).toBeUndefined();
    });
  });

  describe('Response handling', () => {
    it('should return array of ItemRecord objects', async () => {
      const mockItems = [
        {
          _id: '1',
          itemCode: 'ITEM001',
          itemName: 'Rice Basmati',
          description: 'Premium basmati rice',
          brand: 'Brand A',
          barcode: '123456789',
          countryOfOrigin: 'India',
          blend: 'Pure',
          grainType: 'Long',
          varietyType: 'Basmati',
          processType: 'Milled',
          unit: 'kg',
          bagWeightKg: 50,
          packing: 'Bag',
          variant: 'White',
          riceName: 'Basmati',
          category: 'Rice',
          status: 'active',
        },
        {
          _id: '2',
          itemCode: 'ITEM002',
          itemName: 'Rice Jasmine',
          description: null,
          brand: null,
          barcode: null,
          countryOfOrigin: 'Thailand',
          blend: null,
          grainType: 'Long',
          varietyType: 'Jasmine',
          processType: null,
          unit: 'kg',
          bagWeightKg: 25,
          packing: null,
          variant: null,
          riceName: 'Jasmine',
          category: null,
          status: null,
        },
      ];

      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify(mockItems),
      });

      const result = await searchItems('rice');
      expect(result).toEqual(mockItems);
      expect(result).toHaveLength(2);
      expect(result[0]._id).toBe('1');
      expect(result[0].itemCode).toBe('ITEM001');
      expect(result[1]._id).toBe('2');
      expect(result[1].itemCode).toBe('ITEM002');
    });

    it('should return empty array when API returns empty array', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      const result = await searchItems('nonexistent');
      expect(result).toEqual([]);
    });
  });

  describe('Error handling', () => {
    it('should throw descriptive error on API failure (404)', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 404,
        text: async () => 'Not Found',
      });

      await expect(searchItems('test')).rejects.toThrow(
        'API request failed with status 404: Not Found'
      );
    });

    it('should throw descriptive error on API failure (500)', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        text: async () => 'Internal Server Error',
      });

      await expect(searchItems('test')).rejects.toThrow(
        'API request failed with status 500: Internal Server Error'
      );
    });

    it('should throw descriptive error on API failure (401)', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 401,
        text: async () => 'Unauthorized',
      });

      await expect(searchItems('test')).rejects.toThrow(
        'API request failed with status 401: Unauthorized'
      );
    });

    it('should throw error on network failure', async () => {
      mockFetch.mockRejectedValueOnce(new Error('Network error'));

      await expect(searchItems('test')).rejects.toThrow('Network error');
    });

    it('should throw error on invalid JSON response', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => 'invalid json',
      });

      await expect(searchItems('test')).rejects.toThrow();
    });
  });

  describe('Edge cases', () => {
    it('should handle query with only whitespace', async () => {
      const result = await searchItems('  ');
      expect(result).toEqual([]);
      expect(mockFetch).not.toHaveBeenCalled();
    });

    it('should handle very long query strings', async () => {
      const longQuery = 'a'.repeat(1000);
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems(longQuery);

      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining(`/items/search?q=${encodeURIComponent(longQuery)}`),
        expect.any(Object)
      );
    });

    it('should handle unicode characters in query', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems('café');

      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining('/items/search?q=caf%C3%A9'),
        expect.any(Object)
      );
    });

    it('should handle query with numbers', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        text: async () => JSON.stringify([]),
      });

      await searchItems('123');

      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining('/items/search?q=123'),
        expect.any(Object)
      );
    });
  });
});
