import { useState, useEffect, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import ReactFlow, {
  Node,
  Edge,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
  MarkerType,
  NodeProps,
  Handle,
  Position,
} from 'reactflow';
import dagre from 'dagre';
import 'reactflow/dist/style.css';
import { ResolvedArtifact, DependencyResolutionResponse, Dependency } from '../types';
import { resolveDependencies, getArtifactDependencies } from '../api';
import './DependencyGraph.css';

interface DependencyGraphProps {
  projectName: string;
  packageName: string;
  tagName: string;
  onClose: () => void;
}

interface NodeData {
  label: string;
  project: string;
  package: string;
  version: string | null;
  size: number;
  isRoot: boolean;
  onNavigate: (project: string, pkg: string) => void;
}

function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 B';
  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}

// Custom node component
function DependencyNode({ data }: NodeProps<NodeData>) {
  return (
    <div
      className={`flow-node ${data.isRoot ? 'flow-node--root' : ''}`}
      onClick={() => data.onNavigate(data.project, data.package)}
    >
      <Handle type="target" position={Position.Top} className="flow-handle" />
      <div className="flow-node__name">{data.package}</div>
      <div className="flow-node__details">
        {data.version && <span className="flow-node__version">{data.version}</span>}
        <span className="flow-node__size">{formatBytes(data.size)}</span>
      </div>
      <Handle type="source" position={Position.Bottom} className="flow-handle" />
    </div>
  );
}

const nodeTypes = { dependency: DependencyNode };

// Dagre layout function
function getLayoutedElements(
  nodes: Node<NodeData>[],
  edges: Edge[],
  direction: 'TB' | 'LR' = 'TB'
) {
  const dagreGraph = new dagre.graphlib.Graph();
  dagreGraph.setDefaultEdgeLabel(() => ({}));

  const nodeWidth = 180;
  const nodeHeight = 60;

  dagreGraph.setGraph({ rankdir: direction, nodesep: 50, ranksep: 80 });

  nodes.forEach((node) => {
    dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
  });

  edges.forEach((edge) => {
    dagreGraph.setEdge(edge.source, edge.target);
  });

  dagre.layout(dagreGraph);

  const layoutedNodes = nodes.map((node) => {
    const nodeWithPosition = dagreGraph.node(node.id);
    return {
      ...node,
      position: {
        x: nodeWithPosition.x - nodeWidth / 2,
        y: nodeWithPosition.y - nodeHeight / 2,
      },
    };
  });

  return { nodes: layoutedNodes, edges };
}

function DependencyGraph({ projectName, packageName, tagName, onClose }: DependencyGraphProps) {
  const navigate = useNavigate();

  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [resolution, setResolution] = useState<DependencyResolutionResponse | null>(null);
  const [nodes, setNodes, onNodesChange] = useNodesState<NodeData>([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);

  const handleNavigate = useCallback((project: string, pkg: string) => {
    navigate(`/project/${project}/${pkg}`);
    onClose();
  }, [navigate, onClose]);

  // Build graph structure from resolution data
  const buildFlowGraph = useCallback(async (
    resolutionData: DependencyResolutionResponse,
    onNavigate: (project: string, pkg: string) => void
  ) => {
    const artifactMap = new Map<string, ResolvedArtifact>();
    resolutionData.resolved.forEach(artifact => {
      artifactMap.set(artifact.artifact_id, artifact);
    });

    // Fetch dependencies for each artifact
    const depsMap = new Map<string, Dependency[]>();

    for (const artifact of resolutionData.resolved) {
      try {
        const deps = await getArtifactDependencies(artifact.artifact_id);
        depsMap.set(artifact.artifact_id, deps.dependencies);
      } catch {
        depsMap.set(artifact.artifact_id, []);
      }
    }

    // Find the root artifact
    const rootArtifact = resolutionData.resolved.find(
      a => a.project === resolutionData.requested.project &&
           a.package === resolutionData.requested.package
    );

    if (!rootArtifact) {
      return { nodes: [], edges: [] };
    }

    const flowNodes: Node<NodeData>[] = [];
    const flowEdges: Edge[] = [];
    const visited = new Set<string>();
    const nodeIdMap = new Map<string, string>(); // artifact_id -> node id

    // Build nodes and edges recursively
    const processNode = (artifact: ResolvedArtifact, isRoot: boolean) => {
      if (visited.has(artifact.artifact_id)) {
        return nodeIdMap.get(artifact.artifact_id);
      }

      visited.add(artifact.artifact_id);
      const nodeId = `node-${flowNodes.length}`;
      nodeIdMap.set(artifact.artifact_id, nodeId);

      flowNodes.push({
        id: nodeId,
        type: 'dependency',
        position: { x: 0, y: 0 }, // Will be set by dagre
        data: {
          label: `${artifact.project}/${artifact.package}`,
          project: artifact.project,
          package: artifact.package,
          version: artifact.version || artifact.tag,
          size: artifact.size,
          isRoot,
          onNavigate,
        },
      });

      const deps = depsMap.get(artifact.artifact_id) || [];

      for (const dep of deps) {
        const childArtifact = resolutionData.resolved.find(
          a => a.project === dep.project && a.package === dep.package
        );

        if (childArtifact) {
          const childNodeId = processNode(childArtifact, false);
          if (childNodeId) {
            flowEdges.push({
              id: `edge-${nodeId}-${childNodeId}`,
              source: nodeId,
              target: childNodeId,
              markerEnd: {
                type: MarkerType.ArrowClosed,
                width: 15,
                height: 15,
                color: 'var(--accent-primary)',
              },
              style: {
                stroke: 'var(--border-primary)',
                strokeWidth: 2,
              },
            });
          }
        }
      }

      return nodeId;
    };

    processNode(rootArtifact, true);

    // Apply dagre layout
    return getLayoutedElements(flowNodes, flowEdges);
  }, []);

  useEffect(() => {
    async function loadData() {
      setLoading(true);
      setError(null);

      try {
        const result = await resolveDependencies(projectName, packageName, tagName);

        // If only the root package (no dependencies) and no missing deps, close the modal
        const hasDeps = result.artifact_count > 1 || (result.missing && result.missing.length > 0);
        if (!hasDeps) {
          onClose();
          return;
        }

        setResolution(result);

        const { nodes: layoutedNodes, edges: layoutedEdges } = await buildFlowGraph(result, handleNavigate);
        setNodes(layoutedNodes);
        setEdges(layoutedEdges);
      } catch (err) {
        if (err instanceof Error) {
          try {
            const errorData = JSON.parse(err.message);
            if (errorData.error === 'circular_dependency') {
              setError(`Circular dependency detected: ${errorData.cycle?.join(' → ')}`);
            } else if (errorData.error === 'dependency_conflict') {
              setError(`Dependency conflict: ${errorData.message}`);
            } else {
              setError(err.message);
            }
          } catch {
            setError(err.message);
          }
        } else {
          setError('Failed to load dependency graph');
        }
      } finally {
        setLoading(false);
      }
    }

    loadData();
  }, [projectName, packageName, tagName, buildFlowGraph, handleNavigate, onClose, setNodes, setEdges]);

  const defaultViewport = useMemo(() => ({ x: 50, y: 50, zoom: 0.8 }), []);

  return (
    <div className="dependency-graph-modal" onClick={onClose}>
      <div className="dependency-graph-content" onClick={e => e.stopPropagation()}>
        <div className="dependency-graph-header">
          <h2>Dependency Graph</h2>
          <div className="dependency-graph-info">
            <span>{projectName}/{packageName} @ {tagName}</span>
            {resolution && (
              <span className="graph-stats">
                {resolution.artifact_count} cached
                {resolution.missing && resolution.missing.length > 0 && (
                  <span className="missing-count"> • {resolution.missing.length} not cached</span>
                )}
                 • {formatBytes(resolution.total_size)} total
              </span>
            )}
          </div>
          <button className="close-btn" onClick={onClose} title="Close">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <line x1="18" y1="6" x2="6" y2="18"></line>
              <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
          </button>
        </div>

        <div className="dependency-graph-container">
          {loading ? (
            <div className="graph-loading">
              <div className="spinner"></div>
              <span>Resolving dependencies...</span>
            </div>
          ) : error ? (
            <div className="graph-error">
              <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <circle cx="12" cy="12" r="10"></circle>
                <line x1="12" y1="8" x2="12" y2="12"></line>
                <line x1="12" y1="16" x2="12.01" y2="16"></line>
              </svg>
              <p>{error}</p>
            </div>
          ) : nodes.length > 0 ? (
            <ReactFlow
              nodes={nodes}
              edges={edges}
              onNodesChange={onNodesChange}
              onEdgesChange={onEdgesChange}
              nodeTypes={nodeTypes}
              defaultViewport={defaultViewport}
              fitView
              fitViewOptions={{ padding: 0.2 }}
              minZoom={0.1}
              maxZoom={2}
              attributionPosition="bottom-left"
            >
              <Controls />
              <Background color="var(--border-primary)" gap={20} />
            </ReactFlow>
          ) : (
            <div className="graph-empty">No dependencies to display</div>
          )}
        </div>

        {resolution && resolution.missing && resolution.missing.length > 0 && (
          <div className="missing-dependencies">
            <h3>Not Cached ({resolution.missing.length})</h3>
            <p className="missing-hint">These dependencies are referenced but not yet cached on the server.</p>
            <ul className="missing-list">
              {resolution.missing.map((dep, i) => (
                <li key={i} className="missing-item">
                  <span className="missing-name">{dep.project}/{dep.package}</span>
                  {dep.constraint && <span className="missing-constraint">@{dep.constraint}</span>}
                  {dep.required_by && <span className="missing-required-by">← {dep.required_by}</span>}
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  );
}

export default DependencyGraph;
