import { renderHook, act, waitFor } from '@testing-library/react-native'
import { useCategories } from './use-categories'
import { useCategoriesStore } from './use-categories'
import { root } from '@repo/core'
import { CategoryController } from '@repo/sdk'
import { handleError } from './use-error'
import { OWNER_ID } from '@/lib/auth'

// Mock dependencies
jest.mock('@repo/core', () => ({
  root: {
    get: jest.fn(),
  },
}))

jest.mock('./use-error', () => ({
  handleError: jest.fn(async (cb) => {
    try {
      const data = await cb()
      return { data, error: null }
    } catch (e) {
      return { data: null, error: e }
    }
  }),
}))

jest.mock('@/lib/auth', () => ({
  OWNER_ID: 'test-owner-id',
}))

describe('useCategories', () => {
  beforeEach(() => {
    jest.clearAllMocks()
    // Reset the store state before each test
    useCategoriesStore.setState({
      data: undefined,
      loading: false,
      error: null,
      hasLoaded: false,
    })
  })

  afterEach(() => {
    jest.restoreAllMocks()
  })

  describe('initial state', () => {
    it('should return initial state with no data loaded', async () => {
      const mockCategoryController = {
        list: jest.fn().mockResolvedValue({
          categories: [
            {
              id: '1',
              name: 'Category 1',
              nameEn: 'Category 1',
              description: '',
              descriptionEn: '',
              sortOrder: 0,
              isActive: true,
              ownerId: 'owner-id',
              isDeleted: false,
              createdAt: new Date(),
              updatedAt: new Date(),
              tags: [],
              templates: [],
            },
          ],
          total: 1,
          page: 1,
          limit: 1000,
        }),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

      // Wait for auto-load to complete
      await waitFor(() => {
        expect(result.current.data).toBeDefined()
      })

      expect(result.current.error).toBeNull()
    })

    it('should have loading state initially false', async () => {
      const mockCategoryController = {
        list: jest.fn().mockResolvedValue({
          categories: [],
          total: 0,
          page: 1,
          limit: 1000,
        }),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

      // After auto-load completes, loading should be false
      await waitFor(() => {
        expect(result.current.loading).toBe(false)
      })
    })
  })

  describe('load function', () => {
    it('should load categories successfully', async () => {
      const mockData = {
        categories: [
          {
            id: '1',
            name: 'Category 1',
            nameEn: 'Category 1',
            description: '',
            descriptionEn: '',
            sortOrder: 0,
            isActive: true,
            ownerId: 'owner-id',
            isDeleted: false,
            createdAt: new Date(),
            updatedAt: new Date(),
            tags: [],
            templates: [],
          },
          {
            id: '2',
            name: 'Category 2',
            nameEn: 'Category 2',
            description: '',
            descriptionEn: '',
            sortOrder: 0,
            isActive: true,
            ownerId: 'owner-id',
            isDeleted: false,
            createdAt: new Date(),
            updatedAt: new Date(),
            tags: [],
            templates: [],
          },
        ],
        total: 2,
        page: 1,
        limit: 1000,
      }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

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

      expect(mockCategoryController.list).toHaveBeenCalledWith({
        page: 1,
        limit: 1000,
        isActive: true,
        orderBy: 'sortOrder',
        order: 'desc',
        withChildren: true,
        ownerId: OWNER_ID,
      })
      expect(result.current.data).toEqual(mockData)
      expect(result.current.error).toBeNull()
    })

    it('should handle API errors', async () => {
      const mockError = {
        status: 500,
        statusText: 'Internal Server Error',
        message: 'Failed to load categories',
      }

      const mockCategoryController = {
        list: jest.fn().mockRejectedValue(mockError),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

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

      expect(result.current.error).toEqual(mockError)
      expect(result.current.data).toBeNull()
    })

    it('should handle unexpected errors', async () => {
      const mockError = new Error('Network error')

      const mockCategoryController = {
        list: jest.fn().mockRejectedValue(mockError),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

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

      expect(result.current.error).toBeDefined()
    })

    it('should merge custom params with default params', async () => {
      const mockData = { categories: [], total: 0, page: 1, limit: 1000 }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

      // Set hasLoaded to true to prevent auto-load
      useCategoriesStore.setState({ hasLoaded: true })

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

      await act(async () => {
        await result.current.load({ page: 2, limit: 20, orderBy: 'sortOrder', order: 'desc', ownerId: OWNER_ID })
      })

      expect(mockCategoryController.list).toHaveBeenCalledWith({
        page: 2,
        limit: 20,
        isActive: true,
        orderBy: 'sortOrder',
        order: 'desc',
        withChildren: true,
        ownerId: OWNER_ID,
      })
    })

    it('should not load if already loading', async () => {
      const mockCategoryController = {
        list: jest.fn().mockResolvedValue({ categories: [], total: 0 }),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

      // Start first load
      const firstLoad = act(async () => {
        await result.current.load()
      })

      // Try to load again while first is still loading
      act(() => {
        result.current.load()
      })

      await firstLoad

      // Should only call list once due to loading check
      expect(mockCategoryController.list).toHaveBeenCalledTimes(1)
    })
  })

  describe('loading state', () => {
    it('should set loading to true during fetch', async () => {
      let resolveFetch: (value: any) => void
      const fetchPromise = new Promise((resolve) => {
        resolveFetch = resolve
      })

      const mockCategoryController = {
        list: jest.fn().mockReturnValue(fetchPromise),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

      act(() => {
        result.current.load()
      })

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

      await act(async () => {
        resolveFetch!({ categories: [], total: 0 })
        await fetchPromise
      })

      expect(result.current.loading).toBe(false)
    })

    it('should set loading to false after error', async () => {
      const mockError = {
        status: 500,
        statusText: 'Error',
        message: 'Test error',
      }

      const mockCategoryController = {
        list: jest.fn(),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

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

      expect(result.current.loading).toBe(false)
    })
  })

  describe('initial load on mount', () => {
    it('should auto-load on mount if not loaded', async () => {
      const mockData = {
        categories: [
          {
            id: '1',
            name: 'Category 1',
            nameEn: 'Category 1',
            description: '',
            descriptionEn: '',
            sortOrder: 0,
            isActive: true,
            ownerId: 'owner-id',
            isDeleted: false,
            createdAt: new Date(),
            updatedAt: new Date(),
            tags: [],
            templates: [],
          },
        ],
        total: 1,
        page: 1,
        limit: 1000,
      }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

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

      await waitFor(() => {
        expect(result.current.data).toEqual(mockData)
      })

      expect(mockCategoryController.list).toHaveBeenCalled()
    })

    it('should not auto-load if already loaded', async () => {
      const mockData = {
        categories: [
          {
            id: '1',
            name: 'Category 1',
            nameEn: 'Category 1',
            description: '',
            descriptionEn: '',
            sortOrder: 0,
            isActive: true,
            ownerId: 'owner-id',
            isDeleted: false,
            createdAt: new Date(),
            updatedAt: new Date(),
            tags: [],
            templates: [],
          },
        ],
        total: 1,
        page: 1,
        limit: 1000,
      }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

      // Set store to already loaded state
      useCategoriesStore.setState({
        data: mockData,
        loading: false,
        error: null,
        hasLoaded: true,
      })

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

      // Should not call list again since hasLoaded is true
      expect(mockCategoryController.list).not.toHaveBeenCalled()
      expect(result.current.data).toEqual(mockData)
    })
  })

  describe('state persistence', () => {
    it('should share state across hook instances', async () => {
      const mockData = {
        categories: [
          {
            id: '1',
            name: 'Category 1',
            nameEn: 'Category 1',
            description: '',
            descriptionEn: '',
            sortOrder: 0,
            isActive: true,
            ownerId: 'owner-id',
            isDeleted: false,
            createdAt: new Date(),
            updatedAt: new Date(),
            tags: [],
            templates: [],
          },
        ],
        total: 1,
        page: 1,
        limit: 1000,
      }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

      const { result: result1 } = renderHook(() => useCategories())
      const { result: result2 } = renderHook(() => useCategories())

      await act(async () => {
        await result1.current.load()
      })

      // Both hook instances should share the same store state
      expect(result2.current.data).toEqual(mockData)
      expect(result1.current.data).toBe(result2.current.data)
    })
  })

  describe('params handling', () => {
    it('should use initial params when calling load without arguments', async () => {
      const mockData = { categories: [], total: 0, page: 1, limit: 1000 }
      const initialParams = {
        page: 1,
        limit: 1000,
        orderBy: 'sortOrder',
        order: 'desc',
        isActive: false,
        ownerId: OWNER_ID,
      }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

      // Set hasLoaded to true to prevent auto-load
      useCategoriesStore.setState({ hasLoaded: true })

      const { result } = renderHook(() => useCategories(initialParams))

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

      expect(mockCategoryController.list).toHaveBeenCalledWith({
        page: 1,
        limit: 1000,
        isActive: false, // Initial params should override default
        orderBy: 'sortOrder',
        order: 'desc',
        withChildren: true,
        ownerId: OWNER_ID,
      })
    })

    it('should override initial params when load is called with arguments', async () => {
      const mockData = { categories: [], total: 0, page: 1, limit: 1000 }
      const initialParams = {
        page: 1,
        limit: 1000,
        orderBy: 'sortOrder',
        order: 'desc',
        isActive: true,
        ownerId: OWNER_ID,
      }

      const mockCategoryController = {
        list: jest.fn().mockResolvedValue(mockData),
      }
      ;(root.get as jest.Mock).mockReturnValue(mockCategoryController)

      // Set hasLoaded to true to prevent auto-load
      useCategoriesStore.setState({ hasLoaded: true })

      const { result } = renderHook(() => useCategories(initialParams))

      await act(async () => {
        await result.current.load({ isActive: false, page: 1, limit: 1000, orderBy: 'sortOrder', order: 'desc', ownerId: OWNER_ID })
      })

      expect(mockCategoryController.list).toHaveBeenCalledWith({
        page: 1,
        limit: 1000,
        isActive: false, // Load params should override initial
        orderBy: 'sortOrder',
        order: 'desc',
        withChildren: true,
        ownerId: OWNER_ID,
      })
    })
  })
})
