import React, { useMemo, useRef, useEffect, useState } from 'react';
import {
  ArrowLeft, ArrowRight, Crosshair, Globe, GitMerge, MousePointer2, Plus,
  Server, Trash2, X, Shield,
} from '../../icons';
import type { Net, MazeNode, Edge } from './types';
import { DEFAULT_SERVICES } from './data';
import ServiceConfigForm from '../ServiceConfigForm';
import type { ApiError } from '../../utils/api';

export type Selection =
  | { type: 'net'; id: string }
  | { type: 'node'; id: string }
  | { type: 'edge'; id: string }
  | { type: 'service'; id: string; nodeId: string }
  | null;

interface Props {
  selection: Selection;
  nets: Net[];
  nodes: MazeNode[];
  edges: Edge[];
  /** Topology ID (MazeNET-only) — required for the schema-driven service
   *  config form to hit the per-topology REST path. Omit for fleet. */
  topologyId?: string;
  topologyStatus?: string;
  onClose?: () => void;
  onDeleteNet?: (id: string) => void;
  onDeleteNode?: (id: string) => void;
  onDeleteEdge?: (id: string) => void;
  onRemoveService?: (nodeId: string, slug: string) => void;
  // Live (post-deploy) service mutation, hitting W3 endpoints directly.
  // Distinct from onRemoveService which queues a design-time graph
  // mutation.  Both can coexist; the inspector picks based on
  // topologyStatus (active/degraded → live, pending/anything else →
  // design-time only).  Wiring these props from MazeNET.tsx is the
  // single switch that turns chips into live controls.
  /** Trigger the schema-driven add-service flow.  Synchronous: opens
   *  the AddServiceConfigModal at the page level (or auto-confirms if
   *  the service has no schema fields).  Errors surface inside the modal. */
  onLiveAddService?: (nodeName: string, slug: string) => void;
  onLiveRemoveService?: (nodeName: string, slug: string) => Promise<void>;
  /** Per-decky-eligible service slugs, fetched via useServiceRegistry. */
  availableServices?: string[];
  /** Toggle ``forwards_l3`` (gateway) on the selected decky.  When the
   *  topology is active/degraded the caller is responsible for the
   *  destructive-recreate confirm dialog and the ``force: true`` submit
   *  — this prop just relays the user's intent. */
  onToggleGateway?: (nodeId: string, nextValue: boolean) => Promise<void>;
  /** Tarpit controls — only shown when topology is active/degraded and node is a deployed decky. */
  onLiveTarpitEnable?: (nodeName: string, ports: number[], delayMs: number) => Promise<void>;
  onLiveTarpitDisable?: (nodeName: string) => Promise<void>;
  onAddDecky?: (netId: string) => void;
  setSelection?: (sel: Selection) => void;
  pendingChanges?: number;
  className?: string;
}

const Inspector: React.FC<Props> = ({
  selection, nets, nodes, edges, topologyId, topologyStatus, onClose,
  onDeleteNet, onDeleteNode, onDeleteEdge, onRemoveService,
  onLiveAddService, onLiveRemoveService, availableServices = [],
  onToggleGateway,
  onLiveTarpitEnable, onLiveTarpitDisable,
  onAddDecky, setSelection,
  pendingChanges = 0,
  className = '',
}) => {
  const liveOpsEnabled =
    !!onLiveAddService &&
    !!onLiveRemoveService &&
    (topologyStatus === 'active' || topologyStatus === 'degraded');
  const [addOpen, setAddOpen] = useState(false);
  const [addSlug, setAddSlug] = useState('');
  const [busy, setBusy] = useState<string | null>(null);  // slug currently mutating
  const [opError, setOpError] = useState<string | null>(null);

  // Tarpit state — local form, fires parent callbacks
  const [tarpitOpen, setTarpitOpen] = useState(false);
  const [tarpitPorts, setTarpitPorts] = useState('22');
  const [tarpitDelay, setTarpitDelay] = useState(30000);
  const tarpitEnabled = liveOpsEnabled && !!onLiveTarpitEnable && !!onLiveTarpitDisable;

  // Close tarpit form when selection changes
  const prevNodeId = useRef<string | undefined>(undefined);
  useEffect(() => {
    const nodeId = selection?.type === 'node' ? selection.id : undefined;
    if (nodeId !== prevNodeId.current) {
      prevNodeId.current = nodeId;
      setTarpitOpen(false);
    }
  }, [selection]);
  const net  = selection?.type === 'net'  ? nets.find((n) => n.id === selection.id)  : undefined;
  const node = selection?.type === 'node' ? nodes.find((n) => n.id === selection.id) : undefined;
  const edge = selection?.type === 'edge' ? edges.find((e) => e.id === selection.id) : undefined;
  const serviceSel = selection?.type === 'service' ? selection : undefined;
  const serviceMeta = serviceSel ? DEFAULT_SERVICES.find((s) => s.slug === serviceSel.id) : undefined;
  const serviceParent = serviceSel ? nodes.find((n) => n.id === serviceSel.nodeId) : undefined;
  const serviceParentNet = serviceParent ? nets.find((n) => n.id === serviceParent.netId) : undefined;

  const activeNetIds = useMemo(() => {
    const s = new Set<string>();
    edges.forEach((e) => {
      const f = nodes.find((n) => n.id === e.from);
      const t = nodes.find((n) => n.id === e.to);
      if (f) s.add(f.netId);
      if (t) s.add(t.netId);
    });
    return s;
  }, [edges, nodes]);

  const typeLabel = selection ? selection.type.toUpperCase() : 'IDLE';
  const isGateway = node?.kind === 'decky' && !!node.decky_config?.forwards_l3;
  const isObserved = node?.kind === 'observed';

  return (
    <aside className={`maze-inspector ${className}`}>
      <div className="maze-inspector-title">
        <Crosshair size={12} className="violet-accent" />
        <span>INSPECTOR</span>
        <span className="dim inspector-type-label">{typeLabel}</span>
        {onClose && (
          <button
            type="button"
            className="inspector-close-btn"
            onClick={onClose}
            title="Hide inspector"
          >
            <X size={12} />
          </button>
        )}
      </div>

      <div className="maze-inspector-body">
        {!selection && (
          <div className="inspector-empty">
            <MousePointer2 size={22} style={{ opacity: 0.4, marginBottom: 10 }} />
            <div>SELECT A NODE, NETWORK, OR EDGE</div>
            <div style={{ marginTop: 10, fontSize: '0.6rem', opacity: 0.5 }}>
              Right-click for actions
            </div>
          </div>
        )}

        {node && (
          <>
            <div className="inspector-head">
              <span className={`status-dot ${node.status}`} />
              <span className="inspector-head-title">{node.name}</span>
              <span className="chip violet inspector-head-chip">{node.archetype}</span>
            </div>
            <div className="kvs">
              <div className="k">NETWORK</div>
              <div className="v violet-accent">
                {nets.find((nn) => nn.id === node.netId)?.label ?? node.netId}
              </div>
              <div className="k">STATUS</div>
              <div className="v">{node.status.toUpperCase()}</div>
              <div className="k">SERVICES</div>
              <div className="v">
                <div className="inspector-service-row">
                  {node.services.length === 0 && <span className="dim">—</span>}
                  {node.services.map((s) => (
                    <span key={s} className="service-tag" style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                      <span>{s}</span>
                      {liveOpsEnabled && node.kind !== 'observed' && (
                        <button
                          type="button"
                          title={`Remove ${s} (live)`}
                          disabled={busy === s}
                          onClick={async () => {
                            setOpError(null);
                            setBusy(s);
                            try {
                              await onLiveRemoveService!(node.name, s);
                            } catch (err) {
                              const msg = (err as ApiError)?.response?.data?.detail
                                ?? 'Remove failed.';
                              setOpError(msg);
                            } finally {
                              setBusy(null);
                            }
                          }}
                          style={{
                            background: 'transparent', border: 'none', padding: 0,
                            color: 'inherit', cursor: busy === s ? 'wait' : 'pointer',
                            opacity: busy === s ? 0.4 : 0.7, lineHeight: 1,
                          }}
                        >
                          <X size={9} />
                        </button>
                      )}
                    </span>
                  ))}
                  {liveOpsEnabled && node.kind !== 'observed' && !addOpen && (
                    <button
                      type="button"
                      className="service-tag"
                      onClick={() => { setAddOpen(true); setAddSlug(''); }}
                      style={{ cursor: 'pointer', borderStyle: 'dashed' }}
                      title="Add service (live)"
                    >
                      <Plus size={10} /> ADD
                    </button>
                  )}
                </div>
                {liveOpsEnabled && addOpen && (
                  <div style={{ display: 'flex', gap: 6, marginTop: 6, alignItems: 'center' }}>
                    <select
                      value={addSlug}
                      onChange={(e) => setAddSlug(e.target.value)}
                      style={{
                        flex: 1, fontSize: '0.75rem', padding: '4px 6px',
                        background: 'rgba(255,255,255,0.04)',
                        border: '1px solid var(--border-color, #30363d)',
                        color: 'var(--text-color)',
                      }}
                    >
                      <option value="">— pick a service —</option>
                      {availableServices
                        .filter((s) => !(node.services as readonly string[]).includes(s))
                        .map((s) => (
                          <option key={s} value={s}>{s}</option>
                        ))}
                    </select>
                    <button
                      type="button"
                      disabled={!addSlug || busy === addSlug}
                      onClick={() => {
                        if (!addSlug) return;
                        setOpError(null);
                        // Fire-and-forget: opens the schema-driven config
                        // modal at the page level (or auto-confirms for
                        // schema-less services). Errors surface in the modal.
                        onLiveAddService!(node.name, addSlug);
                        setAddOpen(false);
                        setAddSlug('');
                      }}
                      style={{
                        padding: '4px 10px', fontSize: '0.7rem',
                        border: '1px solid var(--accent-color, #00ff88)',
                        background: 'var(--accent-color, #00ff88)',
                        color: 'var(--bg-color, #0d1117)',
                        cursor: busy === addSlug ? 'wait' : 'pointer',
                        opacity: !addSlug || busy === addSlug ? 0.5 : 1,
                        textTransform: 'uppercase',
                      }}
                    >
                      ADD
                    </button>
                    <button
                      type="button"
                      onClick={() => { setAddOpen(false); setAddSlug(''); }}
                      style={{
                        padding: '4px 10px', fontSize: '0.7rem',
                        border: '1px solid var(--dim-color)',
                        background: 'transparent', color: 'var(--dim-color)',
                        cursor: 'pointer', textTransform: 'uppercase',
                      }}
                    >
                      CANCEL
                    </button>
                  </div>
                )}
                {opError && (
                  <div style={{ color: '#ff5555', fontSize: '0.7rem', marginTop: 6 }}>{opError}</div>
                )}
              </div>
            </div>
            <div>
              <div className="type-label inspector-section-label">CONNECTIONS</div>
              {edges.filter((e) => e.from === node.id || e.to === node.id).map((e) => {
                const otherId = e.from === node.id ? e.to : e.from;
                const other = nodes.find((n) => n.id === otherId);
                const Arrow = e.from === node.id ? ArrowRight : ArrowLeft;
                return (
                  <div key={e.id} className="inspector-conn-row">
                    <Arrow size={10} className={e.traffic === 'hot' ? 'alert-text' : 'dim'} />
                    <span>{other?.name ?? '—'}</span>
                    <span className="chip dim-chip inspector-conn-chip">{e.traffic}</span>
                  </div>
                );
              })}
              {edges.filter((e) => e.from === node.id || e.to === node.id).length === 0 && (
                <div className="dim inspector-empty-line">NO EDGES</div>
              )}
            </div>
            {onToggleGateway && !isObserved && (
              <button
                type="button"
                className={`maze-btn small ${isGateway ? 'alert' : ''}`}
                disabled={busy === '__gateway__'}
                title={
                  isGateway
                    ? 'Demote this decky from gateway (forwards_l3=false)'
                    : 'Promote this decky to gateway (forwards_l3=true)'
                }
                onClick={async () => {
                  const next = !isGateway;
                  // forwards_l3 flip on a deployed topology recreates
                  // the base container — destructive.  Confirm before
                  // hitting the API; the caller (MazeNET.tsx) submits
                  // with force: true on active topologies.
                  const live = topologyStatus === 'active' || topologyStatus === 'degraded';
                  if (live) {
                    const ok = window.confirm(
                      `${next ? 'Promote' : 'Demote'} ${node.name} ${next ? 'to' : 'from'} gateway?\n\n` +
                      'This recreates the base container to apply the new port-publishing config. ' +
                      'In-container state is lost; active sessions to it drop.',
                    );
                    if (!ok) return;
                  }
                  setOpError(null);
                  setBusy('__gateway__');
                  try {
                    await onToggleGateway(node.id, next);
                  } catch (err) {
                    const msg = (err as ApiError)?.response?.data?.detail
                      ?? 'Gateway toggle failed.';
                    setOpError(msg);
                  } finally {
                    setBusy(null);
                  }
                }}
              >
                <Shield size={12} />
                {busy === '__gateway__'
                  ? (isGateway ? 'DEMOTING…' : 'PROMOTING…')
                  : (isGateway ? 'DEMOTE GATEWAY' : 'PROMOTE TO GATEWAY')}
              </button>
            )}
            {tarpitEnabled && !isObserved && (
              <div className="inspector-tarpit-wrap">
                <div className="inspector-tarpit-row">
                  <button
                    type="button"
                    className={`maze-btn small ${tarpitOpen ? 'active' : ''}`}
                    disabled={busy === '__tarpit__'}
                    onClick={() => setTarpitOpen((o) => !o)}
                    title="Configure tc netem tarpit on this decky"
                  >
                    <Shield size={12} />
                    {tarpitOpen ? 'CANCEL' : 'TARPIT'}
                  </button>
                  <button
                    type="button"
                    className="maze-btn alert small"
                    disabled={busy === '__tarpit__'}
                    title="Remove active tarpit rule"
                    onClick={async () => {
                      setOpError(null);
                      setBusy('__tarpit__');
                      try {
                        await onLiveTarpitDisable!(node.name);
                      } catch (err) {
                        const msg = (err as ApiError)
                          ?.response?.data?.detail ?? 'Tarpit disable failed.';
                        setOpError(msg);
                      } finally {
                        setBusy(null);
                      }
                    }}
                  >
                    {busy === '__tarpit__' ? '…' : 'DISABLE'}
                  </button>
                </div>
                {tarpitOpen && (
                  <div className="inspector-tarpit-form">
                    <div className="inspector-tarpit-field">
                      <label className="type-label">PORTS</label>
                      <input
                        className="maze-input"
                        value={tarpitPorts}
                        placeholder="22,80,443"
                        onChange={(e) => setTarpitPorts(e.target.value)}
                      />
                    </div>
                    <div className="inspector-tarpit-field">
                      <label className="type-label">
                        DELAY · {tarpitDelay >= 1000
                          ? `${(tarpitDelay / 1000).toFixed(0)}s`
                          : `${tarpitDelay}ms`}
                      </label>
                      <input
                        type="range"
                        min={100}
                        max={60000}
                        step={100}
                        value={tarpitDelay}
                        onChange={(e) => setTarpitDelay(parseInt(e.target.value, 10))}
                        style={{ width: '100%' }}
                      />
                    </div>
                    <button
                      type="button"
                      className="maze-btn alert small"
                      disabled={busy === '__tarpit__' || !tarpitPorts.trim()}
                      onClick={async () => {
                        const ports = tarpitPorts
                          .split(',')
                          .map((p) => parseInt(p.trim(), 10))
                          .filter((p) => !isNaN(p) && p > 0 && p <= 65535);
                        if (!ports.length) return;
                        setOpError(null);
                        setBusy('__tarpit__');
                        try {
                          await onLiveTarpitEnable!(node.name, ports, tarpitDelay);
                          setTarpitOpen(false);
                        } catch (err) {
                          const msg = (err as ApiError)
                            ?.response?.data?.detail ?? 'Tarpit enable failed.';
                          setOpError(msg);
                        } finally {
                          setBusy(null);
                        }
                      }}
                    >
                      {busy === '__tarpit__' ? 'APPLYING…' : 'APPLY TARPIT'}
                    </button>
                  </div>
                )}
              </div>
            )}
            {onDeleteNode && (
              <button
                type="button"
                className="maze-btn alert small"
                disabled={isObserved || isGateway}
                title={
                  isObserved ? 'observed entity — not a deployed decky'
                  : isGateway ? 'DMZ gateway — pinned to its DMZ network'
                  : undefined
                }
                onClick={() => !isObserved && !isGateway && onDeleteNode(node.id)}
              >
                <Trash2 size={12} /> REMOVE FROM GRAPH
              </button>
            )}
          </>
        )}

        {net && (
          <>
            <div className="inspector-head">
              {net.kind === 'internet'
                ? <Globe size={14} className="violet-accent" />
                : <GitMerge size={14} className="violet-accent" />}
              <span className="inspector-head-title">{net.label}</span>
              {net.kind !== 'internet' && !activeNetIds.has(net.id) && (
                <span className="chip-mini inspector-head-chip">INACTIVE</span>
              )}
            </div>
            <div className="kvs">
              <div className="k">KIND</div><div className="v">{net.kind.toUpperCase()}</div>
              <div className="k">CIDR</div><div className="v">{net.cidr}</div>
              <div className="k">DECKIES</div>
              <div className="v" style={{ fontWeight: 700 }}>
                {nodes.filter((n) => n.netId === net.id).length}
              </div>
            </div>
            <div>
              <div className="type-label inspector-section-label">MEMBERS</div>
              {nodes.filter((n) => n.netId === net.id).map((n) => (
                <div
                  key={n.id}
                  className="inspector-member-row"
                  onClick={() => setSelection?.({ type: 'node', id: n.id })}
                >
                  <span className={`status-dot ${n.status}`} />
                  <span>{n.name}</span>
                  <span className="dim inspector-member-arch">{n.archetype}</span>
                </div>
              ))}
              {nodes.filter((n) => n.netId === net.id).length === 0 && (
                <div className="dim inspector-empty-line">NO MEMBERS</div>
              )}
            </div>
            {net.kind !== 'internet' && onAddDecky && (
              <button type="button" className="maze-btn small" onClick={() => onAddDecky(net.id)}>
                <Plus size={10} /> ADD DECKY
              </button>
            )}
            {net.kind !== 'internet' && onDeleteNet && (
              <button
                type="button"
                className="maze-btn alert small"
                onClick={() => onDeleteNet(net.id)}
              >
                <Trash2 size={10} /> REMOVE NETWORK
              </button>
            )}
          </>
        )}

        {edge && (
          <>
            <div className="inspector-head">
              <Server size={14} className="violet-accent" />
              <span className="inspector-head-title">EDGE · {edge.id.slice(0, 8)}</span>
            </div>
            <div className="kvs">
              <div className="k">FROM</div>
              <div className="v">{nodes.find((n) => n.id === edge.from)?.name ?? edge.from}</div>
              <div className="k">TO</div>
              <div className="v">{nodes.find((n) => n.id === edge.to)?.name ?? edge.to}</div>
              <div className="k">TRAFFIC</div>
              <div className="v">{edge.traffic.toUpperCase()}</div>
              {edge.label && (
                <>
                  <div className="k">LABEL</div>
                  <div className="v">{edge.label}</div>
                </>
              )}
            </div>
            {onDeleteEdge && (
              <button
                type="button"
                className="maze-btn alert small"
                onClick={() => onDeleteEdge(edge.id)}
              >
                <Trash2 size={10} /> CUT EDGE
              </button>
            )}
          </>
        )}

        {serviceSel && (
          <>
            <div className="inspector-head">
              <Shield
                size={14}
                className={serviceMeta?.risk === 'high' ? 'alert-text' : 'violet-accent'}
              />
              <span className="inspector-head-title">
                {serviceMeta?.name ?? serviceSel.id.toUpperCase()}
              </span>
              {serviceMeta && (
                <span className={`chip inspector-head-chip ${
                  serviceMeta.risk === 'high' ? 'alert'
                  : serviceMeta.risk === 'med' ? 'violet'
                  : 'dim-chip'
                }`}>
                  {serviceMeta.risk.toUpperCase()}
                </span>
              )}
            </div>
            <div className="kvs">
              <div className="k">EXPOSED ON</div>
              <div className="v violet-accent">{serviceParent?.name ?? '—'}</div>
              <div className="k">PROTOCOL</div>
              <div className="v">{(serviceMeta?.proto ?? '—').toUpperCase()}</div>
              <div className="k">PORT</div>
              <div className="v" style={{ fontWeight: 700 }}>{serviceMeta?.port ?? '—'}</div>
              <div className="k">SUBNET</div>
              <div className="v">{serviceParentNet?.label ?? '—'}</div>
            </div>
            {topologyId && serviceParent && serviceParent.kind !== 'observed' && (
              <ServiceConfigForm
                key={`${serviceParent.name}:${serviceSel.id}`}
                deckyName={serviceParent.name}
                serviceSlug={serviceSel.id}
                topologyId={topologyId}
                currentConfig={
                  ((serviceParent.decky_config as { service_config?: Record<string, Record<string, unknown>> } | undefined)
                    ?.service_config?.[serviceSel.id]) ?? {}
                }
              />
            )}
            {onRemoveService && serviceParent && serviceParent.kind !== 'observed' && (
              <button
                type="button"
                className="maze-btn alert small"
                disabled={topologyStatus === 'degraded'}
                title={topologyStatus === 'degraded' ? 'topology degraded — mutations blocked' : undefined}
                onClick={() => onRemoveService(serviceSel.nodeId, serviceSel.id)}
              >
                <Trash2 size={10} /> REMOVE SERVICE
              </button>
            )}
          </>
        )}

        {pendingChanges > 0 && (
          <div className="inspector-diff-block">
            <div className="type-label inspector-section-label">PENDING DIFF</div>
            <div className="maze-diff">
              <span className="ctx">  +{pendingChanges} graph mutation(s)</span>{'\n'}
              <span className="ctx">  networks: {nets.length}</span>{'\n'}
              <span className="ctx">  deckies:  {nodes.length}</span>{'\n'}
              <span className="ctx">  paths:    {edges.length}</span>
            </div>
          </div>
        )}

        {topologyStatus && !selection && (
          <div className="kvs inspector-status-block">
            <div className="k">TOPOLOGY</div>
            <div className="v">{topologyStatus.toUpperCase()}</div>
          </div>
        )}
      </div>
    </aside>
  );
};

export default Inspector;
