import type { Monaco } from '@monaco-editor/react'
import { acceptUntrustedSql, safeSql, untrustedSql } from '@supabase/pg-meta'
import { useQueryClient } from '@tanstack/react-query'
import { useDebounce } from '@uidotdev/usehooks'
import { useParams } from 'common'
import {
  AlertCircle,
  Book,
  CheckCircle2,
  FolderOpen,
  Loader2,
  Maximize2,
  PlusIcon,
  X,
} from 'lucide-react'
import type { editor as MonacoEditor } from 'monaco-editor'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import {
  Button,
  cn,
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
  KeyboardShortcut,
  Popover,
  PopoverContent,
  PopoverTrigger,
  SQL_ICON,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { CodeBlock } from 'ui-patterns/CodeBlock'

import { containsUnknownFunction, isReadOnlySelect } from '../AIAssistantPanel/AIAssistant.utils'
import { AIEditor } from '../AIEditor'
import { ButtonTooltip } from '../ButtonTooltip'
import { SqlWarningAdmonition } from '../SqlWarningAdmonition'
import { formatSqlError } from './EditorPanel.utils'
import { SaveSnippetDialog } from './SaveSnippetDialog'
import { isExplainQuery } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
import { generateSnippetTitle } from '@/components/interfaces/SQLEditor/SQLEditor.constants'
import {
  createSqlSnippetSkeletonV2,
  suffixWithLimit,
} from '@/components/interfaces/SQLEditor/SQLEditor.utils'
import { useAddDefinitions } from '@/components/interfaces/SQLEditor/useAddDefinitions'
import Results from '@/components/interfaces/SQLEditor/UtilityPanel/Results'
import { SqlRunButton } from '@/components/interfaces/SQLEditor/UtilityPanel/RunButton'
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { useContentIdQuery } from '@/data/content/content-id-query'
import { useContentQuery, type Content } from '@/data/content/content-query'
import { useContentUpsertMutation } from '@/data/content/content-upsert-mutation'
import { contentKeys } from '@/data/content/keys'
import { useExecuteSqlMutation } from '@/data/sql/execute-sql-mutation'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { BASE_PATH } from '@/lib/constants'
import { useProfile } from '@/lib/profile'
import { editorPanelState, useEditorPanelStateSnapshot } from '@/state/editor-panel-state'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useIsShortcutEnabled } from '@/state/shortcuts/useIsShortcutEnabled'
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'

export const EditorPanel = () => {
  const {
    value,
    templates,
    results,
    error,
    initialPrompt,
    onChange,
    setValue,
    setTemplates,
    setResults,
    setError,
    activeSnippetId,
    pendingReset,
  } = useEditorPanelStateSnapshot()
  const { profile } = useProfile()
  const { closeSidebar } = useSidebarManagerSnapshot()
  const sqlEditorSnap = useSqlEditorV2StateSnapshot()
  const queryClient = useQueryClient()

  const [activeSnippet, setActiveSnippet] = useState<Extract<Content, { type: 'sql' }> | null>(null)
  const [isEditingTitle, setIsEditingTitle] = useState(false)
  const [titleInput, setTitleInput] = useState('')
  const titleInputRef = useRef<HTMLInputElement>(null)
  const editorRef = useRef<MonacoEditor.IStandaloneCodeEditor | null>(null)
  const shouldRefocusAfterRunRef = useRef(false)
  const [monaco, setMonaco] = useState<Monaco | null>(null)

  useAddDefinitions('', monaco)

  const label = activeSnippet?.name ?? 'SQL Editor'

  const commitRename = () => {
    const newName = titleInput.trim()
    if (!newName || !activeSnippet) {
      setIsEditingTitle(false)
      return
    }
    setActiveSnippet({ ...activeSnippet, name: newName })
    setIsEditingTitle(false)
  }
  const isInlineEditorHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.INLINE_EDITOR_TOGGLE)
  const isAIAssistantHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE)

  const currentValue = value || safeSql``

  const { ref } = useParams()
  const router = useRouter()
  const { data: project } = useSelectedProjectQuery()
  const { data: org } = useSelectedOrganizationQuery()

  const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>()
  const [showResults, setShowResults] = useState(true)
  const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false)
  const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle')
  const saveStatusTimerRef = useRef<ReturnType<typeof setTimeout>>(null)
  const originalSnippetRef = useRef<{ sql: string; name: string } | null>(null)

  const refocusEditor = () => {
    requestAnimationFrame(() => {
      setTimeout(() => editorRef.current?.focus(), 0)
    })
  }

  const clearPendingRunRefocus = () => {
    shouldRefocusAfterRunRef.current = false
  }

  const refocusEditorAfterRunIfNeeded = () => {
    if (!shouldRefocusAfterRunRef.current) return

    shouldRefocusAfterRunRef.current = false
    refocusEditor()
  }

  const showSaveSuccess = () => {
    setSaveStatus('success')
    if (saveStatusTimerRef.current) {
      clearTimeout(saveStatusTimerRef.current)
    }
    saveStatusTimerRef.current = setTimeout(() => setSaveStatus('idle'), 2000)
  }
  const [isTemplatesOpen, setIsTemplatesOpen] = useState(false)
  const [isSnippetsOpen, setIsSnippetsOpen] = useState(false)
  const [snippetSearch, setSnippetSearch] = useState('')
  const debouncedSnippetSearch = useDebounce(snippetSearch, 300)

  const { data: snippetsData, isLoading: isLoadingSnippets } = useContentQuery(
    { projectRef: ref, type: 'sql', name: debouncedSnippetSearch || undefined },
    { enabled: isSnippetsOpen }
  )

  const { data: snippetById } = useContentIdQuery(
    { projectRef: ref, id: activeSnippetId ?? undefined },
    { enabled: !!activeSnippetId }
  )

  useEffect(() => {
    if (!pendingReset) return
    setActiveSnippet(null)
    setIsEditingTitle(false)
    originalSnippetRef.current = null
    editorPanelState.pendingReset = false
  }, [pendingReset, setActiveSnippet, setIsEditingTitle])

  useEffect(() => {
    if (!snippetById || !activeSnippetId) return
    const sqlSnippet = snippetById as unknown as Extract<Content, { type: 'sql' }>
    const sql = sqlSnippet.content.unchecked_sql ?? safeSql``
    setValue(sql)
    setActiveSnippet(sqlSnippet)
    originalSnippetRef.current = { sql, name: sqlSnippet.name }
    editorPanelState.setActiveSnippetId(null)
  }, [snippetById, activeSnippetId, setValue, setActiveSnippet])

  const { header: errorHeader, lines: errorContent } = error
    ? formatSqlError(error)
    : { header: undefined, lines: [] as string[] }

  const { mutate: upsertContent, isPending: isUpserting } = useContentUpsertMutation({
    onSuccess: (_, vars) => {
      if (vars.payload.id && ref) {
        queryClient.invalidateQueries({ queryKey: contentKeys.resource(ref, vars.payload.id) })
      }
      originalSnippetRef.current = { sql: currentValue, name: vars.payload.name }
      showSaveSuccess()
    },
    onError: () => setSaveStatus('error'),
  })

  const { mutate: executeSql, isPending: isExecuting } = useExecuteSqlMutation({
    onSuccess: async (res) => {
      setResults(res.result)
      setError(undefined)
      refocusEditorAfterRunIfNeeded()
    },
    onError: (mutationError) => {
      setError(mutationError)
      setResults([])
      refocusEditorAfterRunIfNeeded()
    },
  })

  const onExecuteSql = (skipValidation = false) => {
    setError(undefined)
    setShowWarning(undefined)
    setResults(undefined)

    if (currentValue.length === 0) {
      clearPendingRunRefocus()
      return
    }

    if (!skipValidation) {
      const isReadOnlySelectSQL = isReadOnlySelect(currentValue)
      if (!isReadOnlySelectSQL) {
        const hasUnknownFunctions = containsUnknownFunction(currentValue)
        setShowWarning(hasUnknownFunctions ? 'hasUnknownFunctions' : 'hasWriteOperation')
        return
      }
    }

    executeSql({
      sql: suffixWithLimit(acceptUntrustedSql(currentValue), 100),
      projectRef: project?.ref,
      connectionString: project?.connectionString,
      isStatementTimeoutDisabled: true,
      handleError: (executeError) => {
        throw executeError
      },
      contextualInvalidation: true,
    })
  }

  // Check if this is an EXPLAIN query result
  const isValidExplainQuery = isExplainQuery(results ?? [])

  const handleChange = (value: string) => {
    setValue(untrustedSql(value))
    onChange?.(untrustedSql(value))
  }

  const onSelectTemplate = (content: string) => {
    handleChange(content)
    setIsTemplatesOpen(false)
  }

  const onExecuteSqlFromButton = () => {
    shouldRefocusAfterRunRef.current = true
    onExecuteSql()
    refocusEditor()
  }

  const handleClosePanel = () => {
    clearPendingRunRefocus()
    closeSidebar(SIDEBAR_KEYS.EDITOR_PANEL)
    setTemplates([])
    setError(undefined)
    setShowWarning(undefined)
    setShowResults(true)
    setActiveSnippet(null)
    setIsEditingTitle(false)
    editorPanelState.setActiveSnippetId(null)
  }

  return (
    <div className="flex h-full flex-col bg-background">
      <div className="border-b border-b-muted flex items-center justify-between gap-x-4 pl-4 pr-3 h-(--header-height)">
        {isEditingTitle ? (
          <input
            ref={titleInputRef}
            value={titleInput}
            onChange={(e) => setTitleInput(e.target.value)}
            onBlur={commitRename}
            onKeyDown={(e) => {
              if (e.key === 'Enter') commitRename()
              if (e.key === 'Escape') setIsEditingTitle(false)
            }}
            className="text-xs bg-transparent border-b border-foreground-lighter outline-hidden w-48 py-0.5"
            autoFocus
          />
        ) : (
          <div
            className={cn('text-xs', activeSnippet && 'cursor-pointer hover:text-foreground')}
            onClick={() => {
              if (!activeSnippet) return
              setTitleInput(activeSnippet.name)
              setIsEditingTitle(true)
            }}
          >
            {label}
          </div>
        )}
        <div className="flex items-center gap-2">
          {activeSnippet && (
            <ButtonTooltip
              size="tiny"
              type="text"
              className="w-7 h-7 p-0"
              icon={<PlusIcon size={14} />}
              tooltip={{ content: { side: 'bottom', text: 'New snippet' } }}
              onClick={() => editorPanelState.openAsNew()}
            />
          )}
          <Popover open={isSnippetsOpen} onOpenChange={setIsSnippetsOpen}>
            <PopoverTrigger asChild>
              <ButtonTooltip
                size="tiny"
                type="text"
                role="combobox"
                aria-expanded={isSnippetsOpen}
                className="w-7 h-7 p-0"
                icon={<FolderOpen size={14} />}
                tooltip={{
                  content: {
                    side: 'bottom',
                    text: 'Open snippet',
                  },
                }}
              ></ButtonTooltip>
            </PopoverTrigger>
            <PopoverContent align="end" className="w-[300px] p-0">
              <Command shouldFilter={false}>
                <CommandInput
                  placeholder="Search snippets..."
                  value={snippetSearch}
                  onValueChange={setSnippetSearch}
                />
                <CommandList>
                  {isLoadingSnippets ? (
                    <div className="py-6 text-center text-sm text-foreground-light">
                      Loading snippets...
                    </div>
                  ) : (
                    <CommandEmpty>No snippets found.</CommandEmpty>
                  )}
                  <CommandGroup>
                    {(snippetsData?.content ?? []).map((snippet) => (
                      <CommandItem
                        key={snippet.id}
                        value={snippet.id}
                        className="cursor-pointer"
                        onSelect={() => {
                          if (snippet.id) editorPanelState.setActiveSnippetId(snippet.id)
                          setIsSnippetsOpen(false)
                          setSnippetSearch('')
                        }}
                      >
                        {snippet.name}
                      </CommandItem>
                    ))}
                  </CommandGroup>
                </CommandList>
              </Command>
            </PopoverContent>
          </Popover>
          {templates.length > 0 && (
            <Popover open={isTemplatesOpen} onOpenChange={setIsTemplatesOpen}>
              <PopoverTrigger asChild>
                <Button
                  size="tiny"
                  type="default"
                  role="combobox"
                  className="mr-2"
                  aria-expanded={isTemplatesOpen}
                  icon={<Book size={14} />}
                >
                  Templates
                </Button>
              </PopoverTrigger>
              <PopoverContent align="end" className="w-[300px] p-0">
                <Command>
                  <CommandInput placeholder="Search templates..." />
                  <CommandList>
                    <CommandEmpty>No templates found.</CommandEmpty>
                    <CommandGroup>
                      {templates.map((template) => (
                        <HoverCard key={template.name}>
                          <HoverCardTrigger asChild>
                            <CommandItem
                              value={template.name}
                              onSelect={() => onSelectTemplate(template.content)}
                              className="cursor-pointer"
                            >
                              <div className="flex items-center gap-3">
                                <SQL_ICON
                                  size={16}
                                  className={cn(
                                    'w-5 h-5 flex-0 mr-2 transition-colors fill-foreground-muted'
                                  )}
                                />
                                <div className="flex-1">
                                  <h4 className="text-foreground flex-1">{template.name}</h4>
                                  <p className="text-xs text-foreground-light">
                                    {template.description}
                                  </p>
                                </div>
                              </div>
                            </CommandItem>
                          </HoverCardTrigger>
                          <HoverCardContent side="left" className="w-[500px] p-0">
                            <CodeBlock
                              language="sql"
                              className="language-sql border-none"
                              hideLineNumbers
                              value={template.content}
                            />
                          </HoverCardContent>
                        </HoverCard>
                      ))}
                    </CommandGroup>
                  </CommandList>
                </Command>
              </PopoverContent>
            </Popover>
          )}
          <ButtonTooltip
            type="text"
            className="w-7 h-7 p-0"
            icon={<Maximize2 strokeWidth={1.5} />}
            tooltip={{
              content: {
                side: 'bottom',
                text: 'Expand to SQL editor',
              },
            }}
            onClick={() => {
              if (!ref) return console.error('Project ref is required')

              if (!project) {
                console.error('Project is required')
                return
              }
              if (!profile) {
                console.error('Profile is required')
                return
              }

              const snippet = createSqlSnippetSkeletonV2({
                name: generateSnippetTitle(),
                sql: currentValue,
                owner_id: profile.id,
                project_id: project.id,
              })

              sqlEditorSnap.addSnippet({ projectRef: ref, snippet })
              sqlEditorSnap.addNeedsSaving(snippet.id)

              router.push(`/project/${ref}/sql/${snippet.id}`)
              handleClosePanel()
            }}
          />

          <ButtonTooltip
            type="text"
            className="w-7 h-7 p-0"
            onClick={handleClosePanel}
            icon={<X strokeWidth={1.5} />}
            tooltip={{
              content: {
                side: 'bottom',
                text: (
                  <div className="flex items-center gap-4">
                    <span>Close Editor</span>
                    {isInlineEditorHotkeyEnabled && <KeyboardShortcut keys={['Meta', 'e']} />}
                  </div>
                ),
              },
            }}
          />
        </div>
      </div>

      <div className="flex-1 overflow-hidden flex flex-col h-full">
        <div className="flex-1 min-h-0 relative [&_.monaco-editor]:!bg [&_.monaco-editor_.margin]:!bg [&_.monaco-editor_.monaco-editor-background]:!bg">
          <AIEditor
            autoFocus
            language="pgsql"
            value={currentValue}
            onChange={handleChange}
            onMount={(editor, m) => {
              editorRef.current = editor
              setMonaco(m)
            }}
            aiEndpoint={`${BASE_PATH}/api/ai/code/complete`}
            aiMetadata={{
              projectRef: project?.ref,
              connectionString: project?.connectionString,
              orgSlug: org?.slug,
              language: 'sql',
            }}
            initialPrompt={initialPrompt}
            options={{
              tabSize: 2,
              fontSize: 13,
              minimap: { enabled: false },
              wordWrap: 'on',
              lineNumbers: 'on',
              folding: false,
              padding: { top: 16 },
              lineNumbersMinChars: 3,
            }}
            executeQuery={onExecuteSql}
            onClose={handleClosePanel}
            closeShortcutEnabled={isInlineEditorHotkeyEnabled}
            openAIAssistantShortcutEnabled={isAIAssistantHotkeyEnabled}
          />
        </div>

        {error !== undefined && (
          <div className="shrink-0">
            <Admonition
              type="warning"
              className="rounded-none border-x-0 border-b-0 [&>div>div>pre]:text-sm [&>div]:flex [&>div]:flex-col [&>div]:gap-y-2"
              title={errorHeader || 'Error running SQL query'}
              description={
                <div>
                  {errorContent.length > 0 ? (
                    errorContent.map((errorText: string, i: number) => (
                      <pre key={`err-${i}`} className="font-mono text-xs whitespace-pre-wrap">
                        {errorText}
                      </pre>
                    ))
                  ) : (
                    <p className="font-mono text-xs">{error?.error}</p>
                  )}
                </div>
              }
            />
          </div>
        )}

        {showWarning && (
          <SqlWarningAdmonition
            className="border-t"
            warningType={showWarning}
            onCancel={() => {
              clearPendingRunRefocus()
              setShowWarning(undefined)
              refocusEditor()
            }}
            onConfirm={() => {
              shouldRefocusAfterRunRef.current = true
              setShowWarning(undefined)
              onExecuteSql(true)
              refocusEditor()
            }}
          />
        )}

        {results !== undefined && results.length > 0 && (
          <div
            className={cn(
              `shrink-0 flex flex-col`,
              isValidExplainQuery ? 'max-h-[600px]' : 'max-h-72',
              showResults && 'h-full'
            )}
          >
            {showResults && (
              <div className="border-t flex-1 overflow-hidden">
                <Results rows={results} />
              </div>
            )}
            <div className="text-xs text-foreground-light border-t py-2 px-5 flex items-center justify-between">
              <span className="font-mono">
                {results.length} rows{results.length >= 100 && ` (Limited to only 100 rows)`}
              </span>
              <Button
                size="tiny"
                type="default"
                className="ml-2"
                onClick={() => setShowResults((prev) => !prev)}
              >
                {showResults ? 'Hide Results' : 'Show Results'}
              </Button>
            </div>
          </div>
        )}
        {results !== undefined && results.length === 0 && !error && (
          <div className="shrink-0">
            <p className="text-xs text-foreground-light font-mono py-2 px-5">
              Success. No rows returned.
            </p>
          </div>
        )}

        <div className="relative shrink-0 flex items-center gap-2 justify-end px-5 py-4 w-full border-t">
          {(isUpserting || saveStatus !== 'idle') && (
            <div
              className={cn(
                'absolute left-0 flex items-center gap-2 px-5 py-3 text-xs',
                saveStatus === 'success' && 'text-brand-600',
                saveStatus === 'error' && 'text-warning',
                saveStatus === 'idle' && 'text-foreground-light'
              )}
            >
              {isUpserting && <Loader2 size={13} className="animate-spin" />}
              {saveStatus === 'success' && <CheckCircle2 size={13} />}
              {saveStatus === 'error' && <AlertCircle size={13} />}
              <span>
                {isUpserting
                  ? 'Saving...'
                  : saveStatus === 'success'
                    ? 'Snippet updated'
                    : 'Failed to save snippet'}
              </span>
            </div>
          )}
          <Button
            type="default"
            size="tiny"
            disabled={
              !currentValue ||
              isExecuting ||
              isUpserting ||
              (!!activeSnippet &&
                currentValue === originalSnippetRef.current?.sql &&
                activeSnippet.name === originalSnippetRef.current?.name)
            }
            onClick={() => {
              if (!ref || !profile || !project) return

              if (activeSnippet) {
                setSaveStatus('idle')
                upsertContent({
                  projectRef: ref,
                  payload: {
                    id: activeSnippet.id,
                    type: 'sql',
                    name: activeSnippet.name,
                    description: activeSnippet.description ?? '',
                    visibility: activeSnippet.visibility ?? 'user',
                    project_id: project.id,
                    owner_id: profile.id,
                    content: {
                      ...activeSnippet.content,
                      sql: currentValue,
                    },
                  },
                })
              } else {
                setIsSaveDialogOpen(true)
              }
            }}
          >
            {activeSnippet ? 'Update snippet' : 'Save as snippet'}
          </Button>
          <SqlRunButton
            isDisabled={isExecuting}
            isExecuting={isExecuting}
            onClick={onExecuteSqlFromButton}
          />
        </div>
      </div>
      <SaveSnippetDialog
        open={isSaveDialogOpen}
        sql={currentValue}
        onOpenChange={setIsSaveDialogOpen}
        onSave={(name) => {
          if (!ref || !profile || !project) return
          const snippet = createSqlSnippetSkeletonV2({
            name,
            sql: currentValue,
            owner_id: profile.id,
            project_id: project.id,
          })
          sqlEditorSnap.addSnippet({ projectRef: ref, snippet })
          sqlEditorSnap.addNeedsSaving(snippet.id)
          setActiveSnippet(snippet as unknown as Extract<Content, { type: 'sql' }>)
          originalSnippetRef.current = { sql: currentValue, name }
          showSaveSuccess()
        }}
      />
    </div>
  )
}

export default EditorPanel
