import {Product} from '@shopify/shop-minis-platform'
import {describe, expect, it, vi} from 'vitest'

import {
  render,
  screen,
  mockMinisSDK,
  resetAllMocks,
  userEvent,
  waitFor,
} from '../../test-utils'

import {BuyNowButton} from './buy-now'

// Mock hooks
const mockShowErrorToast = vi.fn()

vi.mock('../../internal/useShopCartActions', () => ({
  useShopCartActions: () => ({
    addToCart: mockMinisSDK.addToCart,
    buyProduct: mockMinisSDK.buyProduct,
  }),
}))

vi.mock('../../hooks', () => ({
  useShopNavigation: () => ({
    navigateToProduct: mockMinisSDK.navigateToProduct,
  }),
  useErrorToast: () => ({
    showErrorToast: mockShowErrorToast,
  }),
}))

describe('BuyNowButton', () => {
  const mockProduct: Product = {
    id: 'gid://shopify/Product/123',
    title: 'Test Product',
    reviewAnalytics: {
      averageRating: null,
      reviewCount: null,
    },
    shop: {
      id: 'gid://shopify/Shop/1',
      name: 'Test Shop',
    },
    defaultVariantId: 'gid://shopify/ProductVariant/456',
    isFavorited: false,
    price: {
      amount: '10.00',
      currencyCode: 'USD',
    },
  }

  const defaultProps = {
    product: mockProduct,
    productVariantId: 'gid://shopify/ProductVariant/456',
  }

  // eslint-disable-next-line jest/require-top-level-describe
  beforeEach(() => {
    resetAllMocks()
    mockShowErrorToast.mockClear()
  })

  it('renders with default text', () => {
    render(<BuyNowButton {...defaultProps} />)

    expect(screen.getByRole('button')).toBeInTheDocument()
    expect(screen.getByText('Buy now')).toBeInTheDocument()
  })

  it('renders with required props', () => {
    render(<BuyNowButton {...defaultProps} />)

    const button = screen.getByRole('button')
    expect(button).toBeInTheDocument()
  })

  it('respects disabled prop', () => {
    render(<BuyNowButton {...defaultProps} disabled />)

    const button = screen.getByRole('button')
    expect(button).toBeDisabled()
  })

  it('applies custom className', () => {
    render(<BuyNowButton {...defaultProps} className="custom-class" />)

    const button = screen.getByRole('button')
    expect(button).toHaveClass('custom-class')
  })

  it('renders with different sizes', () => {
    const {rerender} = render(<BuyNowButton {...defaultProps} size="sm" />)

    expect(screen.getByRole('button')).toBeInTheDocument()

    rerender(<BuyNowButton {...defaultProps} size="default" />)
    expect(screen.getByRole('button')).toBeInTheDocument()

    rerender(<BuyNowButton {...defaultProps} size="lg" />)
    expect(screen.getByRole('button')).toBeInTheDocument()
  })

  it('renders with discount code prop', () => {
    const discountCode = 'SUMMER20'

    render(<BuyNowButton {...defaultProps} discountCode={discountCode} />)

    expect(screen.getByRole('button')).toBeInTheDocument()
  })

  it('calls buyProduct when clicked and not a referral product', async () => {
    const user = userEvent.setup()
    mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})

    render(<BuyNowButton {...defaultProps} />)

    const button = screen.getByRole('button')
    await user.click(button)

    expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
      productId: mockProduct.id,
      productVariantId: defaultProps.productVariantId,
      quantity: 1,
      discountCode: undefined,
    })
  })

  it('navigates to product page when product is referral', async () => {
    const user = userEvent.setup()
    const referralProduct: Product = {...mockProduct, referral: true}

    render(<BuyNowButton {...defaultProps} product={referralProduct} />)

    // Button should show "View product" instead of "Buy now"
    expect(screen.getByText('View product')).toBeInTheDocument()
    expect(screen.queryByText('Buy now')).not.toBeInTheDocument()

    const button = screen.getByRole('button')
    await user.click(button)

    expect(mockMinisSDK.navigateToProduct).toHaveBeenCalledWith({
      productId: referralProduct.id,
    })
    expect(mockMinisSDK.buyProduct).not.toHaveBeenCalled()
  })

  it('shows processing state while purchasing', async () => {
    const user = userEvent.setup()
    mockMinisSDK.buyProduct.mockImplementation(() => new Promise(() => {})) // Never resolves

    render(<BuyNowButton {...defaultProps} />)

    const button = screen.getByRole('button')
    await user.click(button)

    // Check for processing state - button should be disabled and have aria-busy
    await waitFor(() => {
      expect(button).toBeDisabled()
      expect(button).toHaveAttribute('aria-busy', 'true')
    })
  })

  it('handles buy product error gracefully', async () => {
    const user = userEvent.setup()
    const error = new Error('Purchase failed')
    mockMinisSDK.buyProduct.mockRejectedValueOnce(error)

    render(<BuyNowButton {...defaultProps} />)

    const button = screen.getByRole('button')
    await user.click(button)

    await waitFor(() => {
      expect(mockMinisSDK.buyProduct).toHaveBeenCalled()
      expect(mockShowErrorToast).toHaveBeenCalledWith({
        message: 'Failed to complete purchase',
      })
    })

    // Button should be enabled again after error
    expect(button).toBeEnabled()
    expect(button).toHaveAttribute('aria-busy', 'false')
  })

  it('does not call buyProduct when disabled', async () => {
    const user = userEvent.setup()

    render(<BuyNowButton {...defaultProps} disabled />)

    const button = screen.getByRole('button')
    await user.click(button)

    expect(mockMinisSDK.buyProduct).not.toHaveBeenCalled()
  })

  it('passes discount code to buyProduct', async () => {
    const user = userEvent.setup()
    const discountCode = 'DISCOUNT10'
    mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})

    render(<BuyNowButton {...defaultProps} discountCode={discountCode} />)

    const button = screen.getByRole('button')
    await user.click(button)

    expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
      productId: mockProduct.id,
      productVariantId: defaultProps.productVariantId,
      quantity: 1,
      discountCode,
    })
  })

  it('handles product without shop data', async () => {
    const user = userEvent.setup()
    mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})

    const productWithoutShop: Product = {
      ...mockProduct,
      shop: undefined as any,
    }

    render(
      <BuyNowButton
        product={productWithoutShop}
        productVariantId="gid://shopify/ProductVariant/456"
      />
    )

    const button = screen.getByRole('button')
    await user.click(button)

    expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
      productId: productWithoutShop.id,
      productVariantId: 'gid://shopify/ProductVariant/456',
      quantity: 1,
      discountCode: undefined,
    })
  })

  it('handles product with partial shop data', async () => {
    const user = userEvent.setup()
    mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})

    const productWithPartialShop: Product = {
      ...mockProduct,
      shop: {
        id: 'gid://shopify/Shop/1',
        name: undefined as any,
      },
    }

    render(
      <BuyNowButton
        product={productWithPartialShop}
        productVariantId="gid://shopify/ProductVariant/456"
      />
    )

    const button = screen.getByRole('button')
    await user.click(button)

    expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
      productId: productWithPartialShop.id,
      productVariantId: 'gid://shopify/ProductVariant/456',
      quantity: 1,
      discountCode: undefined,
    })
  })

  describe('sold-out state', () => {
    const soldOutProduct: Product = {
      ...mockProduct,
      variants: [
        {
          id: 'gid://shopify/ProductVariant/456',
          title: 'Default',
          isFavorited: false,
          availableForSale: false,
          price: {
            amount: '10.00',
            currencyCode: 'USD',
          },
        },
      ],
    }

    it('shows "Sold out" text when matching variant is not available for sale', () => {
      render(
        <BuyNowButton
          product={soldOutProduct}
          productVariantId="gid://shopify/ProductVariant/456"
        />
      )

      expect(screen.getByText('Sold out')).toBeInTheDocument()
      expect(screen.queryByText('Buy now')).not.toBeInTheDocument()
    })

    it('referral products show "View product" and stay clickable even when sold out', async () => {
      const user = userEvent.setup()
      const referralSoldOutProduct: Product = {
        ...soldOutProduct,
        referral: true,
      }

      render(
        <BuyNowButton
          product={referralSoldOutProduct}
          productVariantId="gid://shopify/ProductVariant/456"
        />
      )

      expect(screen.getByText('View product')).toBeInTheDocument()
      expect(screen.queryByText('Sold out')).not.toBeInTheDocument()

      const button = screen.getByRole('button')
      expect(button).toBeEnabled()

      await user.click(button)
      expect(mockMinisSDK.navigateToProduct).toHaveBeenCalledWith({
        productId: referralSoldOutProduct.id,
      })
      expect(mockMinisSDK.buyProduct).not.toHaveBeenCalled()
    })

    it('falls back to selectedVariant when variants array is missing', () => {
      const productWithSelectedVariant: Product = {
        ...mockProduct,
        variants: undefined,
        selectedVariant: {
          id: 'gid://shopify/ProductVariant/456',
          title: 'Default',
          isFavorited: false,
          availableForSale: false,
          price: {amount: '10.00', currencyCode: 'USD'},
        },
      }

      render(
        <BuyNowButton
          product={productWithSelectedVariant}
          productVariantId="gid://shopify/ProductVariant/456"
        />
      )

      expect(screen.getByText('Sold out')).toBeInTheDocument()
      expect(screen.getByRole('button')).toBeDisabled()
    })

    it('disables the button when matching variant is sold out', () => {
      render(
        <BuyNowButton
          product={soldOutProduct}
          productVariantId="gid://shopify/ProductVariant/456"
        />
      )

      expect(screen.getByRole('button')).toBeDisabled()
    })

    it('does not call buyProduct when matching variant is sold out', async () => {
      const user = userEvent.setup()

      render(
        <BuyNowButton
          product={soldOutProduct}
          productVariantId="gid://shopify/ProductVariant/456"
        />
      )

      const button = screen.getByRole('button')
      await user.click(button)

      expect(mockMinisSDK.buyProduct).not.toHaveBeenCalled()
      expect(mockMinisSDK.navigateToProduct).not.toHaveBeenCalled()
    })
  })
})
