import { LOCAL_STORAGE_KEYS, mergeRefs, useParams } from 'common'
import { AnimatePresence, motion } from 'framer-motion'
import { XIcon } from 'lucide-react'
import Head from 'next/head'
import { useRouter } from 'next/router'
import {
  forwardRef,
  Fragment,
  useEffect,
  useLayoutEffect,
  type PropsWithChildren,
  type ReactNode,
} from 'react'
import {
  Alert,
  AlertDescription,
  AlertTitle,
  cn,
  LogoLoader,
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
  useIsMobile,
  usePanelRef,
} from 'ui'

import { useEditorType } from '../editors/EditorsLayout.hooks'
import { useSetMainScrollContainer } from '../MainScrollContainerContext'
import { useMobileSheet } from '../Navigation/NavigationBar/MobileSheetContext'
import ProductMenuBar from '../Navigation/ProductMenuBar'
import BuildingState from './BuildingState'
import ConnectingState from './ConnectingState'
import { getSectionKeyFromPathname, MobileMenuContent } from './LayoutHeader/MobileMenuContent'
import { LoadingState } from './LoadingState'
import { ProjectPausedState } from './PausedState/ProjectPausedState'
import { PauseFailedState } from './PauseFailedState'
import { PausingState } from './PausingState'
import { ResizingState } from './ResizingState'
import RestartingState from './RestartingState'
import { RestoreFailedState } from './RestoreFailedState'
import { RestoringState } from './RestoringState'
import { UnhealthyState } from './UnhealthyState'
import { UpgradingState } from './UpgradingState'
import { CreateBranchModal } from '@/components/interfaces/BranchManagement/CreateBranchModal'
import { ProjectAPIDocs } from '@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
import { BannerFreeMicroUpgrade } from '@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade'
import { BANNER_ID, useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import PartnerIcon from '@/components/ui/PartnerIcon'
import { ResourceExhaustionWarningBanner } from '@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner'
import { useResourceWarningsQuery } from '@/data/usage/resource-warnings-query'
import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { withAuth } from '@/hooks/misc/withAuth'
import { PROJECT_STATUS } from '@/lib/constants'
import { MANAGED_BY } from '@/lib/constants/infrastructure'
import { buildStudioPageTitle } from '@/lib/page-title'
import { getPathnameWithoutQuery } from '@/lib/pathname.utils'
import { useAppStateSnapshot } from '@/state/app-state'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'

// [Joshen] This is temporary while we unblock users from managing their project
// if their project is not responding well for any reason. Eventually needs a bit of an overhaul
const routesToIgnoreProjectDetailsRequest = [
  '/project/[ref]/settings/infrastructure',
  '/project/[ref]/settings/addons',
  '/project/[ref]/settings/general',
  '/project/[ref]/database/settings',
  '/project/[ref]/storage/settings',
]

const routesToIgnoreDBConnection = [
  '/project/[ref]/branches',
  '/project/[ref]/database/backups',
  '/project/[ref]/settings',
  '/project/[ref]/functions',
  '/project/[ref]/logs',
]

const routesToIgnorePostgrestConnection = [
  '/project/[ref]/settings/general',
  '/project/[ref]/settings/infrastructure',
  '/project/[ref]/settings/addons',
  '/project/[ref]/database/settings',
  '/project/[ref]/reports',
]

const DEFAULT_PROJECT_INTEGRATION_BANNER_DISMISS_KEY =
  LOCAL_STORAGE_KEYS.PROJECT_INTEGRATION_BANNER_DISMISSED('unknown', 'unknown')

function getProjectIntegrationBannerDismissKey({
  projectRef,
  integrationSource,
}: {
  projectRef?: string
  integrationSource?: string | null
}) {
  if (!projectRef || !integrationSource) return DEFAULT_PROJECT_INTEGRATION_BANNER_DISMISS_KEY

  return LOCAL_STORAGE_KEYS.PROJECT_INTEGRATION_BANNER_DISMISSED(projectRef, integrationSource)
}

export interface ProjectLayoutProps {
  isLoading?: boolean
  isBlocking?: boolean
  product?: string
  productMenu?: ReactNode
  browserTitle?: {
    entity?: string
    section?: string
    override?: string
  }
  // Deprecated: use browserTitle.entity instead. Kept for backwards compatibility.
  selectedTable?: string
  resizableSidebar?: boolean
  productMenuClassName?: string
}

export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<ProjectLayoutProps>>(
  (
    {
      isLoading = false,
      isBlocking = true,
      product = '',
      productMenu,
      browserTitle,
      children,
      selectedTable,
      resizableSidebar = false,

      productMenuClassName,
    },
    ref
  ) => {
    const router = useRouter()
    const { data: selectedOrganization } = useSelectedOrganizationQuery()
    const { data: selectedProject } = useSelectedProjectQuery()
    const { addBanner, dismissBanner } = useBannerStack()
    const { data: resourceWarnings } = useResourceWarningsQuery({
      slug: selectedOrganization?.slug,
    })
    const projectResourceWarnings = resourceWarnings?.find(
      (w) => w.project === selectedProject?.ref
    )
    const isComputeNearExhaustion =
      !!projectResourceWarnings?.cpu_exhaustion ||
      !!projectResourceWarnings?.memory_and_swap_exhaustion ||
      !!projectResourceWarnings?.disk_space_exhaustion ||
      !!projectResourceWarnings?.disk_io_exhaustion
    const isNanoCompute = selectedProject?.infra_compute_size === 'nano'
    const showUpgradeBanner = isNanoCompute && isComputeNearExhaustion
    const [isFreeMicroUpgradeBannerDismissed] = useLocalStorageQuery(
      LOCAL_STORAGE_KEYS.FREE_MICRO_UPGRADE_BANNER_DISMISSED(selectedProject?.ref ?? ''),
      false
    )
    const [isProjectIntegrationBannerDismissed, setIsProjectIntegrationBannerDismissed] =
      useLocalStorageQuery(
        getProjectIntegrationBannerDismissKey({
          projectRef: selectedProject?.ref,
          integrationSource: selectedProject?.integration_source,
        }),
        false
      )
    const { showSidebar } = useAppStateSnapshot()
    const { setContent: setMobileSheetContent, registerOpenMenu } = useMobileSheet()

    const pathname = getPathnameWithoutQuery(router.asPath, router.pathname)
    const currentSectionKey = getSectionKeyFromPathname(pathname)

    const setMainScrollContainer = useSetMainScrollContainer()
    const combinedRef = mergeRefs(ref, setMainScrollContainer)

    const { appTitle } = useCustomContent(['app:title'])
    const brandTitle = appTitle || 'Briven'

    const isMobile = useIsMobile()

    const editor = useEditorType()
    const forceShowProductMenu = editor === undefined
    const sideBarIsOpen = (forceShowProductMenu || showSidebar) && !isMobile

    const panelRef = usePanelRef()

    const projectName = selectedProject?.name
    const organizationName = selectedOrganization?.name
    const pageTitle =
      browserTitle?.override ||
      buildStudioPageTitle({
        entity: browserTitle?.entity ?? selectedTable,
        section: browserTitle?.section,
        surface: product,
        project: projectName,
        org: organizationName,
        brand: brandTitle,
      }) ||
      brandTitle

    const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE

    const ignorePausedState =
      router.pathname === '/project/[ref]' ||
      router.pathname.includes('/project/[ref]/settings') ||
      router.pathname.includes('/project/[ref]/functions') ||
      router.pathname.includes('/project/[ref]/logs')
    const showPausedState = isPaused && !ignorePausedState
    const showStripeProjectBanner =
      selectedProject?.integration_source === 'stripe_projects' &&
      !isProjectIntegrationBannerDismissed

    useEffect(() => {
      if (!selectedProject?.ref) return
      const isProjectHomepage = router.pathname === '/project/[ref]'
      if (isProjectHomepage && showUpgradeBanner && !isFreeMicroUpgradeBannerDismissed) {
        addBanner({
          id: BANNER_ID.FREE_MICRO_UPGRADE,
          isDismissed: false,
          content: <BannerFreeMicroUpgrade />,
          priority: 2,
        })
      } else {
        dismissBanner(BANNER_ID.FREE_MICRO_UPGRADE)
      }
    }, [
      router.pathname,
      selectedProject?.ref,
      showUpgradeBanner,
      isFreeMicroUpgradeBannerDismissed,
      addBanner,
      dismissBanner,
    ])

    useLayoutEffect(() => {
      const unregister = registerOpenMenu(() => {
        setMobileSheetContent(
          <MobileMenuContent
            currentProductMenu={productMenu ?? null}
            currentProduct={product}
            currentSectionKey={currentSectionKey}
            onCloseSheet={() => setMobileSheetContent(null)}
          />
        )
      })
      return unregister
    }, [registerOpenMenu, productMenu, product, currentSectionKey, setMobileSheetContent])

    return (
      <>
        <Head>
          <title>{pageTitle}</title>
          <meta name="description" content="Briven Studio" />
        </Head>
        <div className="flex flex-row h-full w-full">
          <ResizablePanelGroup orientation="horizontal">
            {productMenu && sideBarIsOpen && (
              <ResizablePanel
                panelRef={panelRef}
                minSize={256}
                maxSize={resizableSidebar ? 512 : 256}
                defaultSize={256}
                id="panel-left"
                disabled={!resizableSidebar}
              >
                <AnimatePresence initial={false}>
                  <motion.div
                    initial={{ width: 0, opacity: 0, height: '100%' }}
                    animate={{ width: 'auto', opacity: 1, height: '100%' }}
                    exit={{ width: 0, opacity: 0, height: '100%' }}
                    className="h-full"
                    transition={{ duration: 0.12 }}
                  >
                    <MenuBarWrapper
                      isLoading={isLoading}
                      isBlocking={isBlocking}
                      productMenu={productMenu}
                    >
                      <ProductMenuBar title={product} className={productMenuClassName}>
                        {productMenu}
                      </ProductMenuBar>
                    </MenuBarWrapper>
                  </motion.div>
                </AnimatePresence>
              </ResizablePanel>
            )}
            {productMenu && sideBarIsOpen && (
              <ResizableHandle
                withHandle
                disabled={resizableSidebar ? false : true}
                className="hidden md:flex"
              />
            )}
            <ResizablePanel
              className={cn('h-full flex flex-col w-full xl:min-w-[600px] bg-dash-sidebar')}
              id="panel-project-content"
            >
              <main
                className="h-full flex flex-col flex-1 w-full overflow-y-auto overflow-x-hidden @container"
                ref={combinedRef}
              >
                {showStripeProjectBanner && (
                  <Alert
                    variant="default"
                    className="flex items-center gap-4 border-t-0 border-x-0 rounded-none"
                  >
                    <PartnerIcon
                      organization={{ managed_by: MANAGED_BY.STRIPE_PROJECTS }}
                      showTooltip={false}
                      size="medium"
                    />
                    <div className="flex-1">
                      <AlertTitle>This project is connected to Stripe</AlertTitle>
                      <AlertDescription>
                        Changes made here may affect your connected Stripe project.
                      </AlertDescription>
                    </div>
                    <ButtonTooltip
                      type="text"
                      icon={<XIcon size={14} />}
                      className="h-7 w-7 p-0"
                      onClick={() => setIsProjectIntegrationBannerDismissed(true)}
                      aria-label="Dismiss project integration banner"
                      tooltip={{ content: { text: 'Dismiss' } }}
                    />
                  </Alert>
                )}
                {showPausedState ? (
                  <div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center px-4">
                    <div className="w-full">
                      <ProjectPausedState product={product} />
                    </div>
                  </div>
                ) : (
                  <ContentWrapper isLoading={isLoading} isBlocking={isBlocking}>
                    <ResourceExhaustionWarningBanner />
                    {children}
                  </ContentWrapper>
                )}
              </main>
            </ResizablePanel>
          </ResizablePanelGroup>
        </div>
        <CreateBranchModal />
        <ProjectAPIDocs />
      </>
    )
  }
)

ProjectLayout.displayName = 'ProjectLayout'

export const ProjectLayoutWithAuth = withAuth(ProjectLayout)

interface MenuBarWrapperProps {
  isLoading: boolean
  isBlocking?: boolean
  productMenu?: ReactNode
  children: ReactNode
}

const MenuBarWrapper = ({
  isLoading,
  isBlocking = true,
  productMenu,
  children,
}: MenuBarWrapperProps) => {
  const router = useRouter()
  const { data: selectedProject } = useSelectedProjectQuery()
  const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname)

  if (!isBlocking) {
    return children
  }

  const showMenuBar =
    !requiresProjectDetails || (requiresProjectDetails && selectedProject !== undefined)

  return !isLoading && productMenu && showMenuBar ? children : null
}

interface ContentWrapperProps {
  isLoading: boolean
  isBlocking?: boolean
  children: ReactNode
}

/**
 * Check project.status to show building state or error state
 *
 * [Joshen] As of 210422: Current testing connection by pinging postgres
 * Ideally we'd have a more specific monitoring of the project such as during restarts
 * But that will come later: https://briven.slack.com/archives/C01D6TWFFFW/p1650427619665549
 *
 * Just note that this logic does not differentiate between a "restarting" state and
 * a "something is wrong and can't connect to project" state.
 *
 * [TODO] Next iteration should scrape long polling and just listen to the project's status
 */
const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapperProps) => {
  const router = useRouter()
  const { ref } = useParams()
  const state = useDatabaseSelectorStateSnapshot()
  const { data: selectedProject } = useSelectedProjectQuery()
  const isBackupsPage = router.pathname.includes('/project/[ref]/database/backups')
  const isHomePage = router.pathname === '/project/[ref]'

  const requiresDbConnection = !routesToIgnoreDBConnection.some((x) => router.pathname.includes(x))
  const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname)
  const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname)

  const isRestarting = selectedProject?.status === PROJECT_STATUS.RESTARTING
  const isResizing = selectedProject?.status === PROJECT_STATUS.RESIZING
  const isProjectUpgrading = selectedProject?.status === PROJECT_STATUS.UPGRADING
  const isProjectRestoring = selectedProject?.status === PROJECT_STATUS.RESTORING
  const isProjectRestoreFailed = selectedProject?.status === PROJECT_STATUS.RESTORE_FAILED
  const isProjectBuilding =
    selectedProject?.status === PROJECT_STATUS.COMING_UP ||
    selectedProject?.status === PROJECT_STATUS.UNKNOWN
  const isProjectPausing = selectedProject?.status === PROJECT_STATUS.PAUSING
  const isProjectPauseFailed = selectedProject?.status === PROJECT_STATUS.PAUSE_FAILED
  const isProjectUnhealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_UNHEALTHY
  const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE'

  const ignoreUnhealthyState =
    isHomePage ||
    router.pathname.includes('/project/[ref]/settings') ||
    router.pathname.includes('/project/[ref]/logs')

  const shouldRedirectToHomeForBuilding = isProjectBuilding && requiresDbConnection && !isHomePage

  // Don't show building state on the home page — it handles building state inline
  const shouldShowBuildingState = isProjectBuilding && requiresDbConnection && !isHomePage

  useEffect(() => {
    if (shouldRedirectToHomeForBuilding && ref) {
      router.replace(`/project/${ref}`)
    }
  }, [shouldRedirectToHomeForBuilding, ref, router])

  useEffect(() => {
    if (ref) state.setSelectedDatabaseId(ref)
  }, [ref])

  if (isBlocking && (isLoading || (requiresProjectDetails && selectedProject === undefined))) {
    return router.pathname.endsWith('[ref]') ? <LoadingState /> : <LogoLoader />
  }

  if (isRestarting && !isBackupsPage) {
    return <RestartingState />
  }

  if (isResizing && !isBackupsPage) {
    return <ResizingState />
  }

  if (isProjectUpgrading && !isBackupsPage) {
    return <UpgradingState />
  }

  if (isProjectPausing) {
    return <PausingState project={selectedProject} />
  }

  if (isProjectPauseFailed) {
    return <PauseFailedState />
  }

  if (isProjectUnhealthy && !ignoreUnhealthyState) {
    return <UnhealthyState />
  }

  if (requiresPostgrestConnection && isProjectOffline) {
    return <ConnectingState project={selectedProject} />
  }

  if (requiresDbConnection && isProjectRestoring) {
    return <RestoringState />
  }

  if (requiresDbConnection && isProjectRestoreFailed) {
    return <RestoreFailedState />
  }

  if (shouldRedirectToHomeForBuilding) {
    return <LogoLoader />
  }

  if (shouldShowBuildingState) {
    return <BuildingState />
  }

  return <Fragment key={selectedProject?.ref}>{children}</Fragment>
}
