import { renderHook, act, waitFor } from '@/__tests__/utils/test-helpers'
import { usePortfolios } from './use-portfolios'
import { apiClient } from '@/lib/api-client'
import { createMockPortfolio } from '@/__tests__/utils/test-helpers'

jest.mock('@/lib/api-client')

describe('usePortfolios Hook', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  describe('fetchPortfolios', () => {
    it('should fetch portfolios on mount', async () => {
      const mockPortfolios = [createMockPortfolio({ id: 1 }), createMockPortfolio({ id: 2 })]
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: mockPortfolios,
      })

      const { result } = renderHook(() => usePortfolios())

      expect(result.current.isLoading).toBe(true)

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      expect(result.current.portfolios).toEqual(mockPortfolios)
      expect(result.current.error).toBeNull()
    })

    it('should set error on fetch failure', async () => {
      const errorMessage = 'Failed to fetch'
      ;(apiClient.get as jest.Mock).mockRejectedValueOnce(new Error(errorMessage))

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      expect(result.current.error).toBe(errorMessage)
      expect(result.current.portfolios).toEqual([])
    })

    it('should handle non-Error exceptions', async () => {
      ;(apiClient.get as jest.Mock).mockRejectedValueOnce('Unknown error')

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      expect(result.current.error).toBe('Failed to fetch portfolios')
    })

    it('should clear error on successful refetch', async () => {
      ;(apiClient.get as jest.Mock).mockRejectedValueOnce(new Error('Network error'))

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.error).toBe('Network error')
      })

      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [createMockPortfolio()],
      })

      await act(async () => {
        await result.current.fetchPortfolios()
      })

      expect(result.current.error).toBeNull()
      expect(result.current.portfolios.length).toBeGreaterThan(0)
    })
  })

  describe('createPortfolio', () => {
    it('should create new portfolio', async () => {
      const existingPortfolio = createMockPortfolio({ id: 1 })
      const newPortfolio = createMockPortfolio({ id: 2, name: 'New Portfolio' })

      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [existingPortfolio],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      ;(apiClient.post as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: newPortfolio,
      })

      let createdPortfolio
      await act(async () => {
        createdPortfolio = await result.current.createPortfolio(
          newPortfolio.name,
          newPortfolio.domain
        )
      })

      expect(createdPortfolio).toEqual(newPortfolio)
      expect(result.current.portfolios).toContainEqual(newPortfolio)
      expect(result.current.portfolios.length).toBe(2)
    })

    it('should call API with correct parameters', async () => {
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const newPortfolio = createMockPortfolio()
      ;(apiClient.post as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: newPortfolio,
      })

      await act(async () => {
        await result.current.createPortfolio(newPortfolio.name, newPortfolio.domain)
      })

      expect(apiClient.post).toHaveBeenCalledWith('/portfolios', {
        name: newPortfolio.name,
        domain: newPortfolio.domain,
      })
    })

    it('should throw error on creation failure', async () => {
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const errorMessage = 'Failed to create portfolio'
      ;(apiClient.post as jest.Mock).mockRejectedValueOnce(new Error(errorMessage))

      await expect(
        act(async () => {
          await result.current.createPortfolio('Test', 'test.com')
        })
      ).rejects.toThrow(errorMessage)
    })
  })

  describe('uploadPortfolio', () => {
    it('should upload portfolio file', async () => {
      const portfolio = createMockPortfolio({ id: 1 })
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [portfolio],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const file = new File(['test content'], 'portfolio.zip')
      const uploadedPortfolio = createMockPortfolio({
        id: 1,
        path: '/uploads/portfolio.zip',
      })
      ;(apiClient.upload as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: uploadedPortfolio,
      })

      let result_uploaded
      await act(async () => {
        result_uploaded = await result.current.uploadPortfolio(1, file)
      })

      expect(result_uploaded).toEqual(uploadedPortfolio)
      expect(result.current.portfolios[0]).toEqual(uploadedPortfolio)
    })

    it('should call upload API with FormData', async () => {
      const portfolio = createMockPortfolio({ id: 1 })
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [portfolio],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const file = new File(['test'], 'test.zip')
      ;(apiClient.upload as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: portfolio,
      })

      await act(async () => {
        await result.current.uploadPortfolio(1, file)
      })

      expect(apiClient.upload).toHaveBeenCalledWith(
        '/portfolios/1/upload',
        expect.any(FormData)
      )
    })

    it('should throw error on upload failure', async () => {
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [createMockPortfolio()],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const errorMessage = 'Upload failed'
      ;(apiClient.upload as jest.Mock).mockRejectedValueOnce(
        new Error(errorMessage)
      )

      const file = new File(['test'], 'test.zip')

      await expect(
        act(async () => {
          await result.current.uploadPortfolio(1, file)
        })
      ).rejects.toThrow(errorMessage)
    })
  })

  describe('deployPortfolio', () => {
    it('should deploy portfolio', async () => {
      const portfolio = createMockPortfolio({ id: 1, active: false })
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [portfolio],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const deployedPortfolio = createMockPortfolio({
        id: 1,
        active: true,
      })
      ;(apiClient.post as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: deployedPortfolio,
      })

      let deployed
      await act(async () => {
        deployed = await result.current.deployPortfolio(1)
      })

      expect(deployed).toEqual(deployedPortfolio)
      expect(result.current.portfolios[0].active).toBe(true)
    })

    it('should call deploy API correctly', async () => {
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [createMockPortfolio()],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      ;(apiClient.post as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: createMockPortfolio(),
      })

      await act(async () => {
        await result.current.deployPortfolio(1)
      })

      expect(apiClient.post).toHaveBeenCalledWith('/portfolios/1/deploy')
    })

    it('should throw error on deployment failure', async () => {
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: [createMockPortfolio()],
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })

      const errorMessage = 'Deployment failed'
      ;(apiClient.post as jest.Mock).mockRejectedValueOnce(
        new Error(errorMessage)
      )

      await expect(
        act(async () => {
          await result.current.deployPortfolio(1)
        })
      ).rejects.toThrow(errorMessage)
    })
  })

  describe('state management', () => {
    it('should maintain portfolio list correctly', async () => {
      const portfolios = [
        createMockPortfolio({ id: 1 }),
        createMockPortfolio({ id: 2 }),
        createMockPortfolio({ id: 3 }),
      ]
      ;(apiClient.get as jest.Mock).mockResolvedValueOnce({
        success: true,
        data: portfolios,
      })

      const { result } = renderHook(() => usePortfolios())

      await waitFor(() => {
        expect(result.current.portfolios.length).toBe(3)
      })

      expect(result.current.portfolios).toEqual(portfolios)
    })

    it('should track loading state correctly', async () => {
      ;(apiClient.get as jest.Mock).mockImplementationOnce(
        () =>
          new Promise((resolve) =>
            setTimeout(
              () =>
                resolve({
                  success: true,
                  data: [createMockPortfolio()],
                }),
              100
            )
          )
      )

      const { result } = renderHook(() => usePortfolios())

      expect(result.current.isLoading).toBe(true)

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false)
      })
    })
  })
})
