import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
import { X } from 'lucide-react';
import { useNavigate, useLocation, useParams } from 'react-router';
import { useGlobalFilters, useToast } from '@cameleer/design-system';
import { useExecutionDetail } from '../../api/queries/executions';
import { useDiagramByRoute } from '../../api/queries/diagrams';
import { useCatalog } from '../../api/queries/catalog';
import { useAgents } from '../../api/queries/agents';
import { useApplicationConfig, useUpdateApplicationConfig } from '../../api/queries/commands';
import type { TapDefinition, ConfigUpdateResponse } from '../../api/queries/commands';
import { useEnvironmentStore } from '../../api/environment-store';
import { useCanControl } from '../../auth/auth-store';
import { useTracingStore } from '../../stores/tracing-store';
import type { NodeAction, NodeConfig } from '../../components/ProcessDiagram/types';
import { TapConfigModal } from '../../components/TapConfigModal';
import { ExchangeHeader } from './ExchangeHeader';
import { RouteControlBar } from './RouteControlBar';
import { ExecutionDiagram } from '../../components/ExecutionDiagram/ExecutionDiagram';
import { ProcessDiagram } from '../../components/ProcessDiagram';
import styles from './ExchangesPage.module.css';

import Dashboard from '../Dashboard/Dashboard';
import type { SelectedExchange } from '../Dashboard/Dashboard';

export default function ExchangesPage() {
  const navigate = useNavigate();
  const location = useLocation();
  const selectedEnv = useEnvironmentStore((s) => s.environment);
  const { appId: scopedAppId, routeId: scopedRouteId, exchangeId: scopedExchangeId } =
    useParams<{ appId?: string; routeId?: string; exchangeId?: string }>();

  // Restore selection from browser history state (enables Back/Forward)
  const stateSelected = (location.state as any)?.selectedExchange as SelectedExchange | undefined;

  // Derive selection from URL params when no state-based selection exists (Cmd-K, bookmarks)
  const urlDerivedExchange: SelectedExchange | null =
    (scopedExchangeId && scopedAppId && scopedRouteId)
      ? { executionId: scopedExchangeId, applicationId: scopedAppId, routeId: scopedRouteId }
      : null;

  const [selected, setSelectedInternal] = useState<SelectedExchange | null>(stateSelected ?? urlDerivedExchange);

  // Sync selection from history state or URL params on navigation changes
  useEffect(() => {
    const restored = (location.state as any)?.selectedExchange as SelectedExchange | undefined;
    if (restored) {
      setSelectedInternal(restored);
    } else if (scopedExchangeId && scopedAppId && scopedRouteId) {
      setSelectedInternal({
        executionId: scopedExchangeId,
        applicationId: scopedAppId,
        routeId: scopedRouteId,
      });
    } else {
      setSelectedInternal(null);
    }
  }, [location.state, scopedExchangeId, scopedAppId, scopedRouteId]);

  const [splitPercent, setSplitPercent] = useState(50);
  const containerRef = useRef<HTMLDivElement>(null);

  // Select an exchange: push a history entry so Back restores the previous state
  const handleExchangeSelect = useCallback((exchange: SelectedExchange) => {
    setSelectedInternal(exchange);
    navigate(location.pathname + location.search, {
      state: { ...location.state, selectedExchange: exchange },
    });
  }, [navigate, location.pathname, location.search, location.state]);

  // Select a correlated exchange: push another history entry
  const handleCorrelatedSelect = useCallback((executionId: string, applicationId: string, routeId: string) => {
    const exchange = { executionId, applicationId, routeId };
    setSelectedInternal(exchange);
    navigate(location.pathname + location.search, {
      state: { ...location.state, selectedExchange: exchange },
    });
  }, [navigate, location.pathname, location.search, location.state]);

  // Clear selection: navigate up to route level when URL has exchangeId
  const handleClearSelection = useCallback(() => {
    setSelectedInternal(null);
    if (scopedExchangeId && scopedAppId && scopedRouteId) {
      navigate(`/exchanges/${scopedAppId}/${scopedRouteId}`, {
        state: { ...location.state, selectedExchange: undefined },
      });
    }
  }, [scopedExchangeId, scopedAppId, scopedRouteId, navigate, location.state]);

  const handleSplitterDown = useCallback((e: React.PointerEvent) => {
    e.currentTarget.setPointerCapture(e.pointerId);
    const container = containerRef.current;
    if (!container) return;
    const onMove = (me: PointerEvent) => {
      const rect = container.getBoundingClientRect();
      const x = me.clientX - rect.left;
      const pct = Math.min(80, Math.max(20, (x / rect.width) * 100));
      setSplitPercent(pct);
    };
    const onUp = () => {
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
    };
    document.addEventListener('pointermove', onMove);
    document.addEventListener('pointerup', onUp);
  }, []);

  // Show split view when a route is scoped (sidebar) or an exchange is selected
  const showSplit = !!selected || !!scopedRouteId;

  if (!showSplit) {
    return <Dashboard onExchangeSelect={handleExchangeSelect} activeExchangeId={selected?.executionId} />;
  }

  // Determine what the right panel shows
  const panelAppId = selected?.applicationId ?? scopedAppId!;
  const panelRouteId = selected?.routeId ?? scopedRouteId!;
  const panelExchangeId = selected?.executionId ?? undefined;

  return (
    <div ref={containerRef} className={styles.splitView}>
      <div className={styles.leftPanel} style={{ width: `${splitPercent}%` }}>
        <Dashboard onExchangeSelect={handleExchangeSelect} activeExchangeId={selected?.executionId} />
      </div>
      <div className={styles.splitter} onPointerDown={handleSplitterDown} />
      <div className={styles.rightPanel} style={{ width: `${100 - splitPercent}%` }}>
        <button className={styles.closeBtn} onClick={handleClearSelection} title="Close panel" aria-label="Close panel">
          <X size={14} />
        </button>
        <DiagramPanel
          appId={panelAppId}
          routeId={panelRouteId}
          exchangeId={panelExchangeId}
          onCorrelatedSelect={handleCorrelatedSelect}
          onClearSelection={handleClearSelection}
        />
      </div>
    </div>
  );
}

// ─── Right panel: diagram + execution overlay ───────────────────────────────

interface DiagramPanelProps {
  appId: string;
  routeId: string;
  exchangeId?: string;
  onCorrelatedSelect: (executionId: string, applicationId: string, routeId: string) => void;
  onClearSelection: () => void;
}

function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearSelection }: DiagramPanelProps) {
  const selectedEnv = useEnvironmentStore((s) => s.environment);
  const { timeRange } = useGlobalFilters();
  const timeFrom = timeRange.start.toISOString();
  const timeTo = timeRange.end.toISOString();

  const { data: detail } = useExecutionDetail(exchangeId ?? null);
  const diagramQuery = useDiagramByRoute(appId, routeId);

  const { data: catalog } = useCatalog();

  // Route state + capabilities for topology-only control bar
  const { data: agents } = useAgents(undefined, appId);
  const canControl = useCanControl();
  const { hasRouteControl, hasReplay } = useMemo(() => {
    if (!agents) return { hasRouteControl: false, hasReplay: false };
    const agentList = agents as any[];
    return {
      hasRouteControl: agentList.some((a: any) => a.capabilities?.routeControl === true),
      hasReplay: agentList.some((a: any) => a.capabilities?.replay === true),
    };
  }, [agents]);
  const routeState = useMemo(() => {
    if (!catalog) return undefined;
    for (const app of catalog as any[]) {
      if (app.slug !== appId) continue;
      for (const r of app.routes || []) {
        if (r.routeId === routeId) return (r.routeState ?? 'started') as 'started' | 'stopped' | 'suspended';
      }
    }
    return undefined;
  }, [catalog, appId, routeId]);

  const knownRouteIds = useMemo(() => {
    const ids = new Set<string>();
    if (catalog) {
      for (const app of catalog as any[]) {
        for (const r of app.routes || []) {
          ids.add(r.routeId);
        }
      }
    }
    return ids;
  }, [catalog]);

  // Build endpoint URI → routeId map for cross-route drill-down
  const endpointRouteMap = useMemo(() => {
    const map = new Map<string, string>();
    if (catalog) {
      for (const app of catalog as any[]) {
        for (const r of app.routes || []) {
          if (r.fromEndpointUri) {
            map.set(r.fromEndpointUri, r.routeId);
          }
        }
      }
    }
    return map;
  }, [catalog]);

  // Build nodeConfigs from app config (for TRACE/TAP badges)
  const { data: appConfig } = useApplicationConfig(appId, selectedEnv);
  const nodeConfigs = useMemo(() => {
    const map = new Map<string, NodeConfig>();
    if (appConfig?.tracedProcessors) {
      for (const pid of Object.keys(appConfig.tracedProcessors)) {
        map.set(pid, { traceEnabled: true });
      }
    }
    if (appConfig?.taps) {
      for (const tap of appConfig.taps) {
        if (tap.enabled) {
          const existing = map.get(tap.processorId);
          map.set(tap.processorId, { ...existing, tapExpression: tap.expression });
        }
      }
    }
    return map;
  }, [appConfig]);

  // Processor options for tap modal dropdown
  const processorOptions = useMemo(() => {
    const nodes = diagramQuery.data?.nodes;
    if (!nodes) return [];
    return (nodes as Array<{ id?: string; label?: string }>)
      .filter(n => n.id)
      .map(n => ({ value: n.id!, label: n.label || n.id! }));
  }, [diagramQuery.data]);

  // Tap modal state
  const [tapModalOpen, setTapModalOpen] = useState(false);
  const [tapModalTarget, setTapModalTarget] = useState<string | undefined>();
  const [editingTap, setEditingTap] = useState<TapDefinition | null>(null);

  const updateConfig = useUpdateApplicationConfig();
  const { toast } = useToast();

  const handleTapSave = useCallback((updatedConfig: typeof appConfig) => {
    if (!updatedConfig) return;
    updateConfig.mutate({ config: updatedConfig, environment: selectedEnv }, {
      onSuccess: (saved: ConfigUpdateResponse) => {
        if (saved.pushResult.success) {
          toast({ title: 'Tap configuration saved', description: `Pushed to ${saved.pushResult.total}/${saved.pushResult.total} agents (v${saved.config.version})`, variant: 'success' });
        } else {
          const failed = [...saved.pushResult.responses.filter(r => r.status !== 'SUCCESS').map(r => r.agentId), ...saved.pushResult.timedOut];
          toast({ title: 'Tap configuration saved — partial push failure', description: `${saved.pushResult.responded}/${saved.pushResult.total} responded. Failed: ${failed.join(', ')}`, variant: 'warning', duration: 86_400_000 });
        }
      },
      onError: () => {
        toast({ title: 'Tap update failed', description: 'Could not save configuration', variant: 'error', duration: 86_400_000 });
      },
    });
  }, [updateConfig, toast]);

  const handleTapDelete = useCallback((tap: TapDefinition) => {
    if (!appConfig) return;
    const taps = appConfig.taps.filter(t => t.tapId !== tap.tapId);
    updateConfig.mutate({ config: { ...appConfig, taps }, environment: selectedEnv }, {
      onSuccess: (saved: ConfigUpdateResponse) => {
        if (saved.pushResult.success) {
          toast({ title: 'Tap deleted', description: `${tap.attributeName} removed — pushed to ${saved.pushResult.total}/${saved.pushResult.total} agents (v${saved.config.version})`, variant: 'success' });
        } else {
          const failed = [...saved.pushResult.responses.filter(r => r.status !== 'SUCCESS').map(r => r.agentId), ...saved.pushResult.timedOut];
          toast({ title: 'Tap deleted — partial push failure', description: `${saved.pushResult.responded}/${saved.pushResult.total} responded. Failed: ${failed.join(', ')}`, variant: 'warning', duration: 86_400_000 });
        }
      },
      onError: () => {
        toast({ title: 'Tap delete failed', description: 'Could not save configuration', variant: 'error', duration: 86_400_000 });
      },
    });
  }, [appConfig, updateConfig, toast]);

  const handleNodeAction = useCallback((nodeId: string, action: NodeAction) => {
    if (action === 'configure-tap') {
      if (!appConfig) return;
      // Check if there's an existing tap for this processor
      const existing = appConfig.taps?.find(t => t.processorId === nodeId) ?? null;
      setEditingTap(existing);
      setTapModalTarget(nodeId);
      setTapModalOpen(true);
    } else if (action === 'toggle-trace') {
      if (!appConfig) return;
      const newMap = useTracingStore.getState().toggleProcessor(appId, nodeId);
      const enabled = nodeId in newMap;
      const tracedProcessors: Record<string, string> = {};
      for (const [k, v] of Object.entries(newMap)) tracedProcessors[k] = v;
      updateConfig.mutate({ config: {
        ...appConfig,
        tracedProcessors,
      }, environment: selectedEnv }, {
        onSuccess: (saved: ConfigUpdateResponse) => {
          if (saved.pushResult.success) {
            toast({ title: `Tracing ${enabled ? 'enabled' : 'disabled'}`, description: `${nodeId} — pushed to ${saved.pushResult.total}/${saved.pushResult.total} agents (v${saved.config.version})`, variant: 'success' });
          } else {
            const failed = [...saved.pushResult.responses.filter(r => r.status !== 'SUCCESS').map(r => r.agentId), ...saved.pushResult.timedOut];
            toast({ title: `Tracing update — partial push failure`, description: `${saved.pushResult.responded}/${saved.pushResult.total} responded. Failed: ${failed.join(', ')}`, variant: 'warning', duration: 86_400_000 });
          }
        },
        onError: () => {
          useTracingStore.getState().toggleProcessor(appId, nodeId);
          toast({ title: 'Tracing update failed', description: 'Could not save configuration', variant: 'error', duration: 86_400_000 });
        },
      });
    }
  }, [appId, appConfig, updateConfig, toast]);

  const tapModal = appConfig && (
    <TapConfigModal
      open={tapModalOpen}
      onClose={() => setTapModalOpen(false)}
      tap={editingTap}
      processorOptions={processorOptions}
      defaultProcessorId={tapModalTarget}
      application={appId}
      environment={selectedEnv}
      config={appConfig}
      onSave={handleTapSave}
      onDelete={handleTapDelete}
    />
  );

  // Exchange selected: show header + execution diagram
  if (exchangeId && detail) {
    return (
      <>
        <ExchangeHeader detail={detail} onCorrelatedSelect={onCorrelatedSelect} onClearSelection={onClearSelection} />
        <ExecutionDiagram
          executionId={exchangeId}
          executionDetail={detail}
          knownRouteIds={knownRouteIds}
          endpointRouteMap={endpointRouteMap}
          onNodeAction={canControl ? handleNodeAction : undefined}
          nodeConfigs={nodeConfigs}
        />
        {tapModal}
      </>
    );
  }

  // No exchange selected: show topology-only diagram with route control bar
  if (diagramQuery.data) {
    return (
      <>
        {canControl && (hasRouteControl || hasReplay) && (
          <RouteControlBar
            application={appId}
            routeId={routeId}
            routeState={routeState}
            hasRouteControl={hasRouteControl}
            hasReplay={hasReplay}
          />
        )}
        <ProcessDiagram
          application={appId}
          routeId={routeId}
          diagramLayout={diagramQuery.data}
          knownRouteIds={knownRouteIds}
          endpointRouteMap={endpointRouteMap}
          onNodeAction={canControl ? handleNodeAction : undefined}
          nodeConfigs={nodeConfigs}
        />
        {tapModal}
      </>
    );
  }

  return (
    <div className={styles.emptyRight}>
      Loading diagram...
    </div>
  );
}
