//
//  NSURLCache+TNLAdditionsTest.m
//  TwitterNetworkLayer
//
//  Created on 10/28/14.
//  Copyright © 2020 Twitter. All rights reserved.
//

#import "NSURLCache+TNLAdditions.h"
#import "TNL_Project.h"
#import "TNLHTTPRequest.h"
#import "TNLNetwork.h"
#import "TNLPseudoURLProtocol.h"
#import "TNLRequestDelegate.h"
#import "TNLRequestOperation.h"
#import "TNLRequestOperationQueue.h"
#import "TNLResponse_Project.h"

@import XCTest;

@interface NSURLCache_TNLAdditionsTest : XCTestCase
@property (atomic) NSUInteger spinUps;
@end

@implementation NSURLCache_TNLAdditionsTest

- (void)testNSURLImpotentCache
{
    NSURLCache *cache = [NSURLCache tnl_impotentURLCache];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.dummy.com/path?something=else"]];
    NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:@{ @"Header1" : @"Value1" }];
    NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:[@"Random Data" dataUsingEncoding:NSUTF8StringEncoding] userInfo:nil storagePolicy:NSURLCacheStorageAllowed];

    XCTAssertEqual(0UL, cache.currentMemoryUsage);
    XCTAssertEqual(0UL, cache.currentDiskUsage);
    XCTAssertNil([cache cachedResponseForRequest:request]);

    [cache storeCachedResponse:cachedResponse forRequest:request];

    XCTAssertEqual(0UL, cache.currentMemoryUsage);
    XCTAssertEqual(0UL, cache.currentDiskUsage);
    XCTAssertNil([cache cachedResponseForRequest:request]);
}

+ (TNLResponse *)GETResponseWithURL:(NSURL *)URL config:(TNLRequestConfiguration *)config
{
    __block TNLResponse *response = nil;
    TNLHTTPRequest *request = [TNLHTTPRequest GETRequestWithURL:URL HTTPHeaderFields:nil];
    TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:request
                                                          configuration:config
                                                             completion:^(TNLRequestOperation *operation, TNLResponse *opResponse) {
        response = opResponse;
    }];
    [[TNLRequestOperationQueue defaultOperationQueue] enqueueRequestOperation:op];
    [op waitUntilFinishedWithoutBlockingRunLoop];
    return response;
}

- (void)testCacheHitDetection
{
    NSURL *URL = [NSURL URLWithString:@"http://cache.dummy.com/cacheable"];
    NSData *body = [URL.absoluteString dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *headers = @{
                              @"content-length" : [@(body.length) description],
                              @"cache-control" : @"max-age=10000",
                              @"date" : TNLHTTPDateToString([NSDate date], TNLHTTPDateFormatAuto),
                              };
    NSHTTPURLResponse *URLResponse = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headers];
    [TNLPseudoURLProtocol registerURLResponse:URLResponse body:body withEndpoint:URL];
    tnl_defer(^{
        [TNLPseudoURLProtocol unregisterEndpoint:URL];
    });

    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:NSStringFromSelector(_cmd)];
    [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
    NSURLCache *cache;
    if (tnl_available_ios_13) {
        cache = [[NSURLCache alloc] initWithMemoryCapacity:1024*1024*10
                                              diskCapacity:1024*1024*10
                                              directoryURL:[NSURL URLWithString:path]];
    }
#if !TARGET_OS_MACCATALYST
    else {
        cache = [[NSURLCache alloc] initWithMemoryCapacity:1024*1024*10
                                              diskCapacity:1024*1024*10
                                                  diskPath:path];

    }
#endif
    [cache removeAllCachedResponses];
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // give cache time to purge
    tnl_defer(^{
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; // give cache time to store cache entries
        [cache removeAllCachedResponses];
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // give cache time to purge
    });

    TNLResponse *response = nil;
    TNLMutableRequestConfiguration *config = [TNLMutableRequestConfiguration defaultConfiguration];
    config.URLCache = cache;
    config.protocolOptions = TNLRequestProtocolOptionPseudo;

    config.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
    response = [[self class] GETResponseWithURL:URL config:config];
    XCTAssertEqualObjects(response.info.data, body);
    XCTAssertEqual(response.info.source, TNLResponseSourceNetworkRequest);

    response = [[self class] GETResponseWithURL:URL config:config];
    XCTAssertEqualObjects(response.info.data, body);
    XCTAssertEqual(response.info.source, TNLResponseSourceLocalCache);

    config.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    response = [[self class] GETResponseWithURL:URL config:config];
    XCTAssertEqualObjects(response.info.data, body);
    XCTAssertEqual(response.info.source, TNLResponseSourceNetworkRequest);

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; // give cache time to store cache entries
    [cache removeAllCachedResponses];
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // give cache time to purge

    config.cachePolicy = NSURLRequestReturnCacheDataDontLoad;
    response = [[self class] GETResponseWithURL:URL config:config];
    XCTAssertNil(response.info.data);
    XCTAssertNotNil(response.operationError);
    XCTAssertEqual(response.info.source, TNLResponseSourceNetworkRequest);
}

- (void)testSessionCoalescing
{
    NSURL *URL = [NSURL URLWithString:@"http://cache.dummy.com/cacheable"];
    NSData *body = [URL.absoluteString dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *headers = @{
                              @"content-length" : [@(body.length) description],
                              @"cache-control" : @"max-age=10000",
                              @"date" : TNLHTTPDateToString([NSDate date], TNLHTTPDateFormatAuto),
                              };
    NSHTTPURLResponse *URLResponse = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headers];
    [TNLPseudoURLProtocol registerURLResponse:URLResponse body:body withEndpoint:URL];
    tnl_defer(^{
        [TNLPseudoURLProtocol unregisterEndpoint:URL];
    });

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(_didSpinUpSession:)
                                                 name:TNLNetworkDidSpinUpSessionNotification
                                               object:nil];

    NSURLCache *cache = nil;
    TNLResponse *response = nil;
    TNLMutableRequestConfiguration *config = [TNLMutableRequestConfiguration defaultConfiguration];
    config.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    config.protocolOptions = TNLRequestProtocolOptionPseudo;

    // warm the NSURLSessions
    response = [[self class] GETResponseWithURL:URL config:config];
    const NSUInteger originalSpinUpCount = self.spinUps;

    // 1) shared cache

    cache = [NSURLCache sharedURLCache];
    config.URLCache = cache;
    tnl_defer(^{
        [cache removeAllCachedResponses];
    });
    response = [[self class] GETResponseWithURL:URL config:config];


    // 2) proxy shared cache

    cache = [NSURLCache tnl_sharedURLCacheProxy];
    config.URLCache = cache;
    tnl_defer(^{
        [cache removeAllCachedResponses];
    });
    response = [[self class] GETResponseWithURL:URL config:config];

    // 3) impotent cache

    cache = [NSURLCache tnl_impotentURLCache];
    config.URLCache = cache;
    tnl_defer(^{
        [cache removeAllCachedResponses];
    });
    response = [[self class] GETResponseWithURL:URL config:config];

    // 4) nil cache

    cache = nil;
    config.URLCache = cache;
    tnl_defer(^{
        [cache removeAllCachedResponses];
    });
    response = [[self class] GETResponseWithURL:URL config:config];

    // TNL will use demuxing to avoid spinning up multiple sessions
    XCTAssertEqual(originalSpinUpCount, self.spinUps);
}

- (void)_didSpinUpSession:(NSNotification *)note
{
    self.spinUps++;
}

@end
