using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
class HybridP2PClient
{
    // 配置参数
    private readonly string _serverIp;
    private readonly int _serverPort;
    private readonly string _clientId;
    private const int PunchAttempts = 5;
    private const int PunchInterval = 1000; // ms
    // 网络组件
    private TcpClient _tcpServerConnection;
    private UdpClient _udpClient;
    private TcpListener _tcpListener;
    private NetworkStream _tcpStream;
    
    // 状态变量
    private IPEndPoint _peerUdpEP;
    private IPEndPoint _peerTcpEP;
    private bool _isConnected = false;
    private bool _useTCP = true;
    private bool _isRunning = true;
    private int _localUdpPort;
    public HybridP2PClient(string serverIp, int serverPort, string clientId)
    {
        _serverIp = serverIp;
        _serverPort = serverPort;
        _clientId = clientId;
    }
    public void Start()
    {
        // 连接到协调服务器
        ConnectToServer();
        // 启动本地TCP监听器
        StartTcpListener();
        
        // 启动本地UDP监听器
        StartUdpListener();
        // 启动UDP心跳线程
        new Thread(UdpHeartbeat).Start();
        Console.WriteLine("输入要连接的客户端ID (或按回车退出):");
        while (_isRunning)
        {
            string targetId = Console.ReadLine();
            if (string.IsNullOrEmpty(targetId)) break;
            RequestConnection(targetId);
        }
        
        // 清理资源
        _isRunning = false;
        _tcpListener?.Stop();
        _udpClient?.Close();
        _tcpServerConnection?.Close();
    }
    #region 服务器通信
    private void ConnectToServer()
    {
        try
        {
            // 使用TCP连接服务器
            _tcpServerConnection = new TcpClient(_serverIp, _serverPort);
            _tcpStream = _tcpServerConnection.GetStream();
            Console.WriteLine("已连接到协调服务器");
            // 注册到服务器
            string registerMsg = $"REGISTER:{_clientId}";
            byte[] data = Encoding.ASCII.GetBytes(registerMsg);
            _tcpStream.Write(data, 0, data.Length);
            // 启动接收服务器消息的线程
            new Thread(ReceiveFromServer).Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接服务器失败: {ex.Message}");
        }
    }
    private void RequestConnection(string targetId)
    {
        if (_isConnected)
        {
            Console.WriteLine("已连接到对等方,请先断开当前连接");
            return;
        }
        
        string message = $"CONNECT:{_clientId}:{targetId}";
        byte[] data = Encoding.ASCII.GetBytes(message);
        _tcpStream.Write(data, 0, data.Length);
    }
    private void ReceiveFromServer()
    {
        try
        {
            byte[] buffer = new byte[1024];
            
            while (_isRunning)
            {
                int bytesRead = _tcpStream.Read(buffer, 0, buffer.Length);
                if (bytesRead == 0) break;
                string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                Console.WriteLine($"收到服务器消息: {message}");
                string[] parts = message.Split(':');
                if (parts[0] == "PEER_INFO")
                {
                    HandlePeerInfo(parts);
                }
                else if (parts[0] == "REQUEST_UDP")
                {
                    // 服务器请求UDP端口信息
                    SendUdpPortInfo();
                }
                else if (parts[0] == "ERROR")
                {
                    Console.WriteLine($"服务器错误: {message.Substring(6)}");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"接收服务器消息错误: {ex.Message}");
        }
    }
    private void SendUdpPortInfo()
    {
        try
        {
            string message = $"UDP_PORT:{_clientId}:{_localUdpPort}";
            byte[] data = Encoding.ASCII.GetBytes(message);
            _tcpStream.Write(data, 0, data.Length);
            Console.WriteLine($"已发送UDP端口信息: {_localUdpPort}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发送UDP端口信息失败: {ex.Message}");
        }
    }
    private void HandlePeerInfo(string[] parts)
    {
        // 格式: PEER_INFO:<peer_id>:<tcp_ep>:<udp_ep>
        if (parts.Length < 4) return;
        
        string peerId = parts[1];
        
        // 解析TCP端点
        string[] tcpParts = parts[2].Split(':');
        if (tcpParts.Length < 2) return;
        _peerTcpEP = new IPEndPoint(
            IPAddress.Parse(tcpParts[0]), 
            int.Parse(tcpParts[1]));
        
        // 解析UDP端点
        string[] udpParts = parts[3].Split(':');
        if (udpParts.Length < 2) return;
        _peerUdpEP = new IPEndPoint(
            IPAddress.Parse(udpParts[0]), 
            int.Parse(udpParts[1]));
        
        Console.WriteLine($"目标客户端信息: TCP={_peerTcpEP}, UDP={_peerUdpEP}");
        
        // 启动打洞线程
        new Thread(AttemptPunch).Start();
    }
    #endregion
    #region 打洞与连接
    private void AttemptPunch()
    {
        Console.WriteLine("开始P2P连接尝试...");
        
        // 优先尝试TCP连接
        if (AttemptTcpConnection())
        {
            _useTCP = true;
            _isConnected = true;
            Console.WriteLine("TCP连接成功!使用TCP进行通信");
            StartChatting();
            return;
        }
        
        Console.WriteLine("TCP连接失败,尝试UDP打洞...");
        
        // TCP失败后尝试UDP打洞
        if (AttemptUdpPunch())
        {
            _useTCP = false;
            _isConnected = true;
            Console.WriteLine("UDP打洞成功!使用UDP进行通信");
            StartChatting();
            return;
        }
        
        Console.WriteLine("所有连接尝试失败,无法建立P2P连接");
    }
    private bool AttemptTcpConnection()
    {
        Console.WriteLine("尝试TCP打洞连接...");
        
        for (int i = 0; i < PunchAttempts; i++)
        {
            try
            {
                Console.WriteLine($"TCP尝试 {i+1}/{PunchAttempts} 连接到 {_peerTcpEP}");
                var tcpClient = new TcpClient();
                tcpClient.Connect(_peerTcpEP);
                
                // 保存连接
                _tcpPeerConnection = tcpClient;
                return true;
            }
            catch (SocketException sex)
            {
                Console.WriteLine($"TCP连接失败: {sex.SocketErrorCode}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"TCP连接异常: {ex.Message}");
            }
            
            Thread.Sleep(PunchInterval);
        }
        
        return false;
    }
    private bool _udpConnected = false;
    
    private bool AttemptUdpPunch()
    {
        Console.WriteLine("尝试UDP打洞...");
        _udpConnected = false;
        
        // 发送多个打洞包(确保穿过NAT)
        for (int i = 0; i < PunchAttempts; i++)
        {
            try
            {
                string message = $"PUNCH:{_clientId}:{i}";
                byte[] data = Encoding.ASCII.GetBytes(message);
                _udpClient.Send(data, data.Length, _peerUdpEP);
                Console.WriteLine($"发送UDP打洞包到 {_peerUdpEP}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发送UDP打洞包失败: {ex.Message}");
            }
            
            Thread.Sleep(PunchInterval);
        }
        
        // 检查是否收到对方消息
        Console.WriteLine("等待UDP连接确认... (10秒)");
        DateTime startTime = DateTime.Now;
        
        while ((DateTime.Now - startTime).TotalSeconds < 10)
        {
            if (_udpConnected)
            {
                return true;
            }
            Thread.Sleep(100);
        }
        
        return false;
    }
    #endregion
    #region 网络监听
    private void StartTcpListener()
    {
        try
        {
            // 绑定随机本地端口
            _tcpListener = new TcpListener(IPAddress.Any, 0);
            _tcpListener.Start();
            var localEp = (IPEndPoint)_tcpListener.LocalEndpoint;
            Console.WriteLine($"TCP监听端口: {localEp.Port}");
            
            // 启动接受TCP连接的线程
            new Thread(() =>
            {
                while (_isRunning)
                {
                    try
                    {
                        TcpClient peer = _tcpListener.AcceptTcpClient();
                        var remoteEp = (IPEndPoint)peer.Client.RemoteEndPoint;
                        
                        if (_isConnected)
                        {
                            Console.WriteLine($"已连接,拒绝来自 {remoteEp} 的TCP连接");
                            peer.Close();
                            continue;
                        }
                        
                        _tcpPeerConnection = peer;
                        _isConnected = true;
                        _useTCP = true;
                        Console.WriteLine($"接受来自 {remoteEp} 的TCP连接");
                        StartChatting();
                    }
                    catch (Exception ex)
                    {
                        if (_isRunning) Console.WriteLine($"接受TCP连接错误: {ex.Message}");
                    }
                }
            }).Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"启动TCP监听器失败: {ex.Message}");
        }
    }
    private void StartUdpListener()
    {
        try
        {
            // 绑定随机本地端口
            _udpClient = new UdpClient(0);
            var localEp = (IPEndPoint)_udpClient.Client.LocalEndPoint;
            _localUdpPort = localEp.Port;
            Console.WriteLine($"UDP监听端口: {localEp.Port}");
            
            // 启动UDP接收线程
            new Thread(() =>
            {
                while (_isRunning)
                {
                    try
                    {
                        IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
                        byte[] data = _udpClient.Receive(ref remoteEP);
                        string message = Encoding.ASCII.GetString(data);
                        
                        // 检查是否是来自目标对等方的消息
                        if (_peerUdpEP != null && 
                            (remoteEP.Address.Equals(_peerUdpEP.Address) || 
                             remoteEP.Port == _peerUdpEP.Port))
                        {
                            if (message.StartsWith("PUNCH:"))
                            {
                                Console.WriteLine($"收到UDP打洞包: {message}");
                                if (!_isConnected) _udpConnected = true;
                            }
                            else if (message.StartsWith("MSG:"))
                            {
                                Console.WriteLine($"收到UDP消息: {message.Substring(4)}");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        if (_isRunning) Console.WriteLine($"接收UDP消息错误: {ex.Message}");
                    }
                }
            }).Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"启动UDP监听器失败: {ex.Message}");
        }
    }
    private void UdpHeartbeat()
    {
        var serverEP = new IPEndPoint(IPAddress.Parse(_serverIp), _serverPort);
        
        while (_isRunning)
        {
            try
            {
                // 每30秒发送一次心跳
                Thread.Sleep(30000);
                
                string message = $"HEARTBEAT:{_clientId}";
                byte[] data = Encoding.ASCII.GetBytes(message);
                _udpClient.Send(data, data.Length, serverEP);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发送UDP心跳失败: {ex.Message}");
            }
        }
    }
    #endregion
    #region 通信处理
    private TcpClient _tcpPeerConnection;
    
    private void StartChatting()
    {
        if (_useTCP)
        {
            // TCP通信模式
            new Thread(() => ReceiveTcpMessages(_tcpPeerConnection)).Start();
            new Thread(() => SendTcpMessages(_tcpPeerConnection)).Start();
        }
        else
        {
            // UDP通信模式
            new Thread(SendUdpMessages).Start();
        }
    }
    private void ReceiveTcpMessages(TcpClient peer)
    {
        try
        {
            NetworkStream stream = peer.GetStream();
            byte[] buffer = new byte[1024];
            
            while (_isConnected)
            {
                int bytesRead = stream.Read(buffer, 0, buffer.Length);
                if (bytesRead == 0) break;
                
                string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                Console.WriteLine($"对方(TCP): {message}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"接收TCP消息错误: {ex.Message}");
        }
        finally
        {
            _isConnected = false;
            peer.Close();
            Console.WriteLine("TCP连接已关闭");
        }
    }
    private void SendTcpMessages(TcpClient peer)
    {
        try
        {
            NetworkStream stream = peer.GetStream();
            Console.WriteLine("输入消息开始聊天 (输入'exit'退出):");
            
            while (_isConnected)
            {
                string message = Console.ReadLine();
                if (message == "exit") break;
                
                byte[] data = Encoding.ASCII.GetBytes(message);
                stream.Write(data, 0, data.Length);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发送TCP消息错误: {ex.Message}");
        }
        finally
        {
            _isConnected = false;
            peer.Close();
        }
    }
    private void SendUdpMessages()
    {
        Console.WriteLine("输入消息开始聊天 (输入'exit'退出):");
        
        while (_isConnected)
        {
            try
            {
                string message = Console.ReadLine();
                if (message == "exit") break;
                
                byte[] data = Encoding.ASCII.GetBytes($"MSG:{message}");
                _udpClient.Send(data, data.Length, _peerUdpEP);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发送UDP消息错误: {ex.Message}");
            }
        }
        
        _isConnected = false;
    }
    #endregion
    static void Main(string[] args)
    {
        Console.Write("输入协调服务器IP: ");
        string serverIp = Console.ReadLine();
        
        Console.Write("输入客户端ID: ");
        string clientId = Console.ReadLine();
        var client = new HybridP2PClient(serverIp, 11000, clientId);
        client.Start();
    }
}