import { apiClient } from './api-client'
import { ApiResponse } from './types'

// Mock fetch
global.fetch = jest.fn()

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {}

  return {
    getItem: (key: string) => store[key] || null,
    setItem: (key: string, value: string) => {
      store[key] = value.toString()
    },
    removeItem: (key: string) => {
      delete store[key]
    },
    clear: () => {
      store = {}
    },
  }
})()

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock,
})

describe('ApiClient', () => {
  beforeEach(() => {
    ;(global.fetch as jest.Mock).mockClear()
    localStorage.clear()
    jest.clearAllMocks()
  })

  describe('GET requests', () => {
    it('should make successful GET request', async () => {
      const mockResponse = { success: true, data: { id: 1 } }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      const result = await apiClient.get<{ id: number }>('/portfolios')

      expect(result).toEqual(mockResponse)
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:8000/api/portfolios',
        expect.objectContaining({ method: 'GET' })
      )
    })

    it('should include Authorization header when token exists', async () => {
      const token = 'test-token'
      localStorage.setItem('auth_token', token)
      const mockResponse = { success: true, data: [] }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      await apiClient.get('/portfolios')

      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          headers: expect.objectContaining({
            Authorization: `Bearer ${token}`,
          }),
        })
      )
    })

    it('should not include Authorization header when no token exists', async () => {
      const mockResponse = { success: true, data: [] }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      await apiClient.get('/portfolios')

      const callArgs = (global.fetch as jest.Mock).mock.calls[0][1]
      expect(callArgs.headers.Authorization).toBeUndefined()
    })

    it('should throw error on failed GET request', async () => {
      const errorMessage = 'Not Found'
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: false,
        status: 404,
        json: async () => ({ message: errorMessage }),
      })

      await expect(apiClient.get('/invalid')).rejects.toThrow(errorMessage)
    })
  })

  describe('POST requests', () => {
    it('should make successful POST request with body', async () => {
      const requestBody = { email: 'test@example.com', password: 'password' }
      const mockResponse = { success: true, data: { token: 'jwt-token' } }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      const result = await apiClient.post('/auth/login', requestBody)

      expect(result).toEqual(mockResponse)
      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify(requestBody),
        })
      )
    })

    it('should make POST request without body', async () => {
      const mockResponse = { success: true, data: {} }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      await apiClient.post('/endpoint')

      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          method: 'POST',
          body: undefined,
        })
      )
    })

    it('should throw error on POST validation failure (422)', async () => {
      const errorData = {
        message: 'Validation failed',
        errors: { email: ['Email already exists'] },
      }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: false,
        status: 422,
        json: async () => errorData,
      })

      await expect(apiClient.post('/auth/register')).rejects.toThrow(
        'Validation failed'
      )
    })
  })

  describe('PUT requests', () => {
    it('should make successful PUT request', async () => {
      const updateData = { name: 'Updated Name' }
      const mockResponse = { success: true, data: { name: 'Updated Name' } }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      const result = await apiClient.put('/portfolios/1', updateData)

      expect(result).toEqual(mockResponse)
      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          method: 'PUT',
          body: JSON.stringify(updateData),
        })
      )
    })
  })

  describe('DELETE requests', () => {
    it('should make successful DELETE request', async () => {
      const mockResponse = { success: true }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      const result = await apiClient.delete('/portfolios/1')

      expect(result).toEqual(mockResponse)
      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({ method: 'DELETE' })
      )
    })
  })

  describe('401 Unauthorized handling', () => {
    it('should clear token and redirect on 401', async () => {
      const token = 'expired-token'
      localStorage.setItem('auth_token', token)
      const mockHref = jest.fn()
      Object.defineProperty(window, 'location', {
        value: { href: mockHref },
        writable: true,
      })

      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: async () => ({ message: 'Unauthorized' }),
      })

      try {
        await apiClient.get('/protected')
      } catch (error) {
        // Expected to throw
      }

      expect(localStorage.getItem('auth_token')).toBeNull()
    })
  })

  describe('Error handling', () => {
    it('should handle network errors', async () => {
      const networkError = new Error('Network request failed')
      ;(global.fetch as jest.Mock).mockRejectedValueOnce(networkError)

      await expect(apiClient.get('/endpoint')).rejects.toThrow(
        'Network request failed'
      )
    })

    it('should use default error message when not provided', async () => {
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: async () => ({ success: false }),
      })

      await expect(apiClient.get('/endpoint')).rejects.toThrow(
        'API request failed'
      )
    })
  })

  describe('File upload', () => {
    it('should upload FormData successfully', async () => {
      const token = 'test-token'
      localStorage.setItem('auth_token', token)
      const formData = new FormData()
      formData.append('file', new File(['test'], 'test.zip'))

      const mockResponse = { success: true, data: { path: '/uploads/test.zip' } }
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => mockResponse,
      })

      const result = await apiClient.upload('/portfolios/1/upload', formData)

      expect(result).toEqual(mockResponse)
      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          method: 'POST',
          body: formData,
        })
      )
    })

    it('should handle upload errors', async () => {
      const formData = new FormData()
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: async () => ({ message: 'Invalid file' }),
      })

      await expect(
        apiClient.upload('/portfolios/1/upload', formData)
      ).rejects.toThrow('Invalid file')
    })

    it('should handle 401 during upload', async () => {
      const token = 'expired-token'
      localStorage.setItem('auth_token', token)
      const formData = new FormData()

      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: async () => ({ message: 'Unauthorized' }),
      })

      try {
        await apiClient.upload('/portfolios/1/upload', formData)
      } catch (error) {
        // Expected
      }

      expect(localStorage.getItem('auth_token')).toBeNull()
    })
  })

  describe('Content-Type headers', () => {
    it('should set Content-Type for JSON requests', async () => {
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({ success: true }),
      })

      await apiClient.post('/endpoint', { data: 'test' })

      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          headers: expect.objectContaining({
            'Content-Type': 'application/json',
          }),
        })
      )
    })

    it('should not set Content-Type for file uploads', async () => {
      const formData = new FormData()
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({ success: true }),
      })

      await apiClient.upload('/upload', formData)

      const callArgs = (global.fetch as jest.Mock).mock.calls[0][1]
      expect(callArgs.headers['Content-Type']).toBeUndefined()
    })
  })

  describe('API URL construction', () => {
    it('should use NEXT_PUBLIC_API_URL environment variable', async () => {
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({ success: true }),
      })

      await apiClient.get('/test')

      expect(global.fetch).toHaveBeenCalledWith(
        expect.stringContaining('http://localhost:8000/api'),
        expect.any(Object)
      )
    })

    it('should properly combine API base URL with endpoint', async () => {
      ;(global.fetch as jest.Mock).mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({ success: true }),
      })

      await apiClient.get('/portfolios/1/details')

      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:8000/api/portfolios/1/details',
        expect.any(Object)
      )
    })
  })
})
