import {renderHook} from '@testing-library/react'
import {describe, expect, it, vi} from 'vitest'

import {useHandleAction} from './useHandleAction'

import type {ShopActionResult} from '@shopify/shop-minis-platform/actions'

describe('useHandleAction', () => {
  describe('Success Case', () => {
    it('returns data when action succeeds', async () => {
      const mockData = {id: '123', name: 'Test'}
      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: mockData,
        } as ShopActionResult<typeof mockData>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      const data = await result.current()

      expect(data).toEqual(mockData)
      expect(mockAction).toHaveBeenCalledTimes(1)
    })

    it('passes arguments to the action correctly', async () => {
      const mockData = {success: true}
      const mockAction = vi.fn((_arg1: string, _arg2: number) =>
        Promise.resolve({
          ok: true as const,
          data: mockData,
        } as ShopActionResult<typeof mockData>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      await result.current('test', 42)

      expect(mockAction).toHaveBeenCalledWith('test', 42)
    })

    it('handles complex data structures', async () => {
      const complexData = {
        user: {
          id: '1',
          name: 'John',
          addresses: [
            {street: '123 Main St', city: 'New York'},
            {street: '456 Oak Ave', city: 'Boston'},
          ],
        },
        metadata: {
          timestamp: '2024-01-01',
          version: 2,
        },
      }

      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: complexData,
        } as ShopActionResult<typeof complexData>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      const data = await result.current()

      expect(data).toEqual(complexData)
    })

    it('handles null/undefined data', async () => {
      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: null,
        } as ShopActionResult<null>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      const data = await result.current()

      expect(data).toBeNull()
    })
  })

  describe('Error Case', () => {
    it('throws error when action fails', async () => {
      const mockError = {
        code: 'ERROR_CODE',
        message: 'Something went wrong',
      }

      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: false as const,
          error: mockError,
        } as ShopActionResult<any>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      await expect(result.current()).rejects.toEqual(mockError)
      expect(mockAction).toHaveBeenCalledTimes(1)
    })

    it('preserves error structure', async () => {
      const complexError = {
        code: 'VALIDATION_ERROR',
        message: 'Validation failed',
        details: {
          fields: ['email', 'password'],
          reasons: ['Invalid format', 'Too short'],
        },
      }

      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: false as const,
          error: complexError,
        } as unknown as ShopActionResult<any>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      await expect(result.current()).rejects.toEqual(complexError)
    })

    it('handles string errors', async () => {
      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: false as const,
          error: 'Simple error message',
        } as unknown as ShopActionResult<any>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      await expect(result.current()).rejects.toBe('Simple error message')
    })
  })

  describe('Function Stability', () => {
    it('maintains reference equality across renders', () => {
      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: 'test',
        } as ShopActionResult<string>)
      )

      const {result, rerender} = renderHook(() => useHandleAction(mockAction))

      const firstRender = result.current
      rerender()
      const secondRender = result.current

      expect(firstRender).toBe(secondRender)
    })

    it('updates when action changes', () => {
      const mockAction1 = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: 'action1',
        } as ShopActionResult<string>)
      )

      const mockAction2 = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: 'action2',
        } as ShopActionResult<string>)
      )

      const {result, rerender} = renderHook(
        ({action}) => useHandleAction(action),
        {initialProps: {action: mockAction1}}
      )

      const firstRender = result.current

      rerender({action: mockAction2})
      const secondRender = result.current

      expect(firstRender).not.toBe(secondRender)
    })
  })

  describe('Multiple Calls', () => {
    it('handles multiple concurrent calls', async () => {
      let callCount = 0
      const mockAction = vi.fn(async () => {
        const currentCall = ++callCount
        await new Promise(resolve => setTimeout(resolve, 10))
        return {
          ok: true as const,
          data: currentCall,
        } as ShopActionResult<number>
      })

      const {result} = renderHook(() => useHandleAction(mockAction))

      const [result1, result2, result3] = await Promise.all([
        result.current(),
        result.current(),
        result.current(),
      ])

      expect(result1).toBe(1)
      expect(result2).toBe(2)
      expect(result3).toBe(3)
      expect(mockAction).toHaveBeenCalledTimes(3)
    })

    it('handles sequential calls', async () => {
      let counter = 0
      const mockAction = vi.fn(
        async () =>
          ({
            ok: true as const,
            data: ++counter,
          }) as ShopActionResult<number>
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      const result1 = await result.current()
      const result2 = await result.current()
      const result3 = await result.current()

      expect(result1).toBe(1)
      expect(result2).toBe(2)
      expect(result3).toBe(3)
    })
  })

  describe('Promise Behavior', () => {
    it('returns a promise', () => {
      const mockAction = vi.fn(() =>
        Promise.resolve({
          ok: true as const,
          data: 'test',
        } as ShopActionResult<string>)
      )

      const {result} = renderHook(() => useHandleAction(mockAction))

      const returnValue = result.current()

      expect(returnValue).toBeInstanceOf(Promise)
    })

    it('handles rejected promises from action', async () => {
      const networkError = new Error('Network error')
      const mockAction = vi.fn(() => Promise.reject(networkError))

      const {result} = renderHook(() => useHandleAction(mockAction))

      await expect(result.current()).rejects.toThrow('Network error')
    })
  })
})
