/*
 Copyright Spotify AB.
 SPDX-License-Identifier: Apache-2.0
 */

#import <SPTDataLoader/SPTDataLoaderResponse.h>

#import "SPTDataLoaderResponse+Private.h"

NS_ASSUME_NONNULL_BEGIN

NSString * const SPTDataLoaderResponseErrorDomain = @"com.sptdataloaderresponse.error";

static NSString * const SPTDataLoaderResponseHeaderRetryAfter = @"Retry-After";

@interface SPTDataLoaderResponse ()

@property (nonatomic, strong, readwrite) NSDictionary<NSString *, NSString *> *responseHeaders;
@property (nonatomic, strong, readwrite) NSError *error;
@property (nonatomic, strong, readwrite) NSData *body;
@property (nonatomic, assign, readwrite) NSTimeInterval requestTime;

@end

@implementation SPTDataLoaderResponse

#pragma mark Private

+ (instancetype)dataLoaderResponseWithRequest:(SPTDataLoaderRequest *)request response:(nullable NSURLResponse *)response
{
    return [[self alloc] initWithRequest:request response:response];
}

- (instancetype)initWithRequest:(SPTDataLoaderRequest *)request response:(nullable NSURLResponse *)response
{
    self = [super init];
    if (self) {
        _request = request;

        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            if (httpResponse.statusCode >= SPTDataLoaderResponseHTTPStatusCodeMovedMultipleChoices
                || httpResponse.statusCode <= SPTDataLoaderResponseHTTPStatusCodeSwitchProtocols) {
                _error = [NSError errorWithDomain:SPTDataLoaderResponseErrorDomain
                                             code:httpResponse.statusCode
                                         userInfo:nil];
            }
            _resolvedURL = httpResponse.URL;
            _responseHeaders = httpResponse.allHeaderFields;
            _statusCode = httpResponse.statusCode;
        }

        _retryAfter = [self retryAfterForHeaders:_responseHeaders];
    }

    return self;
}

- (BOOL)shouldRetry
{
    if ([self.error.domain isEqualToString:SPTDataLoaderResponseErrorDomain]) {
        switch (self.error.code) {
            case SPTDataLoaderResponseHTTPStatusCodeInvalid:
            case SPTDataLoaderResponseHTTPStatusCodeContinue:
            case SPTDataLoaderResponseHTTPStatusCodeSwitchProtocols:
            case SPTDataLoaderResponseHTTPStatusCodeOK:
            case SPTDataLoaderResponseHTTPStatusCodeCreated:
            case SPTDataLoaderResponseHTTPStatusCodeAccepted:
            case SPTDataLoaderResponseHTTPStatusCodeNonAuthoritiveInformation:
            case SPTDataLoaderResponseHTTPStatusCodeNoContent:
            case SPTDataLoaderResponseHTTPStatusCodeResetContent:
            case SPTDataLoaderResponseHTTPStatusCodePartialContent:
            case SPTDataLoaderResponseHTTPStatusCodeMovedMultipleChoices:
            case SPTDataLoaderResponseHTTPStatusCodeMovedPermanently:
            case SPTDataLoaderResponseHTTPStatusCodeFound:
            case SPTDataLoaderResponseHTTPStatusCodeSeeOther:
            case SPTDataLoaderResponseHTTPStatusCodeNotModified:
            case SPTDataLoaderResponseHTTPStatusCodeUseProxy:
            case SPTDataLoaderResponseHTTPStatusCodeUnused:
            case SPTDataLoaderResponseHTTPStatusCodeTemporaryRedirect:
            case SPTDataLoaderResponseHTTPStatusCodeBadRequest:
            case SPTDataLoaderResponseHTTPStatusCodeUnauthorised:
            case SPTDataLoaderResponseHTTPStatusCodePaymentRequired:
            case SPTDataLoaderResponseHTTPStatusCodeForbidden:
            case SPTDataLoaderResponseHTTPStatusCodeMethodNotAllowed:
            case SPTDataLoaderResponseHTTPStatusCodeNotAcceptable:
            case SPTDataLoaderResponseHTTPStatusCodeProxyAuthenticationRequired:
            case SPTDataLoaderResponseHTTPStatusCodeConflict:
            case SPTDataLoaderResponseHTTPStatusCodeGone:
            case SPTDataLoaderResponseHTTPStatusCodeLengthRequired: // We always include the content-length header
            case SPTDataLoaderResponseHTTPStatusCodePreconditionFailed:
            case SPTDataLoaderResponseHTTPStatusCodeRequestEntityTooLarge:
            case SPTDataLoaderResponseHTTPStatusCodeRequestURITooLong:
            case SPTDataLoaderResponseHTTPStatusCodeRequestRangeUnsatisifiable:
            case SPTDataLoaderResponseHTTPStatusCodeExpectationFail:
            case SPTDataLoaderResponseHTTPStatusCodeHTTPVersionNotSupported:
            case SPTDataLoaderResponseHTTPStatusCodeNotImplemented:
            case SPTDataLoaderResponseHTTPStatusCodeNotFound:
                return NO;
            case SPTDataLoaderResponseHTTPStatusCodeRequestTimeout:
            case SPTDataLoaderResponseHTTPStatusCodeUnsupportedMediaTypes:
            case SPTDataLoaderResponseHTTPStatusCodeInternalServerError:
            case SPTDataLoaderResponseHTTPStatusCodeBadGateway:
            case SPTDataLoaderResponseHTTPStatusCodeServiceUnavailable:
            case SPTDataLoaderResponseHTTPStatusCodeGatewayTimeout:
                return YES;
        }
    }

    if ([self.error.domain isEqualToString:NSURLErrorDomain]) {
        switch (self.error.code) {
            case NSURLErrorCancelled:
            case NSURLErrorUnknown:
            case NSURLErrorBadURL:
            case NSURLErrorUnsupportedURL:
            case NSURLErrorZeroByteResource:
            case NSURLErrorCannotDecodeRawData:
            case NSURLErrorCannotDecodeContentData:
            case NSURLErrorCannotParseResponse:
            case NSURLErrorFileDoesNotExist:
            case NSURLErrorNoPermissionsToReadFile:
            case NSURLErrorDataLengthExceedsMaximum:
            case NSURLErrorRedirectToNonExistentLocation:
            case NSURLErrorBadServerResponse:
            case NSURLErrorUserCancelledAuthentication:
            case NSURLErrorUserAuthenticationRequired:
            case NSURLErrorServerCertificateHasBadDate:
            case NSURLErrorServerCertificateUntrusted:
            case NSURLErrorServerCertificateHasUnknownRoot:
            case NSURLErrorServerCertificateNotYetValid:
            case NSURLErrorClientCertificateRejected:
            case NSURLErrorClientCertificateRequired:
                return NO;
            case NSURLErrorTimedOut:
            case NSURLErrorCannotFindHost:
            case NSURLErrorCannotConnectToHost:
            case NSURLErrorNetworkConnectionLost:
            case NSURLErrorDNSLookupFailed:
            case NSURLErrorHTTPTooManyRedirects:
            case NSURLErrorResourceUnavailable:
            case NSURLErrorNotConnectedToInternet:
            case NSURLErrorSecureConnectionFailed:
            case NSURLErrorCannotLoadFromNetwork:
                return YES;
        }
    }

    return NO;
}

- (nullable NSDate *)retryAfterForHeaders:(NSDictionary<NSString *, NSString *> *)headers
{
    static NSDateFormatter *httpDateFormatter;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        httpDateFormatter = [NSDateFormatter new];
        [httpDateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
    });

    NSTimeInterval retryAfterSeconds = [headers[SPTDataLoaderResponseHeaderRetryAfter] doubleValue];
    if (retryAfterSeconds != 0.0) {
        return [NSDate dateWithTimeIntervalSinceNow:retryAfterSeconds];
    }

    NSString *retryAfterValue = headers[SPTDataLoaderResponseHeaderRetryAfter];
    return [httpDateFormatter dateFromString:retryAfterValue];
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"<%@: %p URL = \"%@\"; status-code = %ld; headers = %@>", self.class, (void *)self, self.resolvedURL, (long)self.statusCode, self.responseHeaders];
}


@end

NS_ASSUME_NONNULL_END
