import { render, screen, waitFor } from '@testing-library/react'
import { LOCAL_STORAGE_KEYS } from 'common'
import type { ReactNode } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { MobileSheetProvider } from '../Navigation/NavigationBar/MobileSheetContext'
import { ProjectLayout } from './index'
import { STUDIO_PAGE_TITLE_SEPARATOR } from '@/lib/page-title'

const { mockRouter, mockSetSelectedDatabaseId, mockSetMobileMenuOpen } = vi.hoisted(() => ({
  mockRouter: {
    pathname: '/project/[ref]/observability/query-performance',
    asPath: '/project/default/observability/query-performance',
    push: vi.fn(),
    replace: vi.fn(),
  },
  mockSetSelectedDatabaseId: vi.fn(),
  mockSetMobileMenuOpen: vi.fn(),
}))

const {
  mockAddBanner,
  mockDismissBanner,
  mockProjectState,
  mockResourceWarningsState,
  mockBannerDismissedState,
  mockUseLocalStorageQuery,
} = vi.hoisted(() => ({
  mockAddBanner: vi.fn(),
  mockDismissBanner: vi.fn(),
  mockProjectState: {
    current: {
      ref: 'default',
      name: 'Project 1',
      status: 'ACTIVE_HEALTHY',
      postgrestStatus: 'ONLINE',
      infra_compute_size: undefined as string | undefined,
      integration_source: null as string | null,
    },
  },
  mockResourceWarningsState: { current: undefined as any[] | undefined },
  mockBannerDismissedState: { current: false },
  mockUseLocalStorageQuery: vi.fn(),
}))

vi.mock('next/router', () => ({
  useRouter: () => mockRouter,
}))

vi.mock('next/head', async () => {
  const React = await import('react')

  const Head = ({ children }: { children?: ReactNode }) => {
    React.useEffect(() => {
      const titleElement = React.Children.toArray(children).find(
        (child) => React.isValidElement(child) && child.type === 'title'
      )

      if (!React.isValidElement<{ children: ReactNode }>(titleElement)) return

      const titleText = React.Children.toArray(titleElement.props.children).join('')
      document.title = titleText
    }, [children])

    return null
  }

  return { default: Head }
})

vi.mock('common', () => ({
  useParams: () => ({ ref: 'default' }),
  mergeRefs:
    (..._refs: any[]) =>
    (_value: unknown) => {},
  IS_PLATFORM: false,
  LOCAL_STORAGE_KEYS: {
    FREE_MICRO_UPGRADE_BANNER_DISMISSED: (ref: string) =>
      `free-micro-upgrade-banner-dismissed-${ref}`,
    PROJECT_INTEGRATION_BANNER_DISMISSED: (ref: string, integrationSource: string) =>
      `project-integration-banner-dismissed-${ref}-${integrationSource}`,
  },
  isFeatureEnabled: () => false,
}))

vi.mock('framer-motion', () => ({
  AnimatePresence: ({ children }: { children: ReactNode }) => <>{children}</>,
  motion: {
    div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
    create: (Component: any) => Component,
  },
}))

vi.mock('ui', () => ({
  cn: (...classes: Array<string | false | null | undefined>) => classes.filter(Boolean).join(' '),
  Alert: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  AlertDescription: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  AlertTitle: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  CommandInput: { displayName: 'CommandInput' },
  Command: { displayName: 'Command' },
  CommandGroup: { displayName: 'CommandGroup' },
  CommandItem: { displayName: 'CommandItem' },
  CommandList: { displayName: 'CommandList' },
  LogoLoader: () => <div data-testid="logo-loader" />,
  ResizableHandle: (props: any) => <div {...props} />,
  ResizablePanel: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  ResizablePanelGroup: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  Sidebar: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  SidebarContent: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  SidebarFooter: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  SidebarGroup: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  SidebarMenu: ({ children, ...props }: any) => <div {...props}>{children}</div>,
  SidebarMenuButton: (props: any) => <div {...props} />,
  SidebarMenuItem: (props: any) => <div {...props} />,
  useIsMobile: () => false,
  usePanelRef: () => undefined,
  useSidebar: () => ({ setOpen: vi.fn() }),
}))

vi.mock('ui-patterns/MobileSheetNav/MobileSheetNav', () => ({
  default: ({ children }: { children: ReactNode }) => <>{children}</>,
}))

vi.mock('../editors/EditorsLayout.hooks', () => ({
  useEditorType: () => undefined,
}))

vi.mock('../MainScrollContainerContext', () => ({
  useSetMainScrollContainer: () => () => {},
}))

vi.mock('./BuildingState', () => ({ default: () => null }))
vi.mock('./ConnectingState', () => ({ default: () => null }))
vi.mock('./LoadingState', () => ({ LoadingState: () => null }))
vi.mock('./PausedState/ProjectPausedState', () => ({ ProjectPausedState: () => null }))
vi.mock('./PauseFailedState', () => ({ PauseFailedState: () => null }))
vi.mock('./PausingState', () => ({ PausingState: () => null }))
vi.mock('./ProductMenuBar', () => ({
  default: ({ children }: { children: ReactNode }) => <>{children}</>,
}))
vi.mock('./ResizingState', () => ({ ResizingState: () => null }))
vi.mock('./RestartingState', () => ({ default: () => null }))
vi.mock('./RestoreFailedState', () => ({ RestoreFailedState: () => null }))
vi.mock('./RestoringState', () => ({ RestoringState: () => null }))
vi.mock('./UpgradingState', () => ({ UpgradingState: () => null }))

vi.mock('@/components/interfaces/BranchManagement/CreateBranchModal', () => ({
  CreateBranchModal: () => null,
}))
vi.mock('@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs', () => ({
  ProjectAPIDocs: () => null,
}))
vi.mock('@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner', () => ({
  ResourceExhaustionWarningBanner: () => null,
}))
vi.mock('@/components/ui/ButtonTooltip', () => ({
  ButtonTooltip: ({ children, ...props }: any) => <button {...props}>{children}</button>,
}))
vi.mock('@/components/ui/PartnerIcon', () => ({
  default: () => <div data-testid="partner-icon" />,
}))

vi.mock('@/hooks/custom-content/useCustomContent', () => ({
  useCustomContent: () => ({ appTitle: 'Briven' }),
}))

vi.mock('@/hooks/misc/useLocalStorage', () => ({
  useLocalStorageQuery: (...args: unknown[]) => mockUseLocalStorageQuery(...args),
}))

vi.mock('@/components/ui/BannerStack/BannerStackProvider', () => ({
  BANNER_ID: { FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner' },
  useBannerStack: () => ({
    addBanner: mockAddBanner,
    dismissBanner: mockDismissBanner,
    banners: [],
  }),
}))

vi.mock('@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade', () => ({
  BannerFreeMicroUpgrade: () => null,
}))

vi.mock('@/data/usage/resource-warnings-query', () => ({
  useResourceWarningsQuery: () => ({ data: mockResourceWarningsState.current }),
}))

vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
  useSelectedOrganizationQuery: () => ({
    data: { name: 'Organization 1', slug: 'org-1' },
  }),
}))

vi.mock('@/hooks/misc/useSelectedProject', () => ({
  useSelectedProjectQuery: () => ({ data: mockProjectState.current }),
}))

vi.mock('@/hooks/misc/withAuth', () => ({
  withAuth: (Component: any) => Component,
}))

vi.mock('@/hooks/ui/useFlag', () => ({
  usePHFlag: () => undefined,
}))

vi.mock('@/state/app-state', () => ({
  useAppStateSnapshot: () => ({
    mobileMenuOpen: false,
    showSidebar: false,
    setMobileMenuOpen: mockSetMobileMenuOpen,
  }),
}))

vi.mock('@/state/database-selector', () => ({
  useDatabaseSelectorStateSnapshot: () => ({
    setSelectedDatabaseId: mockSetSelectedDatabaseId,
  }),
}))

const renderLayout = () =>
  render(
    <MobileSheetProvider>
      <ProjectLayout product="Database" isBlocking={false}>
        <div />
      </ProjectLayout>
    </MobileSheetProvider>
  )

describe('ProjectLayout title', () => {
  beforeEach(() => {
    mockRouter.pathname = '/project/[ref]/observability/query-performance'
    mockRouter.asPath = '/project/default/observability/query-performance'
    document.title = ''
    mockProjectState.current = {
      ref: 'default',
      name: 'Project 1',
      status: 'ACTIVE_HEALTHY',
      postgrestStatus: 'ONLINE',
      infra_compute_size: undefined,
      integration_source: null,
    }
    mockBannerDismissedState.current = false
    mockUseLocalStorageQuery.mockImplementation(() => [mockBannerDismissedState.current, vi.fn()])
  })

  afterEach(() => {
    vi.clearAllMocks()
    document.title = ''
  })

  it('sets a composed document title and deduplicates identical section/surface labels', async () => {
    render(
      <MobileSheetProvider>
        <ProjectLayout browserTitle={{ section: 'Settings' }} product="Settings" isBlocking={false}>
          <div>Page Content</div>
        </ProjectLayout>
      </MobileSheetProvider>
    )

    await waitFor(() => {
      expect(document.title).toBe(
        ['Settings', 'Project 1', 'Organization 1', 'Briven'].join(STUDIO_PAGE_TITLE_SEPARATOR)
      )
    })
  })

  it('prefers entity-first browserTitle metadata when provided', async () => {
    render(
      <MobileSheetProvider>
        <ProjectLayout
          product="Database"
          browserTitle={{ entity: 'users', section: 'Tables' }}
          isBlocking={false}
        >
          <div>Page Content</div>
        </ProjectLayout>
      </MobileSheetProvider>
    )

    await waitFor(() => {
      expect(document.title).toBe(
        ['users', 'Tables', 'Database', 'Project 1', 'Organization 1', 'Briven'].join(
          STUDIO_PAGE_TITLE_SEPARATOR
        )
      )
    })
  })

  it('renders the Stripe project banner across project surfaces when the selected project is Stripe-connected', () => {
    mockProjectState.current = {
      ...mockProjectState.current,
      integration_source: 'stripe_projects',
    }

    renderLayout()

    expect(screen.getByText('This project is connected to Stripe')).toBeTruthy()
    expect(
      screen.getByText('Changes made here may affect your connected Stripe project.')
    ).toBeTruthy()
    expect(screen.getByTestId('partner-icon')).toBeTruthy()
  })

  it('uses a project-specific dismiss key for the Stripe project banner', () => {
    mockProjectState.current = {
      ...mockProjectState.current,
      integration_source: 'stripe_projects',
    }

    renderLayout()

    expect(mockUseLocalStorageQuery).toHaveBeenCalledWith(
      LOCAL_STORAGE_KEYS.PROJECT_INTEGRATION_BANNER_DISMISSED('default', 'stripe_projects'),
      false
    )
  })
})

describe('FREE_MICRO_UPGRADE banner', () => {
  beforeEach(() => {
    mockRouter.pathname = '/project/[ref]'
    mockRouter.asPath = '/project/default'
    mockProjectState.current = {
      ref: 'default',
      name: 'Project 1',
      status: 'ACTIVE_HEALTHY',
      postgrestStatus: 'ONLINE',
      infra_compute_size: 'nano',
      integration_source: null,
    }
    mockResourceWarningsState.current = [
      {
        project: 'default',
        cpu_exhaustion: true,
        memory_and_swap_exhaustion: false,
        disk_space_exhaustion: false,
      },
    ]
    mockBannerDismissedState.current = false
  })

  afterEach(() => {
    vi.clearAllMocks()
    mockRouter.pathname = '/project/[ref]/observability/query-performance'
    mockRouter.asPath = '/project/default/observability/query-performance'
    mockProjectState.current = {
      ref: 'default',
      name: 'Project 1',
      status: 'ACTIVE_HEALTHY',
      postgrestStatus: 'ONLINE',
      infra_compute_size: undefined,
      integration_source: null,
    }
    mockResourceWarningsState.current = undefined
    mockBannerDismissedState.current = false
  })

  it('calls addBanner when project is nano and compute is near exhaustion', async () => {
    renderLayout()

    await waitFor(() => {
      expect(mockAddBanner).toHaveBeenCalledWith(
        expect.objectContaining({ id: 'free-micro-upgrade-banner' })
      )
    })
  })

  it('calls dismissBanner when banner was previously dismissed', async () => {
    mockBannerDismissedState.current = true

    renderLayout()

    await waitFor(() => {
      expect(mockDismissBanner).toHaveBeenCalledWith('free-micro-upgrade-banner')
    })
    expect(mockAddBanner).not.toHaveBeenCalled()
  })

  it('calls dismissBanner when compute warnings are cleared', async () => {
    mockResourceWarningsState.current = [
      {
        project: 'default',
        cpu_exhaustion: false,
        memory_and_swap_exhaustion: false,
        disk_space_exhaustion: false,
      },
    ]

    renderLayout()

    await waitFor(() => {
      expect(mockDismissBanner).toHaveBeenCalledWith('free-micro-upgrade-banner')
    })
    expect(mockAddBanner).not.toHaveBeenCalled()
  })

  it('calls dismissBanner when project is not nano compute', async () => {
    mockProjectState.current = { ...mockProjectState.current, infra_compute_size: 'micro' }

    renderLayout()

    await waitFor(() => {
      expect(mockDismissBanner).toHaveBeenCalledWith('free-micro-upgrade-banner')
    })
    expect(mockAddBanner).not.toHaveBeenCalled()
  })
})
