'use client'

import { useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { ChevronDown, Plus } from 'lucide-react'
import { useParams } from 'next/navigation'
import { useQueryState } from 'nuqs'
import { Badge, Button, Chip, ChipConfirmModal, Tooltip } from '@/components/emcn'
import { ArrowLeft } from '@/components/emcn/icons'
import { requestJson } from '@/lib/api/client/request'
import { getWorkflowStateContract } from '@/lib/api/contracts/workflows'
import { cn } from '@/lib/core/utils/cn'
import {
  getIssueBadgeLabel,
  getIssueBadgeVariant,
  getMcpToolIssue,
  type McpToolIssue,
} from '@/lib/mcp/tool-validation'
import type { McpTransport } from '@/lib/mcp/types'
import {
  mcpServerIdParam,
  mcpServerIdUrlKeys,
} from '@/app/workspace/[workspaceId]/settings/[section]/search-params'
import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu'
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
import { useMcpOauthPopup } from '@/hooks/mcp/use-mcp-oauth-popup'
import {
  type McpServer,
  type McpTool,
  useAllowedMcpDomains,
  useCreateMcpServer,
  useDeleteMcpServer,
  useForceRefreshMcpTools,
  useMcpServers,
  useMcpToolsQuery,
  useRefreshMcpServer,
  useStoredMcpTools,
  useUpdateMcpServer,
} from '@/hooks/queries/mcp'
import { useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { BlockState } from '@/stores/workflows/workflow/types'
import { McpServerFormModal } from './components'

const logger = createLogger('McpSettings')

function formatTransportLabel(transport: string): string {
  return transport
    .split('-')
    .map((word) =>
      ['http', 'sse', 'stdio'].includes(word.toLowerCase())
        ? word.toUpperCase()
        : word.charAt(0).toUpperCase() + word.slice(1)
    )
    .join('-')
}

function formatToolsLabel(tools: McpTool[], connectionStatus?: string): string {
  if (connectionStatus === 'error') {
    return 'Unable to connect'
  }
  const count = tools.length
  const plural = count !== 1 ? 's' : ''
  const names = count > 0 ? `: ${tools.map((t) => t.name).join(', ')}` : ''
  return `${count} tool${plural}${names}`
}

interface ServerListItemProps {
  server: McpServer
  tools: McpTool[]
  isDeleting: boolean
  isLoadingTools?: boolean
  isRefreshing?: boolean
  onRemove: () => void
  onViewDetails: () => void
}

function ServerListItem({
  server,
  tools,
  isDeleting,
  isLoadingTools = false,
  isRefreshing = false,
  onRemove,
  onViewDetails,
}: ServerListItemProps) {
  const transportLabel = formatTransportLabel(server.transport || 'http')
  const toolsLabel = formatToolsLabel(tools, server.connectionStatus)
  const isError = server.connectionStatus === 'error'

  return (
    <div className='flex items-center justify-between gap-3'>
      <div className='flex min-w-0 flex-col justify-center gap-[1px]'>
        <div className='flex items-center gap-1.5'>
          <span className='max-w-[200px] truncate text-[14px] text-[var(--text-body)]'>
            {server.name || 'Unnamed Server'}
          </span>
          <span className='text-[12px] text-[var(--text-muted)]'>({transportLabel})</span>
        </div>
        <p
          className={cn(
            'truncate text-sm',
            isError ? 'text-[var(--text-error)]' : 'text-[var(--text-muted)]'
          )}
        >
          {isRefreshing
            ? 'Refreshing...'
            : isLoadingTools && tools.length === 0
              ? 'Loading...'
              : toolsLabel}
        </p>
      </div>
      <div className='flex flex-shrink-0 items-center gap-1'>
        <RowActionsMenu
          label='Server actions'
          actions={[
            { label: 'Details', onSelect: onViewDetails },
            { label: 'Delete', destructive: true, disabled: isDeleting, onSelect: onRemove },
          ]}
        />
      </div>
    </div>
  )
}

function buildEditInitialData(server: McpServer) {
  const entries: { key: string; value: string }[] = server.headers
    ? Object.entries(server.headers).map(([key, value]) => ({ key, value }))
    : []
  if (entries.length === 0) entries.push({ key: '', value: '' })
  const last = entries[entries.length - 1]
  if (last.key !== '' || last.value !== '') entries.push({ key: '', value: '' })

  return {
    name: server.name || '',
    transport: (server.transport as McpTransport) || 'streamable-http',
    url: server.url || '',
    timeout: 30000,
    headers: entries,
    oauthClientId: server.oauthClientId || undefined,
    hasOauthClientSecret: server.hasOauthClientSecret === true,
  }
}

export function MCP() {
  const params = useParams()
  const workspaceId = params.workspaceId as string

  const {
    data: servers = [],
    isLoading: serversLoading,
    error: serversError,
  } = useMcpServers(workspaceId)
  const {
    data: mcpToolsData = [],
    error: toolsError,
    isLoading: toolsLoading,
    isFetching: toolsFetching,
  } = useMcpToolsQuery(workspaceId)
  const { data: storedTools = [], refetch: refetchStoredTools } = useStoredMcpTools(workspaceId)
  const forceRefreshToolsMutation = useForceRefreshMcpTools()
  const forceRefreshTools = forceRefreshToolsMutation.mutate
  const createServerMutation = useCreateMcpServer()
  const deleteServerMutation = useDeleteMcpServer()
  const refreshServerMutation = useRefreshMcpServer()
  const updateServerMutation = useUpdateMcpServer()
  const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
  const { data: allowedMcpDomains = null } = useAllowedMcpDomains()

  const [showAddModal, setShowAddModal] = useState(false)
  const [editingServerId, setEditingServerId] = useState<string | null>(null)

  const [searchTerm, setSearchTerm] = useState('')
  const [deletingServers, setDeletingServers] = useState<Set<string>>(() => new Set())
  const { connectingServers: connectingOauthServers, startOauthForServer } = useMcpOauthPopup({
    workspaceId,
  })

  const [serverToDeleteId, setServerToDeleteId] = useState<string | null>(null)
  const showDeleteDialog = serverToDeleteId !== null

  const [selectedServerId, setSelectedServerId] = useQueryState(mcpServerIdParam.key, {
    ...mcpServerIdParam.parser,
    ...mcpServerIdUrlKeys,
  })

  const initialServerIdRef = useRef(selectedServerId)
  const didDeepLinkRefreshRef = useRef(false)
  useEffect(() => {
    if (didDeepLinkRefreshRef.current) return
    if (!initialServerIdRef.current) return
    didDeepLinkRefreshRef.current = true
    forceRefreshTools(workspaceId)
    refetchStoredTools()
  }, [workspaceId, forceRefreshTools, refetchStoredTools])

  const [expandedTools, setExpandedTools] = useState<Set<string>>(() => new Set())

  const handleRemoveServer = (serverId: string) => {
    setServerToDeleteId(serverId)
  }

  const confirmDeleteServer = async () => {
    if (!serverToDeleteId) return

    const serverId = serverToDeleteId
    setServerToDeleteId(null)

    setDeletingServers((prev) => new Set(prev).add(serverId))

    try {
      await deleteServerMutation.mutateAsync({ workspaceId, serverId })
      logger.info(`Removed MCP server: ${serverId}`)
    } catch (error) {
      logger.error('Failed to remove MCP server:', error)
    } finally {
      setDeletingServers((prev) => {
        const newSet = new Set(prev)
        newSet.delete(serverId)
        return newSet
      })
    }
  }

  const toolsByServer = (mcpToolsData || []).reduce(
    (acc, tool) => {
      if (!tool?.serverId) return acc
      if (!acc[tool.serverId]) {
        acc[tool.serverId] = []
      }
      acc[tool.serverId].push(tool)
      return acc
    },
    {} as Record<string, typeof mcpToolsData>
  )

  const filteredServers = (servers || []).filter((server) =>
    server.name?.toLowerCase().includes(searchTerm.toLowerCase())
  )

  const handleViewDetails = (serverId: string) => {
    setSelectedServerId(serverId)
    forceRefreshTools(workspaceId)
    refetchStoredTools()
  }

  const handleBackToList = () => {
    setSelectedServerId(null)
    setExpandedTools(new Set())
  }

  const toggleToolExpanded = (toolName: string) => {
    setExpandedTools((prev) => {
      const newSet = new Set(prev)
      if (newSet.has(toolName)) {
        newSet.delete(toolName)
      } else {
        newSet.add(toolName)
      }
      return newSet
    })
  }

  const handleRefreshServer = async (serverId: string) => {
    try {
      const result = await refreshServerMutation.mutateAsync({ workspaceId, serverId })
      logger.info(
        `Refreshed MCP server: ${serverId}, workflows updated: ${result.workflowsUpdated}`
      )

      const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
      if (activeWorkflowId && result.updatedWorkflowIds?.includes(activeWorkflowId)) {
        logger.info(`Active workflow ${activeWorkflowId} was updated, reloading subblock values`)
        try {
          const { data: workflowData } = await requestJson(getWorkflowStateContract, {
            params: { id: activeWorkflowId },
          })
          if (workflowData?.state?.blocks) {
            useSubBlockStore
              .getState()
              .initializeFromWorkflow(
                activeWorkflowId,
                workflowData.state.blocks as Record<string, BlockState>
              )
          }
        } catch (reloadError) {
          logger.warn('Failed to reload workflow subblock values:', reloadError)
        }
      }
    } catch (error) {
      logger.error('Failed to refresh MCP server:', error)
    }
  }

  useEffect(() => {
    if (!refreshServerMutation.isSuccess) return
    const timeout = window.setTimeout(() => refreshServerMutation.reset(), 3000)
    return () => window.clearTimeout(timeout)
    // eslint-disable-next-line react-hooks/exhaustive-deps -- mutation object is unstable; isSuccess flag is the trigger
  }, [refreshServerMutation.isSuccess])

  const refreshingServerId = refreshServerMutation.isPending
    ? refreshServerMutation.variables?.serverId
    : null
  const refreshedServerId = refreshServerMutation.isSuccess
    ? refreshServerMutation.variables?.serverId
    : null
  const refreshedWorkflowsUpdated = refreshServerMutation.data?.workflowsUpdated

  const editingServer = editingServerId
    ? (servers.find((s) => s.id === editingServerId) as McpServer | undefined)
    : undefined
  const editInitialData = editingServer ? buildEditInitialData(editingServer) : undefined

  const selectedServer = (() => {
    if (!selectedServerId) return null
    const server = servers.find((s) => s.id === selectedServerId) as McpServer | undefined
    if (!server) return null
    const serverTools = (toolsByServer[selectedServerId] || []) as McpTool[]
    return { server, tools: serverTools }
  })()

  const getStoredToolIssues = (
    serverId: string,
    toolName: string
  ): { issue: McpToolIssue; workflowName: string }[] => {
    const relevantStoredTools = storedTools.filter(
      (st) => st.serverId === serverId && st.toolName === toolName
    )

    const serverStates = servers.map((s) => ({
      id: s.id,
      url: s.url,
      connectionStatus: s.connectionStatus,
      lastError: s.lastError || undefined,
    }))

    const discoveredTools = mcpToolsData.map((t) => ({
      serverId: t.serverId,
      name: t.name,
      inputSchema: t.inputSchema,
    }))

    const issues: { issue: McpToolIssue; workflowName: string }[] = []

    for (const storedTool of relevantStoredTools) {
      const issue = getMcpToolIssue(
        {
          serverId: storedTool.serverId,
          serverUrl: storedTool.serverUrl,
          toolName: storedTool.toolName,
          schema: storedTool.schema,
        },
        serverStates,
        discoveredTools
      )

      if (issue) {
        issues.push({ issue, workflowName: storedTool.workflowName })
      }
    }

    return issues
  }

  const error = toolsError || serversError
  const hasServers = servers && servers.length > 0
  const showNoResults = searchTerm.trim() && filteredServers.length === 0 && servers.length > 0

  if (selectedServer) {
    const { server, tools } = selectedServer
    const transportLabel = formatTransportLabel(server.transport || 'http')

    const refreshLabel =
      refreshingServerId === server.id
        ? 'Refreshing...'
        : refreshedServerId === server.id
          ? refreshedWorkflowsUpdated
            ? `Synced (${refreshedWorkflowsUpdated} workflow${refreshedWorkflowsUpdated === 1 ? '' : 's'})`
            : 'Refreshed'
          : 'Refresh Tools'

    return (
      <div className='flex h-full flex-col bg-[var(--bg)]'>
        <div className='flex flex-shrink-0 items-center justify-between bg-[var(--bg)] px-[16px] pt-[8.5px] pb-[8.5px]'>
          <Chip leftIcon={ArrowLeft} onClick={handleBackToList}>
            MCP Tools
          </Chip>
          <div className='flex items-center'>
            <Chip
              onClick={() => handleRefreshServer(server.id)}
              disabled={refreshingServerId === server.id || refreshedServerId === server.id}
            >
              {refreshLabel}
            </Chip>
            <Chip onClick={() => setEditingServerId(server.id)}>Edit</Chip>
          </div>
        </div>

        <div className='min-h-0 flex-1 overflow-y-auto px-6 [scrollbar-gutter:stable_both-edges]'>
          <div className='mx-auto flex max-w-[48rem] flex-col gap-7 pt-4 pb-6'>
            <SettingsSection label='Server'>
              <div className='flex flex-col gap-4.5'>
                <div className='flex flex-col gap-2'>
                  <span className='text-[12px] text-[var(--text-muted)]'>Server Name</span>
                  <p className='text-[14px] text-[var(--text-body)]'>
                    {server.name || 'Unnamed Server'}
                  </p>
                </div>

                <div className='flex flex-col gap-2'>
                  <span className='text-[12px] text-[var(--text-muted)]'>Transport</span>
                  <p className='text-[14px] text-[var(--text-body)]'>{transportLabel}</p>
                </div>

                {server.url && (
                  <div className='flex flex-col gap-2'>
                    <span className='text-[12px] text-[var(--text-muted)]'>URL</span>
                    <p className='break-all text-[14px] text-[var(--text-body)]'>{server.url}</p>
                  </div>
                )}

                {server.connectionStatus === 'error' && (
                  <div className='flex flex-col gap-2'>
                    <span className='text-[12px] text-[var(--text-muted)]'>Status</span>
                    <p className='text-[14px] text-[var(--text-error)]'>
                      {server.lastError || 'Unable to connect'}
                    </p>
                  </div>
                )}

                {server.authType === 'oauth' && server.connectionStatus !== 'connected' && (
                  <div className='flex flex-col gap-2'>
                    <span className='text-[12px] text-[var(--text-muted)]'>Authentication</span>
                    <div>
                      <Chip
                        variant='primary'
                        disabled={connectingOauthServers.has(server.id)}
                        onClick={async () => {
                          await startOauthForServer(server.id)
                        }}
                      >
                        {connectingOauthServers.has(server.id)
                          ? 'Connecting…'
                          : 'Connect with OAuth'}
                      </Chip>
                    </div>
                  </div>
                )}
              </div>
            </SettingsSection>

            <SettingsSection label={`Tools (${tools.length})`}>
              {tools.length === 0 ? (
                <p className='text-[var(--text-muted)] text-sm'>No tools available</p>
              ) : (
                <div className='flex flex-col gap-2'>
                  {tools.map((tool) => {
                    const issues = getStoredToolIssues(server.id, tool.name)
                    const affectedWorkflows = issues.map((i) => i.workflowName)
                    const isExpanded = expandedTools.has(tool.name)
                    const hasParams =
                      tool.inputSchema?.properties &&
                      Object.keys(tool.inputSchema.properties).length > 0
                    const requiredParams = tool.inputSchema?.required || []

                    return (
                      <div
                        key={tool.name}
                        className='overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-3)]'
                      >
                        <Button
                          type='button'
                          variant='ghost'
                          onClick={() => hasParams && toggleToolExpanded(tool.name)}
                          className={cn(
                            'flex h-auto w-full items-start justify-between rounded-none px-2.5 py-2 text-left text-sm',
                            hasParams && 'cursor-pointer hover-hover:bg-[var(--surface-4)]'
                          )}
                          disabled={!hasParams}
                        >
                          <div className='flex-1'>
                            <div className='flex h-[16px] items-center gap-1.5'>
                              <p className='font-medium text-[var(--text-primary)] text-sm leading-none'>
                                {tool.name}
                              </p>
                              {issues.length > 0 && (
                                <Tooltip.Root>
                                  <Tooltip.Trigger asChild>
                                    <div className='flex items-center'>
                                      <Badge
                                        variant={getIssueBadgeVariant(issues[0].issue)}
                                        size='sm'
                                      >
                                        {getIssueBadgeLabel(issues[0].issue)}
                                      </Badge>
                                    </div>
                                  </Tooltip.Trigger>
                                  <Tooltip.Content>
                                    Update in: {affectedWorkflows.join(', ')}
                                  </Tooltip.Content>
                                </Tooltip.Root>
                              )}
                            </div>
                            {tool.description && (
                              <p className='mt-1 text-[var(--text-tertiary)] text-sm'>
                                {tool.description}
                              </p>
                            )}
                          </div>
                          {hasParams && (
                            <ChevronDown
                              className={cn(
                                'mt-0.5 size-[14px] flex-shrink-0 text-[var(--text-muted)] transition-transform duration-200',
                                isExpanded && 'rotate-180'
                              )}
                            />
                          )}
                        </Button>

                        {isExpanded && hasParams && (
                          <div className='border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 py-2'>
                            <p className='mb-1.5 font-medium text-[var(--text-muted)] text-xs uppercase tracking-wide'>
                              Parameters
                            </p>
                            <div className='flex flex-col gap-1.5'>
                              {Object.entries(tool.inputSchema!.properties!).map(
                                ([paramName, param]) => {
                                  const isRequired = requiredParams.includes(paramName)
                                  const paramType =
                                    typeof param === 'object' && param !== null
                                      ? (param as { type?: string }).type || 'any'
                                      : 'any'
                                  const paramDesc =
                                    typeof param === 'object' && param !== null
                                      ? (param as { description?: string }).description
                                      : undefined

                                  return (
                                    <div
                                      key={paramName}
                                      className='rounded-sm border border-[var(--border-1)] bg-[var(--surface-3)] px-2 py-1.5'
                                    >
                                      <div className='flex items-center gap-1.5'>
                                        <span className='font-medium text-[var(--text-primary)] text-small'>
                                          {paramName}
                                        </span>
                                        <Badge variant='outline' size='sm'>
                                          {paramType}
                                        </Badge>
                                        {isRequired && (
                                          <Badge variant='default' size='sm'>
                                            required
                                          </Badge>
                                        )}
                                      </div>
                                      {paramDesc && (
                                        <p className='mt-[3px] text-[var(--text-tertiary)] text-xs leading-relaxed'>
                                          {paramDesc}
                                        </p>
                                      )}
                                    </div>
                                  )
                                }
                              )}
                            </div>
                          </div>
                        )}
                      </div>
                    )
                  })}
                </div>
              )}
            </SettingsSection>
          </div>
        </div>

        <McpServerFormModal
          open={editingServerId !== null}
          onOpenChange={(open) => {
            if (!open) setEditingServerId(null)
          }}
          mode='edit'
          initialData={editInitialData}
          onSubmit={async (config) => {
            const currentServer = servers.find((s) => s.id === selectedServerId)
            await updateServerMutation.mutateAsync({
              workspaceId,
              serverId: selectedServerId!,
              updates: {
                ...config,
                enabled: currentServer?.enabled ?? true,
              },
            })
          }}
          workspaceId={workspaceId}
          availableEnvVars={availableEnvVars}
          allowedMcpDomains={allowedMcpDomains}
        />
      </div>
    )
  }

  return (
    <>
      <SettingsPanel
        search={{
          value: searchTerm,
          onChange: setSearchTerm,
          placeholder: 'Search MCPs...',
        }}
        actions={
          <Chip
            leftIcon={Plus}
            variant='primary'
            onClick={() => setShowAddModal(true)}
            disabled={serversLoading}
          >
            Add Server
          </Chip>
        }
      >
        {error ? (
          <div className='flex h-full flex-col items-center justify-center gap-2'>
            <p className='text-[var(--text-error)] text-xs leading-tight'>
              {getErrorMessage(error, 'Failed to load MCP servers')}
            </p>
          </div>
        ) : serversLoading ? null : !hasServers ? (
          <SettingsEmptyState>Click &quot;Add Server&quot; above to get started</SettingsEmptyState>
        ) : (
          <div className='flex flex-col gap-2'>
            {filteredServers.map((server) => {
              if (!server?.id) return null
              const tools = toolsByServer[server.id] || []
              const isLoadingTools = toolsLoading || toolsFetching

              return (
                <ServerListItem
                  key={server.id}
                  server={server}
                  tools={tools}
                  isDeleting={deletingServers.has(server.id)}
                  isLoadingTools={isLoadingTools}
                  isRefreshing={refreshingServerId === server.id}
                  onRemove={() => handleRemoveServer(server.id)}
                  onViewDetails={() => handleViewDetails(server.id)}
                />
              )
            })}
            {showNoResults && (
              <SettingsEmptyState variant='inline'>
                No servers found matching &quot;{searchTerm}&quot;
              </SettingsEmptyState>
            )}
          </div>
        )}
      </SettingsPanel>

      <McpServerFormModal
        open={showAddModal}
        onOpenChange={setShowAddModal}
        mode='add'
        onSubmit={async (config) => {
          const result = await createServerMutation.mutateAsync({
            workspaceId,
            config: { ...config, enabled: true },
          })
          if (result.authType === 'oauth') {
            await startOauthForServer(result.serverId)
          }
        }}
        workspaceId={workspaceId}
        availableEnvVars={availableEnvVars}
        allowedMcpDomains={allowedMcpDomains}
      />

      <ChipConfirmModal
        open={showDeleteDialog}
        onOpenChange={(open) => {
          if (!open) setServerToDeleteId(null)
        }}
        srTitle='Delete MCP Server'
        title='Delete MCP Server'
        text={[
          'Are you sure you want to delete ',
          {
            text: servers.find((s) => s.id === serverToDeleteId)?.name || 'this server',
            bold: true,
          },
          '? This action cannot be undone.',
        ]}
        confirm={{ label: 'Delete', onClick: confirmDeleteServer }}
      />
    </>
  )
}
