import { ReactNode, useContext } from 'react';
import { Handle, Position, NodeProps,  useReactFlow} from '@xyflow/react';
import { AppNode, AppEdge, AppGraph, NodeData } from '../types/graph';
import {Settings, SettingsContext} from '../types/settings'
import StringBuilder from '../utils/StringBuilder';

import { CIDR, IPUtils } from '../utils/iputils';
import { tryDerivePublicKey } from '../utils/wireguardConfig'
import './CustomNode.css';
import toast from 'react-hot-toast';

class ConfigResult {
  constructor(
    public success: boolean,
    public config?: string,
    public error?: string
  ) {}
}

// function mapAddressToCIDR(nodeIds : string[], getAddress: GetAddress) : Record<string, CIDR> {
//   const nodeIdToCIDR : Record<string, CIDR> = {};
//   for(const nodeId of nodeIds) {
//     const address = getAddress(nodeId);
//     if(!address) continue;
//     const result = IPUtils.parse(address);
//     const cidr = result.cidr;
//     if(!cidr) {
//       throw new Error("节点地址无效");
//     }
//     if(cidr.version === 'IPv4') {
//       cidr.mask = 32;
//     } else if(cidr.version === 'IPv6') {
//       cidr.mask = 128;
//     }
//     nodeIdToCIDR[nodeId] = cidr;
//   }
//   return nodeIdToCIDR;
// }

function generateInterfaceConfig(settings: Settings, data: NodeData) : StringBuilder {
  const address = [data.ipv4Address, data.ipv6Address].flatMap(p => p ? [p] : []).join(', ');
  const config = new StringBuilder();
  config.appendLine(`[Interface]`);
  config.appendLine(`# ${data.label}`);
  config.appendLine(`PrivateKey = ${data.privateKey}`);
  config.appendLine(`ListenPort = ${data.listenPort || settings.listenPort}`);
  config.appendLine(`MTU = ${data.mtu || settings.mtu}`);
  if(address) config.appendLine(`Address = ${address}`);
  if(data.postUp) config.appendLine(`PostUp = ${data.postUp}`);
  if(data.postDown) config.appendLine(`PostDown = ${data.postDown}`);
  if(data.dnsServers) config.appendLine(`DNS = ${data.dnsServers}`);
  return config;
}

function generateConfig(settings: Settings, data: NodeData, graph: AppGraph) : ConfigResult {
  const config = generateInterfaceConfig(settings, data);
  
  const disallowCIDRs : CIDR[] = [];
  if(data.disallowIPs) {
    const disallowList = data.disallowIPs.split(',').map(ip => ip.trim()).filter(ip => ip.length > 0);
    for(const ip of disallowList) {
      const result = IPUtils.parse(ip);
      if(!result.cidr) {
        return new ConfigResult(false, undefined, `无效的禁止访问IP: ${ip} (${result.error})`);
      }
      disallowCIDRs.push(result.cidr);
    }
  }

  const belongsToEdge : Record<string, string | undefined> = {[node.id]: node.id};

  const queue : AppNode[] = [];
  const nearEdges = getNearEdges(node);
  nearEdges.forEach(edge => {
    const nextNode = getNextNode(edge, node)!;
    belongsToEdge[nextNode.id] = edge.id;
    queue.push(nextNode);
  });

  while(queue.length > 0) {
    const currentNode = queue.shift()!;
    const fromEdgeId = belongsToEdge[currentNode.id];
    if(!fromEdgeId) continue;

    getNearEdges(currentNode).forEach(edge => {
      const nextNode = getNextNode(edge, currentNode)!;
      if(!belongsToEdge[nextNode.id]) {
        belongsToEdge[nextNode.id] = fromEdgeId;
        queue.push(nextNode);
      }
    });
  }
  
  const groupedByEdge: Record<string, string[] | undefined> = {};
  const nodeIds : string[] = [];
  for (const nodeId in belongsToEdge) {
    const edgeId = belongsToEdge[nodeId];
    if(edgeId === nodeId) continue; // 跳过起始节点
    nodeIds.push(nodeId);
    if(!edgeId) continue;
    if (!groupedByEdge[edgeId]) {
      groupedByEdge[edgeId] = [];
    }
    groupedByEdge[edgeId].push(nodeId);
  }
  
  for(const edgeId in groupedByEdge) {
    const groupNodeIds = groupedByEdge[edgeId]!;
    const edge = getEdge(edgeId)!;
    const nextNode = getNextNode(edge, node)!;
    const nextNodeData = nextNode.data;

    const publicKey = tryDerivePublicKey(nextNodeData.privateKey);
    if(!publicKey) return new ConfigResult(false, undefined, "无法从私钥派生公钥");

    config.appendLine("");
    config.appendLine("[Peer]");
    config.appendLine(`# ${nextNodeData.label}`);
    config.appendLine(`PublicKey = ${ publicKey}`);
    
    if(edge.data?.isTwoWayEdge || edge.source === node.id) {
      if(edge.data?.persistentKeepalive) {
        config.appendLine(`PersistentKeepalive = ${edge.data.persistentKeepalive}`);
      }

      if(!nextNodeData.listenAddress) {
        return new ConfigResult(false, undefined, `节点 ${nextNodeData.label} 未设置监听地址，无法生成配置`);
      }

      const parse = IPUtils.parse(`${nextNodeData.listenAddress}/0`);
      if(!parse.cidr) {
        return new ConfigResult(false, undefined, `节点 ${nextNodeData.label} 的监听地址无效`);
      }

      const listenAddress = parse.cidr.version === 'IPv4' ? nextNodeData.listenAddress : `[${nextNodeData.listenAddress}]`;
      const listenPort = nextNodeData.listenPort || settings.listenPort;
      config.appendLine(`EndPoint = ${listenAddress}:${listenPort}`);
    }

    const subnets : Record<string, CIDR>[] = [];

    try {
      subnets.push(mapAddressToCIDR(nodeIds, nodeId => node.data.ipv4Address && getNode(nodeId)?.data.ipv4Address));
      subnets.push(mapAddressToCIDR(nodeIds, nodeId => node.data.ipv6Address && getNode(nodeId)?.data.ipv6Address));
    } catch(e) {
      if(e instanceof Error) {
        return new ConfigResult(false, undefined, e.message);
      }
    }
    const allowIPs : string[] = [];
    for(const subnetMap of subnets) {
      const allCIDRs = nodeIds.flatMap(id => subnetMap[id] ? [subnetMap[id]] : []);
      const targetCIDRs = groupNodeIds.flatMap(id => {
        const cidr = subnetMap[id];
        if(!cidr || disallowCIDRs.some(disallow => disallow.contains(cidr))) return [];
        return [cidr];
      });
      const mergeResult = IPUtils.mergeCIDRs(allCIDRs, targetCIDRs);
      if(!mergeResult) {
        return new ConfigResult(false, undefined, `无法生成路由配置`);
      }
      mergeResult.forEach(cidr => {allowIPs.push(cidr.toString())});
    }
    if(allowIPs.length > 0) {
      config.appendLine(`AllowedIPs = ${allowIPs.join(', ')}`);
    }
  }
  
  // console.log(config.toString());

  return new ConfigResult(true, config.toString());
}

export default function CustomNode({ 
  data, 
  selected
}: NodeProps<AppNode>): ReactNode {
  const settings = useContext(SettingsContext);
  const { getNode, getEdge, getEdges } = useReactFlow<AppNode, AppEdge>();

  const handleGenerate = (node : NodeData) => {
    const result = generateConfig(settings, node, getEdges, getEdge, getNode);
    if(result.success && result.config) {
      navigator.clipboard.writeText(result.config).then(() => {
        toast.success("配置已复制到剪贴板");
      }).catch(() => {
        toast.error("复制失败，请手动复制");
      });
    } else {
      toast.error("配置生成失败：" + (result.error || "未知错误"));
    }
  };

  const empty = !(data.ipv4Address || data.ipv6Address);
  
  return (
    <div className={`custom-node ${selected ? 'selected' : ''}`}>
      <div className="node-header">
        <label className="node-label">{data.label}</label>
      </div>

      <div className='node-info'>
        {empty && <div className="empty-tip">未配置地址信息</div>}

        {data.ipv4Address && <div className="info-item">
          <span className="label">IPv4地址：</span>
          <span className="value">{data.ipv4Address || "未设置"}</span>
        </div>}
        
        {data.ipv6Address && <div className="info-item">
          <span className="label">IPv6地址：</span>
          <span className="value">{data.ipv6Address || "未设置"}</span>
        </div>}

        {data.listenAddress && <div className="info-item">
          <span className="label">监听地址：</span>
          <span className="value">{data.listenAddress || "未设置"}</span>
        </div>}
      </div>

      <div className="node-actions">
        <button className="gen-btn" onClick={e => {
          e.stopPropagation();
          handleGenerate(data);
        }} onDoubleClick={e => e.stopPropagation()}>生成配置并复制</button>
      </div>

      {[Position.Top, Position.Bottom, Position.Right, Position.Left].map((position) => (
        (["target", "source"] as const).map((type) => (
          <Handle type={type} position={position} id={position} key={`${type}-${position}`} className="node-handle"/>
        ))
      ))}
    </div>
  );
}
