#import <Foundation/Foundation.h>
#import "XUDPClient.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

#define HOST @"127.0.0.1"
#define SEND_TIMEOUT 5.0  // 发送超时时间(秒)

@interface XUDPClient() {
@private
    GCDAsyncUdpSocket *_udpSocket;
    dispatch_queue_t _clientQueue;
    NSTimer *_healthCheckTimer;
    BOOL _isConnected;
}

@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSDictionary *> *pendingSends;
@property (nonatomic, assign) long currentTag;

@end

@implementation XUDPClient

#pragma mark - Singleton

+ (instancetype)sharedInstance {
    static XUDPClient *_sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedInstance = [[XUDPClient alloc] init];
    });
    return _sharedInstance;
}

// ⭐️ 防止通过alloc/init创建新实例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [XUDPClient sharedInstance];
}

- (instancetype)copyWithZone:(NSZone *)zone {
    return self;
}

- (instancetype)mutableCopyWithZone:(NSZone *)zone {
    return self;
}

- (instancetype)init {
    
    if (self = [super init]) {
        self->_isConnected = NO;
        self->_currentTag = 0;
        self->_pendingSends = [NSMutableDictionary dictionary];
        
        // 创建专用串行队列
        self->_clientQueue = dispatch_queue_create("com.xudpclient.queue", DISPATCH_QUEUE_SERIAL);
        
        [self start];
        [self startHealthCheck];
    }
    
    return self;
}

- (void)start {
    dispatch_async(_clientQueue, ^{
        [self _startInternal];
    });
}

- (void)_startInternal {
    NSLog(@"XC- Starting UDP client");
    
    // 清理旧socket
    if (_udpSocket) {
        [_udpSocket close];
        _udpSocket = nil;
    }
    
    _udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self 
                                               delegateQueue:_clientQueue];
    
    // ⭐️ 启用地址重用,避免TIME_WAIT问题
    NSError *error = nil;
    if (![_udpSocket enableReusePort:YES error:&error]) {
        NSLog(@"❌ Error enabling reuse port: %@", error);
    }
    
    // ⭐️ 设置SO_REUSEADDR
    [self _setSocketOptions];
    
    // 绑定到任意可用端口
    if (![_udpSocket bindToPort:0 error:&error]) {
        NSLog(@"❌ Error binding: %@", error);
        [self _handleBindError:error];
        return;
    }
    
    if (![_udpSocket beginReceiving:&error]) {
        NSLog(@"❌ Error receiving: %@", error);
        [self _scheduleRestart];
        return;
    }
    
    _isConnected = YES;
    NSLog(@"✅ UDP client started successfully");
}

// ⭐️ 设置socket选项
- (void)_setSocketOptions {
    if (!_udpSocket) return;
    
    int fd = [_udpSocket socketFD];
    if (fd == -1) return;
    
    // 设置SO_REUSEADDR,允许快速重启
    int reuseAddr = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) == -1) {
        NSLog(@"⚠️ Failed to set SO_REUSEADDR: %s", strerror(errno));
    }
    
    // 设置发送缓冲区大小
    int sendBufferSize = 65536;
    if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendBufferSize, sizeof(sendBufferSize)) == -1) {
        NSLog(@"⚠️ Failed to set send buffer size: %s", strerror(errno));
    }
    
    // 设置接收缓冲区大小
    int recvBufferSize = 65536;
    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvBufferSize, sizeof(recvBufferSize)) == -1) {
        NSLog(@"⚠️ Failed to set receive buffer size: %s", strerror(errno));
    }
}

// ⭐️ 处理绑定错误
- (void)_handleBindError:(NSError *)error {
    if (error.code == 48) { // EADDRINUSE
        NSLog(@"⚠️ Address already in use, checking TIME_WAIT status");
        [self _checkTimeWaitStatus];
    }
    
    [self _scheduleRestart];
}

// ⭐️ 检查TIME_WAIT状态
- (void)_checkTimeWaitStatus {
    int testSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (testSocket < 0) {
        NSLog(@"❌ Cannot create test socket");
        return;
    }
    
    // 设置SO_REUSEADDR后再测试
    int reuseAddr = 1;
    setsockopt(testSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
    
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(0);
    addr.sin_addr.s_addr = INADDR_ANY;
    
    int result = bind(testSocket, (struct sockaddr *)&addr, sizeof(addr));
    close(testSocket);
    
    if (result != 0) {
        NSLog(@"⚠️ Possible TIME_WAIT issue detected: %s", strerror(errno));
    }
}

// ⭐️ 自动重启调度
- (void)_scheduleRestart {
    NSLog(@"⏰ Scheduling client restart in 3 seconds");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), 
                   _clientQueue, ^{
        [self _startInternal];
    });
}

- (void)stop {
    dispatch_async(_clientQueue, ^{
        [self _stopInternal];
    });
}

- (void)_stopInternal {
    NSLog(@"XC- Stopping UDP client");
    
    [self stopHealthCheck];
    
    if (_udpSocket) {
        // ⭐️ 设置SO_LINGER强制立即关闭,避免TIME_WAIT
        int fd = [_udpSocket socketFD];
        if (fd != -1) {
            struct linger lingerOption = {1, 0};
            setsockopt(fd, SOL_SOCKET, SO_LINGER, &lingerOption, sizeof(lingerOption));
        }
        
        [_udpSocket close];
        _udpSocket = nil;
    }
    
    _isConnected = NO;
    [_pendingSends removeAllObjects];
}

// ⭐️ 健康检查
- (void)startHealthCheck {
    dispatch_async(dispatch_get_main_queue(), ^{
        self->_healthCheckTimer = [NSTimer scheduledTimerWithTimeInterval:30.0
                                                                   target:self
                                                                 selector:@selector(_performHealthCheck)
                                                                 userInfo:nil
                                                                  repeats:YES];
    });
}

- (void)stopHealthCheck {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self->_healthCheckTimer) {
            [self->_healthCheckTimer invalidate];
            self->_healthCheckTimer = nil;
        }
    });
}

- (void)_performHealthCheck {
    dispatch_async(_clientQueue, ^{
        if (!self->_udpSocket || self->_udpSocket.isClosed) {
            NSLog(@"⚠️ Health check failed: socket is closed");
            [self _startInternal];
        }
    });
}

#pragma mark - Send Methods

// ⭐️ 发送到指定端口
- (void)onShow:(NSDictionary *)data toPort:(uint16_t)port {
    NSDictionary *rq = @{
        @"url": @"/adtask/show",
        @"body": data
    };
    [self send:[self dic2Json:rq] toPort:port];
}

// ⭐️ 发送到指定端口
- (void)onEnd:(NSDictionary *)data toPort:(uint16_t)port {
    NSDictionary *rq = @{
        @"url": @"/adtask/end",
        @"body": data
    };
    [self send:[self dic2Json:rq] toPort:port];
}

// ⭐️ 发送方法接收端口参数
- (void)send:(NSString *)msg toPort:(uint16_t)port {
    if (!msg) {
        NSLog(@"⚠️ Cannot send nil message");
        return;
    }
    
    dispatch_async(_clientQueue, ^{
        [self _sendInternal:msg toPort:port];
    });
}

- (void)_sendInternal:(NSString *)msg toPort:(uint16_t)port {
    if (!_udpSocket || _udpSocket.isClosed) {
        NSLog(@"❌ Socket not ready, cannot send message to port %d", port);
        return;
    }
    
    long tag = ++_currentTag;
    
    // ⭐️ 保存待发送数据,用于超时检测
    _pendingSends[@(tag)] = @{
        @"message": msg,
        @"port": @(port),
        @"timestamp": @([[NSDate date] timeIntervalSince1970])
    };
    
    NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
    
    NSLog(@"📤 Sending to %@:%d (tag: %ld)", HOST, port, tag);
    
    // ⭐️ 设置超时时间
    [_udpSocket sendData:data 
                  toHost:HOST 
                    port:port 
             withTimeout:SEND_TIMEOUT 
                     tag:tag];
    
    // ⭐️ 设置超时检查
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((SEND_TIMEOUT + 1.0) * NSEC_PER_SEC)), 
                   _clientQueue, ^{
        [self _checkSendTimeout:tag];
    });
}

// ⭐️ 检查发送超时
- (void)_checkSendTimeout:(long)tag {
    NSDictionary *pendingData = _pendingSends[@(tag)];
    if (pendingData) {
        NSString *msg = pendingData[@"message"];
        uint16_t port = [pendingData[@"port"] unsignedShortValue];
        NSTimeInterval timestamp = [pendingData[@"timestamp"] doubleValue];
        NSTimeInterval elapsed = [[NSDate date] timeIntervalSince1970] - timestamp;
        
        NSLog(@"⏱️ Send timeout for tag %ld (port: %d, elapsed: %.1fs)", 
              tag, port, elapsed);
        
        [_pendingSends removeObjectForKey:@(tag)];
        
        // 超时后不重试,只记录日志
        NSLog(@"❌ Message failed to send within timeout: %@", 
              [msg substringToIndex:MIN(100, msg.length)]);
    }
}

#pragma mark - GCDAsyncUdpSocket Delegate

- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
    NSLog(@"✅ Connected to address");
    _isConnected = YES;
}

- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {
    NSLog(@"❌ Did not connect: %@", error);
    _isConnected = NO;
    [self _scheduleRestart];
}

- (void)udpSocket:(GCDAsyncUdpSocket *)sock 
didSendDataWithTag:(long)tag {
    NSDictionary *pendingData = _pendingSends[@(tag)];
    uint16_t port = pendingData ? [pendingData[@"port"] unsignedShortValue] : 0;
    
    NSLog(@"✅ Message sent successfully (tag: %ld, port: %d)", tag, port);
    
    // ⭐️ 清除待发送记录
    [_pendingSends removeObjectForKey:@(tag)];
}

- (void)udpSocket:(GCDAsyncUdpSocket *)sock 
didNotSendDataWithTag:(long)tag 
       dueToError:(NSError *)error {
    NSDictionary *pendingData = _pendingSends[@(tag)];
    uint16_t port = pendingData ? [pendingData[@"port"] unsignedShortValue] : 0;
    
    NSLog(@"❌ Failed to send (tag: %ld, port: %d): %@", tag, port, error);
    
    [_pendingSends removeObjectForKey:@(tag)];
    
    // ⭐️ 检查错误类型并处理
    if (error.code == 57) { // ENOTCONN - Socket is not connected
        NSLog(@"⚠️ Socket disconnected (ENOTCONN), restarting...");
        [self _startInternal];
    } else if (error.code == 55) { // ENOBUFS - No buffer space available
        NSLog(@"⚠️ Buffer full (ENOBUFS)");
    } else if (error.code == 64) { // EHOSTDOWN - Host is down
        NSLog(@"⚠️ Host is down (EHOSTDOWN)");
    } else if (error.code == 65) { // EHOSTUNREACH - No route to host
        NSLog(@"⚠️ Host unreachable (EHOSTUNREACH)");
    } else if (error.code == 60) { // ETIMEDOUT - Operation timed out
        NSLog(@"⚠️ Send operation timed out (ETIMEDOUT)");
    } else {
        NSLog(@"⚠️ Unknown error code: %ld", (long)error.code);
    }
}

- (void)udpSocket:(GCDAsyncUdpSocket *)sock 
   didReceiveData:(NSData *)data 
      fromAddress:(NSData *)address 
withFilterContext:(id)filterContext {
    @autoreleasepool {
        NSString *revData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        if (!revData) {
            NSLog(@"⚠️ Failed to decode received data");
            return;
        }
        
        NSLog(@"📨 Received: %@", revData);
        
        if (self.hintBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.hintBlock(revData);
            });
        }
    }
}

// ⭐️ Socket异常关闭处理
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error {
    NSLog(@"⚠️ Socket closed. Error: %@", error);
    
    _isConnected = NO;
    
    if (sock == _udpSocket) {
        _udpSocket = nil;
    }
    
    if (error) {
        NSLog(@"❌ Unexpected closure, scheduling restart");
        [self _scheduleRestart];
    }
}

#pragma mark - Utility Methods

- (NSString *)dic2Json:(NSDictionary *)dict {
    if (!dict) return nil;
    
    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
                                                       options:0
                                                         error:&error];
    if (error) {
        NSLog(@"❌ dic2json error: %@", error);
        return nil;
    }
    return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}

- (NSDictionary *)json2dic:(NSString *)jsstr {
    if (!jsstr) return nil;
    
    NSError *jsonError;
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[jsstr dataUsingEncoding:NSUTF8StringEncoding]
                                                        options:NSJSONReadingMutableContainers
                                                          error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ json2dic error: %@", jsonError);
    }
    return dic;
}

@end
