import { useState, useCallback, useEffect, useRef } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import {
  ReactFlowProvider,
  useNodesState,
  useEdgesState,
  addEdge,
  useReactFlow,
  type Node,
  type Edge,
  type Connection,
} from '@xyflow/react'
import '@xyflow/react/dist/style.css'

import { NetworkCanvas } from '@/components/network/NetworkCanvas'
import { DiagramHeader } from '@/components/network/DiagramHeader'
import { DeviceToolbar } from '@/components/network/panels/DeviceToolbar'
import { PropertiesPanel } from '@/components/network/panels/PropertiesPanel'
import { AIAssistPanel } from '@/components/network/panels/AIAssistPanel'
import { networkDiagramsApi, deviceTypesApi } from '@/api'
import { toast } from '@/lib/toast'
import type { DeviceTypeResponse, DeviceProperties, AIGenerateResponse, DiagramEdge } from '@/types'
import type { DeviceNodeData } from '@/components/network/nodes/DeviceNode'

function DiagramEditorInner() {
  const { id } = useParams<{ id: string }>()
  const navigate = useNavigate()
  const { getNodes, fitView } = useReactFlow()

  const [diagramId, setDiagramId] = useState<string | null>(id || null)
  const [name, setName] = useState('Untitled Diagram')
  const [clientName, setClientName] = useState<string | null>(null)
  const [assetName, setAssetName] = useState<string | null>(null)
  const [description, setDescription] = useState<string | null>(null)

  const [nodes, setNodes, onNodesChange] = useNodesState<Node>([])
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([])

  const [selectedNode, setSelectedNode] = useState<Node | null>(null)
  const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null)

  const [isDirty, setIsDirty] = useState(false)
  const [isSaving, setIsSaving] = useState(false)
  const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null)
  const isDirtyRef = useRef(false)
  const diagramIdRef = useRef<string | null>(id || null)

  const [deviceTypes, setDeviceTypes] = useState<DeviceTypeResponse[]>([])
  const [loading, setLoading] = useState(!!id)

  useEffect(() => { isDirtyRef.current = isDirty }, [isDirty])
  useEffect(() => { diagramIdRef.current = diagramId }, [diagramId])

  const handleNodesChange: typeof onNodesChange = useCallback((changes) => {
    onNodesChange(changes)
    const hasRealChange = changes.some(c => c.type !== 'select')
    if (hasRealChange) setIsDirty(true)
  }, [onNodesChange])

  const handleEdgesChange: typeof onEdgesChange = useCallback((changes) => {
    onEdgesChange(changes)
    const hasRealChange = changes.some(c => c.type !== 'select')
    if (hasRealChange) setIsDirty(true)
  }, [onEdgesChange])

  const loadDeviceTypes = useCallback(async () => {
    try {
      const types = await deviceTypesApi.list()
      setDeviceTypes(types)
    } catch { /* ignore */ }
  }, [])

  useEffect(() => { loadDeviceTypes() }, [loadDeviceTypes])

  useEffect(() => {
    if (!id) return
    let cancelled = false
    ;(async () => {
      try {
        const diagram = await networkDiagramsApi.get(id)
        if (cancelled) return
        setName(diagram.name)
        setClientName(diagram.client_name)
        setAssetName(diagram.asset_name)
        setDescription(diagram.description)
        setNodes(
          diagram.nodes.map(n => ({
            id: n.id,
            type: 'device',
            position: n.position,
            data: {
              label: n.label,
              deviceType: n.type,
              properties: n.properties,
            } satisfies DeviceNodeData,
          }))
        )
        setEdges(
          diagram.edges.map(e => ({
            id: e.id,
            source: e.source,
            target: e.target,
            type: 'connection',
            label: e.label || undefined,
            data: {
              connectionType: e.connectionType,
              speed: e.speed,
              notes: e.notes,
            },
          }))
        )
        setLastSavedAt(new Date(diagram.updated_at))
      } catch {
        toast.error('Failed to load diagram')
        navigate('/network-diagrams')
      } finally {
        if (!cancelled) setLoading(false)
      }
    })()
    return () => { cancelled = true }
  }, [id, navigate, setNodes, setEdges])

  const serializeNodes = useCallback(() => {
    return getNodes().map(n => {
      const data = n.data as unknown as DeviceNodeData
      return {
        id: n.id,
        type: data.deviceType,
        label: data.label,
        position: n.position,
        properties: data.properties,
      }
    })
  }, [getNodes])

  const serializeEdges = useCallback((): DiagramEdge[] => {
    return edges.map(e => ({
      id: e.id,
      source: e.source,
      target: e.target,
      label: (e.label as string) || null,
      connectionType: (e.data as Record<string, unknown>)?.connectionType as string || 'ethernet',
      speed: (e.data as Record<string, unknown>)?.speed as string || null,
      notes: (e.data as Record<string, unknown>)?.notes as string || null,
    }))
  }, [edges])

  const handleSave = useCallback(async () => {
    setIsSaving(true)
    try {
      const payload = {
        name,
        client_name: clientName,
        asset_name: assetName,
        description,
        nodes: serializeNodes(),
        edges: serializeEdges(),
      }
      if (diagramIdRef.current) {
        await networkDiagramsApi.update(diagramIdRef.current, payload)
      } else {
        const created = await networkDiagramsApi.create(payload)
        setDiagramId(created.id)
        navigate(`/network-diagrams/${created.id}`, { replace: true })
      }
      setIsDirty(false)
      setLastSavedAt(new Date())
    } catch {
      toast.error('Failed to save diagram')
    } finally {
      setIsSaving(false)
    }
  }, [name, clientName, assetName, description, serializeNodes, serializeEdges, navigate])

  useEffect(() => {
    const interval = setInterval(() => {
      if (isDirtyRef.current && diagramIdRef.current) {
        handleSave()
      }
    }, 30_000)
    return () => clearInterval(interval)
  }, [handleSave])

  const onConnect = useCallback((connection: Connection) => {
    setEdges(eds => addEdge({
      ...connection,
      type: 'connection',
      data: { connectionType: 'ethernet' },
    }, eds))
    setIsDirty(true)
  }, [setEdges])

  const onDragOver = useCallback((event: React.DragEvent) => {
    event.preventDefault()
    event.dataTransfer.dropEffect = 'move'
  }, [])

  const onDrop = useCallback((event: React.DragEvent) => {
    event.preventDefault()
    const raw = event.dataTransfer.getData('application/reactflow-device')
    if (!raw) return

    const { slug, label, category } = JSON.parse(raw) as { slug: string; label: string; category: string }
    const reactFlowBounds = (event.target as HTMLElement).closest('.react-flow')?.getBoundingClientRect()
    if (!reactFlowBounds) return

    const position = {
      x: event.clientX - reactFlowBounds.left,
      y: event.clientY - reactFlowBounds.top,
    }

    const newNode: Node = {
      id: `device-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
      type: 'device',
      position,
      data: {
        label,
        deviceType: slug,
        category,
        properties: {
          hostname: null,
          ip: null,
          subnet: null,
          vendor: null,
          model: null,
          role: null,
          vlan: null,
          notes: null,
          status: 'unknown',
        } satisfies DeviceProperties,
      } satisfies DeviceNodeData,
    }
    setNodes(nds => [...nds, newNode])
    setIsDirty(true)
  }, [setNodes])

  const handleNodeUpdate = useCallback((nodeId: string, updates: Partial<DeviceNodeData>) => {
    setNodes(nds => nds.map(n => {
      if (n.id !== nodeId) return n
      return { ...n, data: { ...n.data, ...updates } }
    }))
    setIsDirty(true)
  }, [setNodes])

  const handleEdgeUpdate = useCallback((edgeId: string, updates: Partial<DiagramEdge>) => {
    setEdges(eds => eds.map(e => {
      if (e.id !== edgeId) return e
      return {
        ...e,
        label: updates.label !== undefined ? (updates.label || undefined) : e.label,
        data: {
          ...(e.data || {}),
          ...(updates.connectionType !== undefined ? { connectionType: updates.connectionType } : {}),
          ...(updates.speed !== undefined ? { speed: updates.speed } : {}),
          ...(updates.notes !== undefined ? { notes: updates.notes } : {}),
        },
      }
    }))
    setSelectedEdge(prev => {
      if (!prev || prev.id !== edgeId) return prev
      return {
        ...prev,
        label: updates.label !== undefined ? (updates.label || undefined) : prev.label,
        data: {
          ...(prev.data || {}),
          ...(updates.connectionType !== undefined ? { connectionType: updates.connectionType } : {}),
          ...(updates.speed !== undefined ? { speed: updates.speed } : {}),
          ...(updates.notes !== undefined ? { notes: updates.notes } : {}),
        },
      }
    })
    setIsDirty(true)
  }, [setEdges])

  const handleDeleteNode = useCallback((nodeId: string) => {
    setNodes(nds => nds.filter(n => n.id !== nodeId))
    setEdges(eds => eds.filter(e => e.source !== nodeId && e.target !== nodeId))
    setSelectedNode(null)
    setIsDirty(true)
  }, [setNodes, setEdges])

  const handleDeleteEdge = useCallback((edgeId: string) => {
    setEdges(eds => eds.filter(e => e.id !== edgeId))
    setSelectedEdge(null)
    setIsDirty(true)
  }, [setEdges])

  const handleAIGenerate = useCallback((result: AIGenerateResponse, mode: 'replace' | 'merge') => {
    const newNodes: Node[] = result.nodes.map(n => ({
      id: n.id,
      type: 'device',
      position: n.position,
      data: {
        label: n.label,
        deviceType: n.type,
        properties: n.properties,
      } satisfies DeviceNodeData,
    }))

    const newEdges: Edge[] = result.edges.map(e => ({
      id: e.id,
      source: e.source,
      target: e.target,
      type: 'connection',
      label: e.label || undefined,
      data: { connectionType: e.connectionType, speed: e.speed, notes: e.notes },
    }))

    if (mode === 'replace') {
      setNodes(newNodes)
      setEdges(newEdges)
    } else {
      setNodes(nds => [...nds, ...newNodes])
      setEdges(eds => [...eds, ...newEdges])
    }

    if (result.suggestedName && !diagramId) {
      setName(result.suggestedName)
      toast.success(`Generated: ${result.suggestedName}`)
    } else {
      toast.success('Diagram generated')
    }

    if (result.notes) {
      toast.info(result.notes)
    }

    setIsDirty(true)
    setTimeout(() => fitView({ padding: 0.2 }), 100)
  }, [setNodes, setEdges, diagramId, fitView])

  const getExistingBounds = useCallback(() => {
    const currentNodes = getNodes()
    if (currentNodes.length === 0) return null
    let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity
    for (const n of currentNodes) {
      minX = Math.min(minX, n.position.x)
      maxX = Math.max(maxX, n.position.x + 120)
      minY = Math.min(minY, n.position.y)
      maxY = Math.max(maxY, n.position.y + 80)
    }
    return { minX, maxX, minY, maxY }
  }, [getNodes])

  const handleExportPng = useCallback(() => {
    toast.info('PNG export — use your browser\'s screenshot tool or Print > Save as Image for now')
  }, [])

  const handleExportPdf = useCallback(() => {
    window.print()
  }, [])

  const handleExportJson = useCallback(async () => {
    if (!diagramId) return
    try {
      const data = await networkDiagramsApi.exportJson(diagramId)
      const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = `${name.replace(/[^a-zA-Z0-9-_ ]/g, '')}.json`
      a.click()
      URL.revokeObjectURL(url)
    } catch {
      toast.error('Failed to export diagram')
    }
  }, [diagramId, name])

  if (loading) {
    return (
      <div className="flex h-full items-center justify-center">
        <div className="h-6 w-6 animate-spin rounded-full border-2 border-accent border-t-transparent" />
      </div>
    )
  }

  return (
    <div className="flex h-full flex-col">
      <DiagramHeader
        name={name}
        clientName={clientName}
        isSaving={isSaving}
        lastSavedAt={lastSavedAt}
        diagramId={diagramId}
        onNameChange={n => { setName(n); setIsDirty(true) }}
        onSave={handleSave}
        onExportPng={handleExportPng}
        onExportPdf={handleExportPdf}
        onExportJson={handleExportJson}
      />
      <div className="flex flex-1 min-h-0">
        <DeviceToolbar deviceTypes={deviceTypes} onDeviceTypesChange={loadDeviceTypes} />
        <div className="flex flex-1 flex-col min-h-0">
          <div className="flex-1 min-h-0">
            <NetworkCanvas
              nodes={nodes}
              edges={edges}
              onNodesChange={handleNodesChange}
              onEdgesChange={handleEdgesChange}
              onConnect={onConnect}
              onNodeSelect={setSelectedNode}
              onEdgeSelect={setSelectedEdge}
              onDrop={onDrop}
              onDragOver={onDragOver}
            />
          </div>
          <AIAssistPanel
            onGenerate={handleAIGenerate}
            getExistingBounds={getExistingBounds}
            hasNodes={nodes.length > 0}
          />
        </div>
        <PropertiesPanel
          selectedNode={selectedNode}
          selectedEdge={selectedEdge}
          onNodeUpdate={handleNodeUpdate}
          onEdgeUpdate={handleEdgeUpdate}
          onDeleteNode={handleDeleteNode}
          onDeleteEdge={handleDeleteEdge}
        />
      </div>
    </div>
  )
}

export default function DiagramEditor() {
  return (
    <ReactFlowProvider>
      <DiagramEditorInner />
    </ReactFlowProvider>
  )
}
