//
//  ZYJQNDnsPrefetcher.m
//  ZYJQNDNS
//
//  Created by yangsen on 2020/3/26.
//  Copyright © 2020 com.qiniu. All rights reserved.
//

#import "ZYJQNDnsPrefetcher.h"
#import "ZYJQNInetAddress.h"
#import "ZYJQNDnsCacheInfo.h"
#import "ZYJQNConfig.h"
#import "ZYJQNDnsCacheFile.h"
#import "ZYJQNUpToken.h"
#import "ZYJQNUtils.h"
#import "ZYJQNAsyncRun.h"
#import "ZYJQNFixedZone.h"
#import "ZYJQNAutoZone.h"
#import "ZYJHappyDNS.h"

//MARK: -- ZYJHappyDNS 适配
@interface ZYJQNRecord(DNS)<ZYJQNInetAddressDelegate>
@end
@implementation ZYJQNRecord(DNS)
- (NSString *)hostValue{
    return nil;
}
- (NSString *)ipValue{
    return self.value;
}
- (NSNumber *)ttlValue{
    return @(self.ttl);
}
- (NSNumber *)timestampValue{
    return @(self.timeStamp);
}
@end

@interface ZYJQNDnsManager(DNS)<ZYJQNDnsDelegate>
@end
@implementation ZYJQNDnsManager(DNS)

- (NSArray<id<ZYJQNInetAddressDelegate>> *)lookup:(NSString *)host{

    return [self queryRecords:host];
}

@end


//MARK: -- DNS Prefetcher
@interface ZYJQNDnsPrefetcher()

/// 是否正在预取，正在预取会直接取消新的预取操作请求
@property(atomic, assign)BOOL isPrefetching;
/// 获取AutoZone时的同步锁
@property(nonatomic, strong)dispatch_semaphore_t getAutoZoneSemaphore;
/// DNS信息本地缓存key
@property(nonatomic, strong)ZYJQNDnsCacheInfo *dnsCacheInfo;
/// happy的dns解析对象列表，会使用多个dns解析对象 包括系统解析
@property(nonatomic, strong)ZYJQNDnsManager * httpDns;
/// 缓存DNS解析结果
@property(nonatomic, strong)NSMutableDictionary <NSString *, NSArray<ZYJQNInetAddress *>*> *addressDictionary;

@end

@implementation ZYJQNDnsPrefetcher

+ (instancetype)shared{
    static ZYJQNDnsPrefetcher *prefetcher = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        prefetcher = [[ZYJQNDnsPrefetcher alloc] init];
    });
    return prefetcher;
}

- (instancetype)init{
    if (self = [super init]) {
        _isPrefetching = NO;
    }
    return self;
}

//MARK: -- uploadManager初始化时，加载本地缓存到内存
/// 同步本地预取缓存 如果存在且满足使用返回false，反之为true
- (BOOL)recoverCache{
    NSLog(@"== recoverCache");
    id <ZYJQNRecorderDelegate> recorder = nil;
    
    NSError *error;
    recorder = [ZYJQNDnsCacheFile dnsCacheFile:kZYJQNGlobalConfiguration.dnscacheDir
                                      error:&error];
    if (error) {
        return YES;
    }
    
    NSData *data = [recorder get:[ZYJQNIP local]];
    if (!data) {
        return YES;
    }
    
    ZYJQNDnsCacheInfo *cacheInfo = [ZYJQNDnsCacheInfo dnsCacheInfo:data];
    if (!cacheInfo) {
        return YES;
    }
    
    NSString *localIp = [ZYJQNIP local];

    if (!localIp || localIp.length == 0) {
        return YES;
    }
    
    if (![cacheInfo.localIp isEqualToString:localIp]) {
        return YES;
    }
    
    [self setDnsCacheInfo:cacheInfo];
    
    return [self recoverDnsCache:cacheInfo.info];
}
/// 本地缓存读取失败后，加载本地域名，预取DNS解析信息
- (void)localFetch{
    if ([self prepareToPreFetch] == NO) {
        return;
    }
    
    NSLog(@"== localFetch");
    [self preFetchHosts:[self getLocalPreHost]];
    [self recorderDnsCache];
    [self endPreFetch];
}
//MARK: -- 检测并预取
/// 根据token检测Dns缓存信息时效，无效则预取。 完成预取操作返回YES，反之返回NO
- (void)checkAndPrefetchDnsIfNeed:(ZYJQNZone *)currentZone token:(NSString *)token{
    if ([self prepareToPreFetch] == NO) {
        return;
    }
    [self preFetchHosts:[self getCurrentZoneHosts:currentZone token:token]];
    [self recorderDnsCache];
    [self endPreFetch];
}
/// 检测已预取dns是否还有效，无效则重新预取
- (void)checkWhetherCachedDnsValid{
    if ([self prepareToPreFetch] == NO) {
        return;
    }
    [self preFetchHosts:[self.addressDictionary allKeys]];
    [self recorderDnsCache];
    [self endPreFetch];
}

//MARK: -- 强制无效缓存
// 无效缓存，会根据inetAddress的host，无效host对应的ip缓存
- (void)invalidInetAdress:(id <ZYJQNInetAddressDelegate>)inetAddress{
    NSArray *inetAddressList = self.addressDictionary[inetAddress.hostValue];
    NSMutableArray *inetAddressListNew = [NSMutableArray array];
    for (id <ZYJQNInetAddressDelegate> inetAddressP in inetAddressList) {
        if (![inetAddress.ipValue isEqualToString:inetAddressP.ipValue]) {
            [inetAddressListNew addObject:inetAddressP];
        }
    }
    [self.addressDictionary setObject:[inetAddressListNew copy] forKey:inetAddress.hostValue];
}

//MARK: -- 读取缓存的DNS信息
/// 根据host从缓存中读取DNS信息
- (NSArray <id <ZYJQNInetAddressDelegate> > *)getInetAddressByHost:(NSString *)host{

    if ([self isDnsOpen] == NO) {
        return nil;
    }
    
    NSArray <ZYJQNInetAddress *> *addressList = self.addressDictionary[host];
    if (![addressList.firstObject isValid]) {
        ZYJQNAsyncRun(^{
            [[ZYJQNTransactionManager shared] setDnsCheckWhetherCachedValidTransactionAction];
        });
    }
    return addressList;
}

//MARK: --
//MARK: -- 根据dns预取
- (BOOL)prepareToPreFetch{
    if ([self isDnsOpen] == NO) {
        return NO;
    }
    
    if (self.isPrefetching == YES) {
        return NO;
    }
    
    NSString *localIp = [ZYJQNIP local];
    if (localIp == nil ||
        (self.dnsCacheInfo && ![localIp isEqualToString:self.dnsCacheInfo.localIp])) {

        [self clearPreHosts];
    }
    
    self.isPrefetching = YES;
    return YES;
}

- (void)endPreFetch{
    self.isPrefetching = NO;
}

- (void)preFetchHosts:(NSArray <NSString *> *)fetchHosts{
    
    self.httpDns.defaultTtl = kZYJQNGlobalConfiguration.dnsCacheTime;
    
    NSArray *nextFetchHosts = fetchHosts;
    
    nextFetchHosts = [self preFetchHosts:nextFetchHosts
                                     dns:kZYJQNGlobalConfiguration.dns];
    
    nextFetchHosts = [self preFetchHosts:nextFetchHosts
                                     dns:self.httpDns];
}

- (NSArray *)preFetchHosts:(NSArray <NSString *> *)preHosts
                       dns:(id <ZYJQNDnsDelegate>)dns{

    if (!preHosts || preHosts.count == 0) {
        return nil;
    }
    
    if (!dns) {
        return [preHosts copy];
    }
    
    NSMutableArray *failHosts = [NSMutableArray array];
    for (NSString *host in preHosts) {
        int rePreNum = 0;
        BOOL isSuccess = NO;
        
        while (rePreNum < kZYJQNGlobalConfiguration.dnsRepreHostNum) {
            if ([self preFetchHost:host dns:dns]) {
                isSuccess = YES;
                break;
            }
        }
        
        if (!isSuccess) {
            [failHosts addObject:host];
        }
    }
    return [failHosts copy];
}

- (BOOL)preFetchHost:(NSString *)preHost
                 dns:(id <ZYJQNDnsDelegate>)dns{
    
    if (!preHost || preHost.length == 0) {
        return NO;
    }
    
    NSArray<ZYJQNInetAddress *>* preAddressList = self.addressDictionary[preHost];
    if (preAddressList && [preAddressList.firstObject isValid]) {
        return YES;
    }
    
    NSArray <id <ZYJQNInetAddressDelegate> > * addressList = [dns lookup:preHost];
    if (addressList && addressList.count > 0) {
        NSMutableArray *addressListP = [NSMutableArray array];
        for (id <ZYJQNInetAddressDelegate>inetAddress in addressList) {
            ZYJQNInetAddress *address = [ZYJQNInetAddress inetAddress:inetAddress];
            if (address) {
                address.hostValue = preHost;
                if (!address.ttlValue) {
                    address.ttlValue = @(kZYJQNDefaultDnsCacheTime);
                }
                if (!address.timestampValue) {
                    address.timestampValue = @([[NSDate date] timeIntervalSince1970]);
                }
                [addressListP addObject:address];
            }
        }
        self.addressDictionary[preHost] = [addressListP copy];
        return YES;
    } else {
        return NO;
    }
}

//MARK: -- 加载和存储缓存信息
- (BOOL)recoverDnsCache:(NSDictionary *)dataDic{
    if (dataDic == nil) {
        return NO;
    }
    
    NSMutableDictionary *newAddressDictionary = [NSMutableDictionary dictionary];
    for (NSString *key in dataDic.allKeys) {
        NSArray *ips = dataDic[key];
        if ([ips isKindOfClass:[NSArray class]]) {
            
            NSMutableArray <ZYJQNInetAddress *> * addressList = [NSMutableArray array];
            
            for (NSDictionary *ipInfo in ips) {
                if ([ipInfo isKindOfClass:[NSDictionary class]]) {
                    ZYJQNInetAddress *address = [ZYJQNInetAddress inetAddress:ipInfo];
                    if (address) {
                        [addressList addObject:address];
                    }
                }
            }
            
            if (addressList.count > 0) {
                newAddressDictionary[key] = [addressList copy];
            }
        }
    }
    self.addressDictionary = newAddressDictionary;
    
    NSLog(@"== recoverDnsCache");
    return NO;
}

- (BOOL)recorderDnsCache{
    NSTimeInterval currentTime = [ZYJQNUtils currentTimestamp];
    NSString *localIp = [ZYJQNIP local];
    
    if (localIp == nil || localIp.length == 0) {
        return NO;
    }

    NSError *error;
    id <ZYJQNRecorderDelegate> recorder = [ZYJQNDnsCacheFile dnsCacheFile:kZYJQNGlobalConfiguration.dnscacheDir
                                                             error:&error];
    if (error) {
        return NO;
    }
    
    NSMutableDictionary *addressInfo = [NSMutableDictionary dictionary];
    for (NSString *key in self.addressDictionary.allKeys) {
       
        NSArray *addressModelList = self.addressDictionary[key];
        NSMutableArray * addressDicList = [NSMutableArray array];
        for (ZYJQNInetAddress *ipInfo in addressModelList) {
           
            NSDictionary *addressDic = [ipInfo toDictionary];
            if (addressDic) {
                [addressDicList addObject:addressDic];
            }
        }
       
        if (addressDicList.count > 0) {
            addressInfo[key] = addressDicList;
        }
    }
   
    ZYJQNDnsCacheInfo *cacheInfo = [ZYJQNDnsCacheInfo dnsCacheInfo:[NSString stringWithFormat:@"%.0lf",currentTime]
                                                     localIp:localIp
                                                        info:addressInfo];
    
    NSData *cacheData = [cacheInfo jsonData];
    if (!cacheData) {
        return NO;
    }
    [self setDnsCacheInfo:cacheInfo];
    [recorder set:localIp data:cacheData];
    return true;
}

- (void)clearPreHosts{
    [self.addressDictionary removeAllObjects];
}


//MARK: -- 获取预取hosts
- (NSArray <NSString *> *)getLocalPreHost{

    NSMutableArray *localHosts = [NSMutableArray array];
    
    NSArray *fixedHosts = [self getFixedZoneHosts];
    [localHosts addObjectsFromArray:fixedHosts];
    
    NSString *ucHost = kZYJQNPreQueryHost;
    [localHosts addObject:ucHost];
    
    return [localHosts copy];
}

- (NSArray <NSString *> *)getAllPreHost:(ZYJQNZone *)currentZone
                                  token:(NSString *)token{
    
    NSMutableSet *set = [NSMutableSet set];
    NSMutableArray *fetchHosts = [NSMutableArray array];
    
    NSArray *fixedHosts = [self getFixedZoneHosts];
    [fetchHosts addObjectsFromArray:fixedHosts];
    
    NSArray *autoHosts = [self getCurrentZoneHosts:currentZone token:token];
    [fetchHosts addObjectsFromArray:autoHosts];
    
    NSString *ucHost = kZYJQNPreQueryHost;
    [fetchHosts addObject:ucHost];
    
    NSArray *cacheHost = [self getCacheHosts];
    [fetchHosts addObjectsFromArray:cacheHost];
    
    NSMutableArray *fetchHostsFiltered = [NSMutableArray array];
    for (NSString *host in fetchHosts) {
        NSInteger countBeforeAdd = set.count;
        [set addObject:host];
        NSInteger countAfterAdd = set.count;
        if (countBeforeAdd < countAfterAdd) {
            [fetchHostsFiltered addObject:host];
        }
    }
    return [fetchHostsFiltered copy];
}

- (NSArray <NSString *> *)getCurrentZoneHosts:(ZYJQNZone *)currentZone
                                        token:(NSString *)token{
    if (!currentZone || !token) {
        return nil;
    }
    [currentZone preQuery:[ZYJQNUpToken parse:token] on:^(int code, ZYJQNHttpResponseInfo *httpResponseInfo) {
        dispatch_semaphore_signal(self.semaphore);
    }];
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    ZYJQNZonesInfo *autoZonesInfo = [currentZone getZonesInfoWithToken:[ZYJQNUpToken parse:token]];
    NSMutableArray *autoHosts = [NSMutableArray array];
    NSArray *zoneInfoList = autoZonesInfo.zonesInfo;
    for (ZYJQNZoneInfo *info in zoneInfoList) {
        for (NSString *host in info.upDomainsList) {
            [autoHosts addObject:host];
        }
    }
    return [autoHosts copy];
}

- (NSArray <NSString *> *)getFixedZoneHosts{
    NSArray <ZYJQNFixedZone *> *fixedZones = [ZYJQNFixedZone localsZoneInfo];
    NSMutableArray *localHosts = [NSMutableArray array];
    for (ZYJQNFixedZone *fixZone in fixedZones) {
        ZYJQNZonesInfo *zonesInfo = [fixZone getZonesInfoWithToken:nil];
        for (ZYJQNZoneInfo *zoneInfo in zonesInfo.zonesInfo) {
            for (NSString *host in zoneInfo.upDomainsList) {
                [localHosts addObject:host];
            }
        }
    }
    return [localHosts copy];
}

- (NSArray <NSString *> *)getCacheHosts{
    return self.addressDictionary.allKeys;
}


//MARK: --
- (BOOL)isDnsOpen{
    return [kZYJQNGlobalConfiguration isDnsOpen];
}

- (NSMutableDictionary<NSString *,NSArray<ZYJQNInetAddress *> *> *)addressDictionary{
    if (_addressDictionary == nil) {
        _addressDictionary = [NSMutableDictionary dictionary];
    }
    return _addressDictionary;
}

- (dispatch_semaphore_t)semaphore{
    if (_getAutoZoneSemaphore == NULL) {
        _getAutoZoneSemaphore = dispatch_semaphore_create(0);
    }
    return _getAutoZoneSemaphore;
}

- (ZYJQNDnsManager *)httpDns{
    if (_httpDns == nil) {
        ZYJQNResolver *systemDnsresolver = [ZYJQNResolver systemResolver];
        ZYJQNDnspodFree *dnspodFree = [[ZYJQNDnspodFree alloc] init];
        ZYJQNDnsManager *httpDns = [[ZYJQNDnsManager alloc] init:@[systemDnsresolver, dnspodFree]
                                               networkInfo:nil];
        _httpDns = httpDns;
    }
    return _httpDns;
}
@end


//MARK: -- DNS 事务
@implementation ZYJQNTransactionManager(Dns)
#define kZYJQNLoadLocalDnstransactionName @"ZYJQNLoadLocalDnstransaction"
#define kZYJQNDnsCheckAndPrefetchtransactionName @"ZYJQNDnsCheckAndPrefetchtransactionName"

- (void)addDnsLocalLoadTransaction{
    
    if ([kZYJQNDnsPrefetcher isDnsOpen] == NO) {
        return;
    }

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        ZYJQNTransaction *transaction = [ZYJQNTransaction transaction:kZYJQNLoadLocalDnstransactionName after:0 action:^{
            
            [kZYJQNDnsPrefetcher recoverCache];
            [kZYJQNDnsPrefetcher localFetch];
        }];
        [[ZYJQNTransactionManager shared] addTransaction:transaction];
    });
}

- (BOOL)addDnsCheckAndPrefetchTransaction:(ZYJQNZone *)currentZone token:(NSString *)token{
    if (!token) {
        return NO;
    }
    
    if ([kZYJQNDnsPrefetcher isDnsOpen] == NO) {
        return NO;
    }
    
    BOOL ret = NO;
    @synchronized (kZYJQNDnsPrefetcher) {
        
        ZYJQNTransactionManager *transactionManager = [ZYJQNTransactionManager shared];
        
        if (![transactionManager existtransactionsForName:token]) {
            ZYJQNTransaction *transaction = [ZYJQNTransaction transaction:token after:0 action:^{
               
                [kZYJQNDnsPrefetcher checkAndPrefetchDnsIfNeed:currentZone token:token];
            }];
            [transactionManager addTransaction:transaction];
            
            ret = YES;
        }
    }
    return ret;
}

- (void)setDnsCheckWhetherCachedValidTransactionAction{

    if ([kZYJQNDnsPrefetcher isDnsOpen] == NO) {
        return;
    }
    
    @synchronized (kZYJQNDnsPrefetcher) {
        
        ZYJQNTransactionManager *transactionManager = [ZYJQNTransactionManager shared];
        ZYJQNTransaction *transaction = [transactionManager transactionsForName:kZYJQNDnsCheckAndPrefetchtransactionName].firstObject;
        
        if (!transaction) {
            
            ZYJQNTransaction *transaction = [ZYJQNTransaction timeTransaction:kZYJQNDnsCheckAndPrefetchtransactionName
                                                                  after:10
                                                               interval:120
                                                                 action:^{
                [kZYJQNDnsPrefetcher checkWhetherCachedDnsValid];
            }];
            [transactionManager addTransaction:transaction];
        } else {
            [transactionManager performTransaction:transaction];
        }
    }
}

@end
