/**
 * (happy path, negative locale, contract, BVA for supported locales).
 */
import React from 'react'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, jest } from '@jest/globals'

const mockHasLocale = jest.fn()
const mockSetRequestLocale = jest.fn()

/** Matches Next.js: notFound() aborts rendering (otherwise layout would continue with bad locale). */
const mockNotFound = jest.fn(() => {
  const err = new Error('NEXT_NOT_FOUND')
  ;(err as unknown as { digest?: string }).digest = 'NEXT_NOT_FOUND'
  throw err
})

jest.mock('@/constants/system', () => ({
  APP_NAME: 'Test App',
}))

const mockRouting = {
  locales: ['en', 'vi'],
}

jest.mock('@/i18n/routing', () => ({
  routing: mockRouting,
}))

jest.mock('next-intl', () => ({
  hasLocale: (...args: unknown[]) => mockHasLocale(...args),
  NextIntlClientProvider: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="next-intl-provider">{children}</div>
  ),
}))

jest.mock('next-intl/server', () => ({
  setRequestLocale: (...args: unknown[]) => mockSetRequestLocale(...args),
}))

jest.mock('next/navigation', () => ({
  notFound: () => mockNotFound(),
}))

jest.mock('@/providers/auth-provider', () => ({
  AuthProvider: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="auth-provider">{children}</div>
  ),
}))

jest.mock('@/providers/query-client-provider', () => ({
  __esModule: true,
  default: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="react-query-provider">{children}</div>
  ),
}))

jest.mock('@/share/ui/tooltip', () => ({
  TooltipProvider: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="tooltip-provider">{children}</div>
  ),
}))

jest.mock('@/share/ui/sonner', () => ({
  Toaster: () => <div data-testid="toaster" />,
}))

describe('[locale]/layout.tsx', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  describe('metadata & generateStaticParams', () => {
    it('happy path: metadata uses APP_NAME (template + default + description)', async () => {
      const { metadata } = await import('../layout')

      expect(metadata.title).toEqual({
        template: '%s | Test App',
        default: 'Test App',
      })
      expect(metadata.description).toBe('Generated by Test App')
    })

    it('contract: generateStaticParams mirrors routing.locales', async () => {
      const { generateStaticParams } = await import('../layout')

      expect(generateStaticParams()).toEqual(
        mockRouting.locales.map((locale) => ({ locale }))
      )
    })
  })

  describe('invalid locale → notFound (negative)', () => {
    beforeEach(() => {
      mockHasLocale.mockReturnValue(false)
    })

    it.each([
      { locale: 'fr', label: 'unsupported language' },
      { locale: 'jp', label: 'another unsupported' },
      { locale: '', label: 'empty segment' },
    ])('throws after notFound for $label ($locale)', async ({ locale }) => {
      const { default: LocaleLayout } = await import('../layout')

      await expect(
        LocaleLayout({
          children: <div>Child</div>,
          params: Promise.resolve({ locale }),
        })
      ).rejects.toThrow('NEXT_NOT_FOUND')

      expect(mockHasLocale).toHaveBeenCalledWith(mockRouting.locales, locale)
      expect(mockNotFound).toHaveBeenCalledTimes(1)
      expect(mockSetRequestLocale).not.toHaveBeenCalled()
    })
  })

  describe('valid locale (happy path + BVA)', () => {
    beforeEach(() => {
      mockHasLocale.mockReturnValue(true)
    })

    it.each([
      { locale: 'vi' as const },
      { locale: 'en' as const },
    ])('sets request locale and renders provider shell for $locale', async ({
      locale,
    }) => {
      const { default: LocaleLayout } = await import('../layout')

      const element = await LocaleLayout({
        children: <div>Child</div>,
        params: Promise.resolve({ locale }),
      })

      const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
      render(element)
      consoleSpy.mockRestore()

      expect(mockHasLocale).toHaveBeenCalledWith(mockRouting.locales, locale)
      expect(mockSetRequestLocale).toHaveBeenCalledWith(locale)
      expect(mockNotFound).not.toHaveBeenCalled()

      type HtmlEl = React.ReactElement<{ lang?: string }>
      expect((element as HtmlEl).props.lang).toBe(locale)

      expect(screen.getByTestId('next-intl-provider')).toBeInTheDocument()
      expect(screen.getByTestId('react-query-provider')).toBeInTheDocument()
      expect(screen.getByTestId('tooltip-provider')).toBeInTheDocument()
      expect(screen.getByTestId('auth-provider')).toBeInTheDocument()
      expect(screen.getByTestId('toaster')).toBeInTheDocument()
      expect(screen.getByText('Child')).toBeInTheDocument()
    })
  })
})

describe('[locale]/not-found.tsx', () => {
  it('contract: re-exports default as a function component', async () => {
    const mod = await import('../not-found')
    expect(mod.default).toEqual(expect.any(Function))
  })
})
