//
//  ZYJQNUploadInfoCollector.m
//  ZYJQiniuSDK
//
//  Created by WorkSpace_Sun on 2020/4/15.
//  Copyright © 2020 Qiniu. All rights reserved.
//

#import "ZYJQNUploadInfoCollector.h"
#import "ZYJQNUploadInfoReporter.h"
#import "ZYJQNHttpResponseInfo.h"
#import "ZYJQNResponseInfo.h"

ZYJQNCollectKey *const ZYJCK_bucket = @"bucket";
ZYJQNCollectKey *const ZYJCK_key = @"key";
ZYJQNCollectKey *const ZYJCK_targetRegionId = @"targetRegionId";
ZYJQNCollectKey *const ZYJCK_currentRegionId = @"currentRegionId";
ZYJQNCollectKey *const ZYJCK_result = @"result";
ZYJQNCollectKey *const ZYJCK_blockBytesSent = @"blockBytesSent";
ZYJQNCollectKey *const ZYJCK_recoveredFrom = @"recoveredFrom";
ZYJQNCollectKey *const ZYJCK_totalBytesSent = @"totalBytesSent";
ZYJQNCollectKey *const ZYJCK_fileSize = @"fileSize";
ZYJQNCollectKey *const ZYJCK_blockApiVersion = @"blockApiVersion";

int64_t ZYJQN_IntNotSet = -11111111;

// Upload Result Type
NSString *const ZYJupload_ok = @"ok";
NSString *const ZYJzero_size_file = @"ZYJzero_size_file";
NSString *const ZYJinvalid_file = @"ZYJinvalid_file";
NSString *const ZYJinvalid_args = @"ZYJinvalid_args";
NSString *const ZYJlocal_io_error = @"ZYJlocal_io_error";

// Network Error Type
NSString *const ZYJunknown_error = @"ZYJunknown_error";
NSString *const ZYJnetwork_error = @"ZYJnetwork_error";
NSString *const ZYJnetwork_timeout = @"timeout";
NSString *const ZYJunknown_host = @"ZYJunknown_host";
NSString *const ZYJcannot_connect_to_host = @"ZYJcannot_connect_to_host";
NSString *const ZYJtransmission_error = @"ZYJtransmission_error";
NSString *const ZYJproxy_error = @"ZYJproxy_error";
NSString *const ZYJssl_error = @"ZYJssl_error";
NSString *const ZYJresponse_error = @"ZYJresponse_error";
NSString *const ZYJparse_error = @"ZYJparse_error";
NSString *const ZYJmalicious_response = @"ZYJmalicious_response";
NSString *const ZYJuser_canceled = @"ZYJuser_canceled";
NSString *const ZYJbad_request = @"ZYJbad_request";
NSString *const ZYJprotocol_error = @"ZYJprotocol_error";

static NSString *const ZYJrequestTypes[] = {@"form", @"mkblk", @"bput", @"mkfile", @"put", @"init_parts", @"upload_part", @"complete_part", @"uc_query", @"httpdns_query"};

@interface ZYJQNCollectItem : NSObject

@property (nonatomic, copy) NSString *identifier;
@property (nonatomic, copy) NSString *bucket;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *token;
@property (nonatomic, copy) NSString *targetRegionId;
@property (nonatomic, copy) NSString *currentRegionId;
@property (nonatomic, copy) NSString *result;

@property (nonatomic, assign) int64_t uploadStartTime;
@property (nonatomic, assign) int64_t uploadEndTime;
@property (nonatomic, assign) int64_t totalBytesSent;
@property (nonatomic, assign) int64_t fileSize;

@property (nonatomic, assign) int64_t recoveredFrom;
@property (nonatomic, assign) int64_t blockApiVersion;
@property (nonatomic, assign) int64_t blockBytesSent;

@property (nonatomic, strong) NSMutableArray<ZYJQNHttpResponseInfo *> *httpRequestList;
@end

@implementation ZYJQNCollectItem

- (instancetype)initWithIdentifier:(NSString *)identifier token:(NSString *)token
{
    self = [super init];
    if (self) {
        _identifier = identifier;
        _token = token;
        _uploadStartTime = ZYJQN_IntNotSet;
        _uploadEndTime = ZYJQN_IntNotSet;
        _fileSize = ZYJQN_IntNotSet;
        _recoveredFrom = ZYJQN_IntNotSet;
        _blockApiVersion = ZYJQN_IntNotSet;
        _totalBytesSent = 0;
        _blockBytesSent = 0;
        _httpRequestList = [NSMutableArray array];
    }
    return self;
}

@end

@interface ZYJQNUploadInfoCollector ()
@property (nonatomic, strong) NSArray<ZYJQNCollectKey *> *updateKeysList;
@property (nonatomic, strong) NSArray <ZYJQNCollectKey *>*appendKeysList;
@property (nonatomic, strong) NSMutableArray<ZYJQNCollectItem *> *collectItemList;
@property (nonatomic, strong) dispatch_queue_t collectQueue;
@end

@implementation ZYJQNUploadInfoCollector
+ (instancetype)sharedInstance {
    
    static ZYJQNUploadInfoCollector *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        sharedInstance.collectItemList = [NSMutableArray array];
        sharedInstance.collectQueue = dispatch_queue_create("com.qiniu.collector", DISPATCH_QUEUE_SERIAL);
        sharedInstance.updateKeysList = @[
            ZYJCK_bucket,
            ZYJCK_key,
            ZYJCK_targetRegionId,
            ZYJCK_currentRegionId,
            ZYJCK_result,
            ZYJCK_recoveredFrom,
            ZYJCK_fileSize,
            ZYJCK_blockApiVersion];
        sharedInstance.appendKeysList = @[
            ZYJCK_blockBytesSent,
            ZYJCK_totalBytesSent];
    });
    return sharedInstance;
}

- (void)registerWithIdentifier:(NSString *)identifier token:(NSString *)token {
    if (!identifier || !token) return;
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *item = [[ZYJQNCollectItem alloc] initWithIdentifier:identifier token:token];
        item.uploadStartTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        [self.collectItemList addObject:item];
    });
}

- (void)update:(ZYJQNCollectKey *)key value:(id)value identifier:(NSString *)identifier {
    if (!identifier || !key || ![self.updateKeysList containsObject:key]) return;
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        if (currentItem) {
            [currentItem setValue:value forKey:key];
        }
    });
}

- (void)append:(ZYJQNCollectKey *)key value:(id)value identifier:(NSString *)identifier {
    if (!identifier || !key || ![self.appendKeysList containsObject:key]) return;
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        if (currentItem) {
            // append NSNumber value
            NSNumber *formalValue = [currentItem valueForKey:key];
            NSNumber *appendValue = (NSNumber *)value;
            int64_t newValue = formalValue.longValue + appendValue.longValue;
            [currentItem setValue:@(newValue) forKey:key];
        }
    });
}

- (void)addRequestWithType:(ZYJQNRequestType)upType httpResponseInfo:(ZYJQNHttpResponseInfo *)httpResponseInfo fileOffset:(int64_t)fileOffset targetRegionId:(NSString *)targetRegionId currentRegionId:(NSString *)currentRegionId identifier:(NSString *)identifier {
    
    if (!identifier || !httpResponseInfo) return;
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        if (currentItem) {
            [currentItem.httpRequestList addObject:httpResponseInfo];
            if ([ZYJQNReportConfig sharedInstance].isReportEnable) {
                
                // 分块上传bytesSent字段有误差  这里分开处理
                int64_t bytesSent;
                if (upType == ZYJQNRequestType_mkblk || upType == ZYJQNRequestType_bput) {
                    if (httpResponseInfo.hasHttpResponse) {
                        bytesSent = httpResponseInfo.bytesTotal;
                    } else {
                        bytesSent = 0;
                    }
                } else {
                    bytesSent = httpResponseInfo.bytesSent;
                }
                
                ZYJQNReportRequestItem *item = [ZYJQNReportRequestItem buildWithUpType:ZYJrequestTypes[upType]
                                                                    TargetBucket:currentItem.bucket
                                                                       targetKey:currentItem.key
                                                                      fileOffset:fileOffset
                                                                  targetRegionId:targetRegionId
                                                                 currentRegionId:currentRegionId
                                                               prefetchedIpCount:ZYJQN_IntNotSet
                                                                             pid:httpResponseInfo.pid
                                                                             tid:httpResponseInfo.tid
                                                                      statusCode:httpResponseInfo.statusCode
                                                                           reqId:httpResponseInfo.reqId
                                                                            host:httpResponseInfo.host
                                                                        remoteIp:httpResponseInfo.remoteIp
                                                                            port:httpResponseInfo.port totalElapsedTime:httpResponseInfo.totalElapsedTime dnsElapsedTime:httpResponseInfo.dnsElapsedTime connectElapsedTime:httpResponseInfo.connectElapsedTime tlsConnectElapsedTime:httpResponseInfo.tlsConnectElapsedTime requestElapsedTime:httpResponseInfo.requestElapsedTime waitElapsedTime:httpResponseInfo.waitElapsedTime responseElapsedTime:httpResponseInfo.responseElapsedTime bytesSent:bytesSent bytesTotal:httpResponseInfo.bytesTotal errorType:httpResponseInfo.errorType errorDescription:httpResponseInfo.errorDescription networkType:httpResponseInfo.networkType signalStrength:httpResponseInfo.signalStrength];
                [Reporter report:[item toJson] token:currentItem.token];
            }
        }
    });
}

- (ZYJQNResponseInfo *)completeWithHttpResponseInfo:(ZYJQNHttpResponseInfo *)lastHttpResponseInfo identifier:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        if (lastHttpResponseInfo.isOK) {
            currentItem.result = ZYJupload_ok;
        } else {
            currentItem.result = lastHttpResponseInfo.errorType;
        }
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
        info = [ZYJQNResponseInfo responseInfoWithHttpResponseInfo:lastHttpResponseInfo duration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}


- (ZYJQNResponseInfo *)completeWithInvalidArgument:(NSString *)text identifier:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        currentItem.result = ZYJinvalid_args;
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
        info = [ZYJQNResponseInfo responseInfoWithInvalidArgument:text duration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}

- (ZYJQNResponseInfo *)completeWithInvalidToken:(NSString *)text identifier:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        currentItem.result = ZYJinvalid_args;
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
        info = [ZYJQNResponseInfo responseInfoWithInvalidToken:text duration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}

- (ZYJQNResponseInfo *)completeWithFileError:(NSError *)error identifier:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        currentItem.result = ZYJinvalid_file;
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
       info = [ZYJQNResponseInfo responseInfoWithFileError:error duration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}

- (ZYJQNResponseInfo *)completeWithLocalIOError:(NSError *)error identifier:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        currentItem.result = ZYJlocal_io_error;
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
       info = [ZYJQNResponseInfo responseInfoWithFileError:error duration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}

- (ZYJQNResponseInfo *)completeWithZeroData:(NSString *)path identifier:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        currentItem.result = ZYJzero_size_file;
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
        info = [ZYJQNResponseInfo responseInfoOfZeroData:path duration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}

- (ZYJQNResponseInfo *)userCancel:(NSString *)identifier {
    
    __block ZYJQNResponseInfo *info;
    dispatch_semaphore_t signal = dispatch_semaphore_create(0);
    dispatch_async(_collectQueue, ^{
        ZYJQNCollectItem *currentItem = [self getCurrentItemWithIdentifier:identifier];
        currentItem.result = ZYJuser_canceled;
        currentItem.uploadEndTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970] * 1000;
        
        info = [ZYJQNResponseInfo cancelWithDuration:(currentItem.uploadEndTime - currentItem.uploadStartTime) / 1000.0];
        dispatch_semaphore_signal(signal);
        
        if ([ZYJQNReportConfig sharedInstance].isReportEnable) [self reportResult:currentItem];
        [self.collectItemList removeObject:currentItem];
    });
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    return info;
}

- (void)reportResult:(ZYJQNCollectItem *)currentItem {
    int64_t regionsCount = !currentItem.targetRegionId || !currentItem.currentRegionId || [currentItem.targetRegionId isEqualToString:currentItem.currentRegionId] ? 1 : 2;
    int64_t totalElapsedTime = currentItem.uploadEndTime - currentItem.uploadStartTime;

    if (currentItem.blockApiVersion != ZYJQN_IntNotSet) {
        ZYJQNReportBlockItem *item = [ZYJQNReportBlockItem buildWithTargetRegionId:currentItem.targetRegionId currentRegionId:currentItem.currentRegionId totalElapsedTime:totalElapsedTime bytesSent:currentItem.blockBytesSent recoveredFrom:currentItem.recoveredFrom fileSize:currentItem.fileSize pid:ZYJQN_IntNotSet tid:ZYJQN_IntNotSet upApiVersion:currentItem.blockApiVersion];
        [Reporter report:[item toJson] token:currentItem.token];
    }
    ZYJQNReportQualityItem *item = [ZYJQNReportQualityItem buildWithResult:currentItem.result totalElapsedTime:totalElapsedTime requestsCount:currentItem.httpRequestList.count regionsCount:regionsCount bytesSent:currentItem.totalBytesSent];
    [Reporter report:[item toJson] token:currentItem.token];
}

- (ZYJQNCollectItem *)getCurrentItemWithIdentifier:(NSString *)identifier {
    ZYJQNCollectItem *item = nil;
    for (NSInteger i = 0; i < self.collectItemList.count; i++) {
        ZYJQNCollectItem *currentItem = self.collectItemList[i];
        if ([currentItem.identifier isEqualToString:identifier]) {
            item = currentItem;
            break;
        }
    }
    return item;
}

@end
