"use client";

import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
  ReactFlow,
  Background,
  Controls,
  Handle,
  MiniMap,
  Position,
  type Node,
  type Edge,
  type NodeProps,
  BackgroundVariant,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { api, type ChargePoint, type ConnectorSummary } from "@/lib/api";
import { useSession } from "@/lib/auth-client";
import dayjs from "@/lib/dayjs";
import { Clock, EvCharger, Plug, Zap } from "lucide-react";

// ── Connection status ─────────────────────────────────────────────────────

type ConnectionStatus = "online" | "stale" | "offline";

function getStatus(cp: ChargePoint, connected: string[]): ConnectionStatus {
  if (cp.transportStatus === "unavailable") return "stale";
  if (cp.transportStatus !== "online" || !connected.includes(cp.chargePointIdentifier))
    return "offline";
  if (!cp.lastHeartbeatAt) return "stale";
  return dayjs().diff(dayjs(cp.lastHeartbeatAt), "minute") < 5 ? "online" : "stale";
}

const STATUS_CONFIG: Record<
  ConnectionStatus,
  { color: string; edgeColor: string; label: string; animated: boolean }
> = {
  online: { color: "#22c55e", edgeColor: "#22c55e", label: "在线", animated: true },
  stale: { color: "#f59e0b", edgeColor: "#f59e0b", label: "通道异常", animated: true },
  offline: { color: "#71717a", edgeColor: "#9ca3af", label: "离线", animated: false },
};

const CONNECTOR_STATUS_COLOR: Record<string, string> = {
  Available: "#22c55e",
  Charging: "#3b82f6",
  Preparing: "#f59e0b",
  Finishing: "#f59e0b",
  SuspendedEV: "#f59e0b",
  SuspendedEVSE: "#f59e0b",
  Reserved: "#a855f7",
  Faulted: "#ef4444",
  Unavailable: "#71717a",
  Occupied: "#f59e0b",
};

const CONNECTOR_STATUS_LABEL: Record<string, string> = {
  Available: "空闲",
  Charging: "充电中",
  Preparing: "准备中",
  Finishing: "结束中",
  SuspendedEV: "EV 暂停",
  SuspendedEVSE: "EVSE 暂停",
  Reserved: "已预约",
  Faulted: "故障",
  Unavailable: "不可用",
  Occupied: "占用",
};

// ── CSMS Hub Node ─────────────────────────────────────────────────────────

type CsmsNodeData = { connectedCount: number; totalCount: number };

function CsmsHubNode({ data }: NodeProps) {
  const { connectedCount, totalCount } = data as CsmsNodeData;
  return (
    <div className="min-w-[200px] rounded-xl border border-accent/70 bg-accent px-5 py-4 text-accent-foreground shadow-lg shadow-accent/25">
      <Handle type="source" position={Position.Bottom} style={{ opacity: 0 }} />
      <div className="mb-2.5 flex items-center gap-2.5">
        <div className="flex rounded-lg bg-accent-foreground/15 p-1.5">
          <Zap size={16} />
        </div>
        <div>
          <div className="text-[13px] font-semibold leading-tight">CSMS 服务器</div>
          <div className="mt-0.5 text-[11px] opacity-75">Helios EVCS</div>
        </div>
      </div>
      <div className="flex items-center gap-1.5 rounded-lg bg-accent-foreground/12 px-2.5 py-1.5">
        <span
          className="size-2 shrink-0 rounded-full"
          style={{
            background: connectedCount > 0 ? "#22c55e" : "#71717a",
            boxShadow: connectedCount > 0 ? "0 0 6px #22c55e" : "none",
          }}
        />
        <span className="text-xs font-medium">
          {connectedCount} / {totalCount} 设备在线
        </span>
      </div>
    </div>
  );
}

// ── Charge Point Node ─────────────────────────────────────────────────────

type ChargePointNodeData = { cp: ChargePoint; status: ConnectionStatus; isAdmin: boolean };

function ChargePointNode({ data }: NodeProps) {
  const { cp, status, isAdmin } = data as ChargePointNodeData;
  const cfg = STATUS_CONFIG[status];
  const hbText = cp.lastHeartbeatAt ? dayjs(cp.lastHeartbeatAt).fromNow() : "从未";

  return (
    <div
      className="flex min-w-[190px] flex-col rounded-xl bg-surface px-2 py-2"
      style={{
        border: `1.5px solid ${status === "offline" ? "var(--color-border)" : cfg.color + "80"}`,
        boxShadow:
          status !== "offline" ? `0 2px 12px ${cfg.color}25` : "0 1px 4px rgba(0,0,0,0.08)",
      }}
    >
      <Handle type="target" position={Position.Top} style={{ opacity: 0 }} />
      <Handle type="source" position={Position.Bottom} style={{ opacity: 0 }} />

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-1">
          <EvCharger className="size-4 shrink-0 text-muted" />
          <div className="flex flex-col">
            <span className="text-[13px] font-semibold tracking-tight text-foreground">
              {cp.deviceName ?? cp.chargePointIdentifier}
            </span>
            {isAdmin && cp.deviceName && (
              <span className="font-mono text-[10px] text-muted">{cp.chargePointIdentifier}</span>
            )}
          </div>
        </div>
        <span
          className="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] font-medium"
          style={{ background: cfg.color + "18", color: cfg.color }}
        >
          <span
            className="inline-block size-1.5 shrink-0 rounded-full"
            style={{
              background: cfg.color,
              boxShadow: status !== "offline" ? `0 0 5px ${cfg.color}` : "none",
            }}
          />
          {cfg.label}
        </span>
      </div>

      {(cp.chargePointVendor || cp.chargePointModel) && (
        <div className="text-[10px] text-muted">
          {[cp.chargePointVendor, cp.chargePointModel].filter(Boolean).join(" · ")}
        </div>
      )}

      <div className="mt-1 flex items-center gap-1 text-[9px] text-muted">
        <Clock size={10} />
        <span>心跳 {hbText}</span>
      </div>
    </div>
  );
}

// ── Connector Node ────────────────────────────────────────────────────────

type ConnectorNodeData = { conn: ConnectorSummary; cpStatus: ConnectionStatus };

function ConnectorNode({ data }: NodeProps) {
  const { conn, cpStatus } = data as ConnectorNodeData;
  const color =
    cpStatus === "offline" ? "#71717a" : (CONNECTOR_STATUS_COLOR[conn.status] ?? "#f59e0b");
  const label = CONNECTOR_STATUS_LABEL[conn.status] ?? conn.status;
  const isActive = conn.status === "Charging";

  return (
    <div
      className="flex min-w-[88px] flex-col items-center gap-1.5 rounded-lg bg-surface px-2.5 py-2"
      style={{
        border: `1.5px solid ${color}80`,
        boxShadow: isActive ? `0 0 10px ${color}40` : "0 1px 3px rgba(0,0,0,0.06)",
      }}
    >
      <Handle type="target" position={Position.Top} style={{ opacity: 0 }} />
      <div className="flex items-center gap-1">
        <Plug className="size-3 shrink-0 text-muted" />
        <span className="text-xs font-semibold text-foreground">#{conn.connectorId}</span>
      </div>
      <div
        className="flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-medium"
        style={{ background: color + "18", color }}
      >
        <span
          className="inline-block size-[5px] shrink-0 rounded-full"
          style={{ background: color, boxShadow: isActive ? `0 0 4px ${color}` : "none" }}
        />
        {label}
      </div>
    </div>
  );
}

// ── Layout constants ──────────────────────────────────────────────────────

const CP_W = 200;
const CP_H = 70; // matches actual rendered height
const CP_GAP_X = 60;
const CP_GAP_Y = 100;
const CONN_W = 96;
const CONN_H = 62;
const CONN_GAP_X = 12;
const CONN_ROW_GAP = 48;
const COLS = 5;
const CSMS_H = 88;

/** Horizontal space a charge point needs (driven by its connector spread). */
function slotWidth(cp: ChargePoint): number {
  const n = cp.connectors.length;
  if (n === 0) return CP_W;
  return Math.max(CP_W, n * CONN_W + (n - 1) * CONN_GAP_X);
}

function buildGraph(
  chargePoints: ChargePoint[],
  connectedIdentifiers: string[],
  isAdmin: boolean,
): { nodes: Node[]; edges: Edge[] } {
  // Group into rows
  const rows: ChargePoint[][] = [];
  for (let i = 0; i < chargePoints.length; i += COLS) {
    rows.push(chargePoints.slice(i, i + COLS));
  }

  // Width of each row (accounting for variable slot widths)
  const rowWidths = rows.map((rowCps) =>
    rowCps.reduce((sum, cp, ci) => sum + slotWidth(cp) + (ci > 0 ? CP_GAP_X : 0), 0),
  );
  const maxRowWidth = Math.max(...rowWidths, CP_W);
  const csmsX = maxRowWidth / 2 - CP_W / 2;

  const nodes: Node[] = [
    {
      id: "csms",
      type: "csmsHub",
      position: { x: csmsX, y: 0 },
      data: { connectedCount: connectedIdentifiers.length, totalCount: chargePoints.length },
      draggable: true,
      width: CP_W,
      height: CSMS_H,
    },
  ];
  const edges: Edge[] = [];

  let cpY = CSMS_H + CP_GAP_Y;

  rows.forEach((rowCps, _rowIdx) => {
    const rowW = rowWidths[_rowIdx];
    // Center narrower rows under CSMS
    let curX = (maxRowWidth - rowW) / 2;

    // tallest connector row determines next cpY offset
    const maxConnSpread = Math.max(
      ...rowCps.map((cp) => (cp.connectors.length > 0 ? CONN_ROW_GAP + CONN_H : 0)),
    );

    rowCps.forEach((cp) => {
      const sw = slotWidth(cp);
      const cpX = curX + (sw - CP_W) / 2; // center CP node within its slot

      const status = getStatus(cp, connectedIdentifiers);
      const cfg = STATUS_CONFIG[status];

      nodes.push({
        id: cp.id,
        type: "chargePoint",
        position: { x: cpX, y: cpY },
        data: { cp, status, isAdmin },
        draggable: true,
        width: CP_W,
        height: CP_H,
      });

      edges.push({
        id: `csms-${cp.id}`,
        source: "csms",
        target: cp.id,
        animated: cfg.animated,
        style: {
          stroke: cfg.edgeColor,
          strokeWidth: status === "offline" ? 1 : 2,
          strokeDasharray: status === "offline" ? "6 4" : undefined,
          opacity: status === "offline" ? 0.4 : 0.85,
        },
      });

      // Connector nodes — centered under their CP
      const sorted = [...cp.connectors].sort((a, b) => a.connectorId - b.connectorId);
      const n = sorted.length;
      if (n > 0) {
        const totalConnW = n * CONN_W + (n - 1) * CONN_GAP_X;
        const connStartX = cpX + CP_W / 2 - totalConnW / 2;
        const connY = cpY + CP_H + CONN_ROW_GAP;

        sorted.forEach((conn, ci) => {
          const connNodeId = `conn-${conn.id}`;
          const connColor =
            status === "offline" ? "#71717a" : (CONNECTOR_STATUS_COLOR[conn.status] ?? "#f59e0b");

          nodes.push({
            id: connNodeId,
            type: "connector",
            position: { x: connStartX + ci * (CONN_W + CONN_GAP_X), y: connY },
            data: { conn, cpStatus: status },
            draggable: true,
            width: CONN_W,
            height: CONN_H,
          });

          edges.push({
            id: `${cp.id}-${connNodeId}`,
            source: cp.id,
            target: connNodeId,
            animated: conn.status === "Charging",
            style: {
              stroke: connColor,
              strokeWidth: conn.status === "Charging" ? 2 : 1.5,
              opacity: status === "offline" ? 0.35 : 0.7,
              strokeDasharray: status === "offline" ? "4 3" : undefined,
            },
          });
        });
      }

      curX += sw + CP_GAP_X;
    });

    cpY += CP_H + CP_GAP_Y + maxConnSpread;
  });

  return { nodes, edges };
}

// ── Node type registry (stable reference — must be outside component) ─────

const nodeTypes = {
  csmsHub: CsmsHubNode,
  chargePoint: ChargePointNode,
  connector: ConnectorNode,
};

// ── Main component ────────────────────────────────────────────────────────

export default function TopologyFlow() {
  const { data: chargePoints = [], isLoading } = useQuery({
    queryKey: ["chargePoints"],
    queryFn: () => api.chargePoints.list(),
    refetchInterval: 10_000,
  });

  const { data: connections } = useQuery({
    queryKey: ["chargePoints", "connections"],
    queryFn: () => api.chargePoints.connections(),
    refetchInterval: 10_000,
  });

  const { data: sessionData } = useSession();
  const isAdmin = sessionData?.user?.role === "admin";

  const connectedIds = connections?.connectedIdentifiers ?? [];

  const { nodes, edges } = useMemo(
    () => buildGraph(chargePoints, connectedIds, isAdmin),
    [chargePoints, connectedIds, isAdmin],
  );

  if (isLoading) {
    return (
      <div className="flex size-full items-center justify-center text-sm text-muted">加载中…</div>
    );
  }

  if (chargePoints.length === 0) {
    return (
      <div className="flex size-full flex-col items-center justify-center gap-2 text-muted">
        <EvCharger className="size-10 opacity-30" />
        <p className="text-sm">暂无充电桩</p>
        <p className="text-xs opacity-60">在「充电桩」页面添加设备后将显示在此处</p>
      </div>
    );
  }

  return (
    <div className="size-full">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        nodeTypes={nodeTypes}
        fitView
        fitViewOptions={{ padding: 0.15 }}
        minZoom={0.15}
        maxZoom={2}
        proOptions={{ hideAttribution: true }}
      >
        <Background
          variant={BackgroundVariant.Dots}
          gap={20}
          size={1}
          color="var(--color-border)"
        />
        <Controls showInteractive={false} />
        <MiniMap
          nodeColor={(n) => {
            if (n.type === "csmsHub") return "#6366f1";
            if (n.type === "chargePoint") {
              const status = (n.data as ChargePointNodeData).status;
              return STATUS_CONFIG[status].color;
            }
            if (n.type === "connector") {
              const { conn, cpStatus } = n.data as ConnectorNodeData;
              return cpStatus === "offline"
                ? "#71717a"
                : (CONNECTOR_STATUS_COLOR[conn.status] ?? "#f59e0b");
            }
            return "#888";
          }}
          nodeStrokeWidth={0}
          style={{
            background: "var(--color-surface-secondary)",
            border: "1px solid var(--color-border)",
          }}
          // zoomable
          // pannable
        />
      </ReactFlow>
    </div>
  );
}
