//
//  BUYIntegrationTest.m
//  Mobile Buy SDK
//
//  Created by Shopify.
//  Copyright (c) 2015 Shopify Inc. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//

@import UIKit;
@import XCTest;

#import <Buy/Buy.h>
#import "BUYTestConstants.h"
#import "BUYCheckout.h"
#import "BUYClientTestBase.h"
#import "BUYClient+Routing.h"
#import <OHHTTPStubs/OHHTTPStubs.h>
#import "OHHTTPStubsResponse+Helpers.h"

@interface BUYIntegrationTest : BUYClientTestBase
@end

@implementation BUYIntegrationTest {
	
	BUYModelManager *_modelManager;

	NSMutableArray *_products;
	
	BUYCart *_cart;
	BUYCheckout *_checkout;
	NSArray *_shippingRates;
	BUYGiftCard *_giftCard;
}

- (void)setUp
{
	[super setUp];
	
	_modelManager = [BUYModelManager modelManager];
	_products = [[NSMutableArray alloc] init];
	
	[self fetchProducts];
}

- (void)tearDown {
	[super tearDown];
	
	[OHHTTPStubs removeAllStubs];
}

#pragma mark - Helpers

- (void)fetchProducts
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testGetProductPage_0" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	[self.client getProductsByIds:self.productIds completion:^(NSArray *products, NSError *error) {
		
		XCTAssertNil(error, @"There was an error getting your store's products");
		XCTAssertNotNil(products, @"Add products to your store for tests to pass");
		
		[_products addObjectsFromArray:products];
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	
	NSLog(@"Fetched products (count: %tu", _products.count);
}

- (void)createCart
{
	_cart = [_modelManager insertCartWithJSONDictionary:nil];
	for (BUYProduct *product in _products) {
		[_cart addVariant:product.variants[0]];
	}
}

- (void)createCheckout
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCheckoutFlowUsingCreditCard_1" useMocks:[self shouldUseMocks]];
	
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	_checkout.shippingAddress = [self shippingAddress];
	_checkout.billingAddress = [self billingAddress];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	XCTAssertEqualObjects(_checkout.shippingAddress.address1, @"150 Elgin Street");
	XCTAssertEqualObjects(_checkout.billingAddress.address1, @"150 Elgin Street");
}

- (void)testCheckoutAttributesAndNotes
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCheckoutFlowUsingCreditCard_1" useMocks:[self shouldUseMocks]];
	
	[self createCart];
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];

	NSString *note = @"Order note";
	_checkout.note = note;
	NSArray *attributes = @[ [[BUYCheckoutAttribute alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"name" : @"attribute1", @"value" : @"value1" }],
							 [[BUYCheckoutAttribute alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"name" : @"attribute2", @"value" : @"value2" }] ];
	_checkout.attributes = [NSSet setWithArray:attributes];
	 
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		
		XCTAssertEqualObjects(returnedCheckout.note, note);
		XCTAssertEqualObjects(returnedCheckout.attributes, _checkout.attributes);
		
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)fetchShippingRates
{
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCheckoutFlowUsingCreditCard_2" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client getShippingRatesForCheckoutWithToken:_checkout.token completion:^(NSArray *shippingRates, BUYStatus status, NSError *error) {
		XCTAssertNil(error);
		XCTAssertEqual(status, BUYStatusComplete);
		
		XCTAssertNotNil(shippingRates);
		XCTAssertTrue([shippingRates count] > 0);
		
		_shippingRates = shippingRates;
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10.0 handler:^(NSError *error) {}];
}

- (void)updateCheckout
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCheckoutFlowUsingCreditCard_3" useMocks:[self shouldUseMocks]];
	_checkout.email = @"test@test.com";
	_checkout.shippingRate = _shippingRates[0];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client updateCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		XCTAssertNotNil(returnedCheckout.shippingRate.shippingRateIdentifier);
		XCTAssertNotNil(returnedCheckout.email);
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	XCTAssertEqualObjects(_checkout.email, @"test@test.com");
}

- (id<BUYPaymentToken>)addCreditCardToCheckout
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCheckoutFlowUsingCreditCard_4" useMocks:[self shouldUseMocks]];
	
	BUYCreditCard *creditCard = [self creditCard];
	__block id<BUYPaymentToken> token = nil;
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client storeCreditCard:creditCard checkout:_checkout completion:^(id<BUYPaymentToken> paymentToken, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(paymentToken);
		
		token = paymentToken;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	
	return token;
}

- (BUYCreditCard *)creditCard
{
	BUYCreditCard *creditCard = [[BUYCreditCard alloc] init];
	creditCard.number = @"4242424242424242";
	creditCard.expiryMonth = @"5";
	creditCard.expiryYear = @"2020";
	creditCard.cvv = @"123";
	creditCard.nameOnCard = @"John Smith";
	
	return creditCard;
}

- (void)completeCheckoutWithToken:(id<BUYPaymentToken>)paymentToken
{
	[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
		return [self shouldUseMocks];
	} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
		NSString *path = request.URL.absoluteString.lastPathComponent;
		if ([path isEqualToString:[self.client urlForCheckoutsCompletionWithToken:_checkout.token].lastPathComponent]) {
			return [OHHTTPStubsResponse responseWithKey:@"testCheckoutFlowUsingCreditCard_5"];
			
		} else if ([path isEqualToString:[self.client urlForCheckoutsProcessingWithToken:_checkout.token].lastPathComponent]) {
			return [OHHTTPStubsResponse responseWithKey:@"testCheckoutFlowUsingCreditCard_14"];
			
		} else if ([path isEqualToString:[self.client urlForCheckoutsWithToken:_checkout.token].lastPathComponent]) {
			return [OHHTTPStubsResponse responseWithKey:@"testCheckoutFlowUsingCreditCard_15"];
		}
		return [OHHTTPStubsResponse responseWithData:[NSData new] statusCode:500 headers:nil];;
	}];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client completeCheckoutWithToken:_checkout.token paymentToken:paymentToken completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		XCTAssertNotNil(returnedCheckout.order);
		XCTAssertNotNil(returnedCheckout.order.identifier);
		XCTAssertNotNil(returnedCheckout.order.statusURL);
		XCTAssertNotNil(returnedCheckout.order.name);
		
		_checkout = returnedCheckout;
		
		[expectation fulfill];
		
		[self confirmCreditCard];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)confirmCreditCard
{
	BUYCreditCard *creditCard = [self creditCard];
	BUYMaskedCreditCard *maskedCard = _checkout.creditCard;
	
	NSArray *names = [creditCard.nameOnCard componentsSeparatedByString:@" "];
	XCTAssertEqualObjects(maskedCard.firstName, names.firstObject);
	XCTAssertEqualObjects(maskedCard.lastName, names.lastObject);
	
	XCTAssertEqual(maskedCard.expiryMonth.integerValue, creditCard.expiryMonth.integerValue);
	XCTAssertEqual(maskedCard.expiryYear.integerValue, creditCard.expiryYear.integerValue);

	XCTAssertEqualObjects(maskedCard.firstDigits, [creditCard.number substringWithRange:NSMakeRange(0, 6)]);
	XCTAssertEqualObjects(maskedCard.lastDigits, [creditCard.number substringWithRange:NSMakeRange(12, 4)]);
}

#pragma mark - Tests

- (void)testApplyingGiftCardToCheckout
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingGiftCardToCheckout_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
}

- (void)applyGiftCardToCheckout
{
	// Check that we have a checkout with a paymentDue greater than 10
	XCTAssertGreaterThan([_checkout.paymentDue integerValue], 11);
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	[self.client applyGiftCardCode:self.giftCardCode toCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		
		XCTAssertNil(error);
		_checkout = checkout;
		_giftCard = _checkout.giftCards[0];
		XCTAssertNotNil(_giftCard);
		XCTAssertEqualObjects([self.giftCardCode substringWithRange:NSMakeRange(self.giftCardCode.length - 4, 4)], _giftCard.lastCharacters);
		
		NSDecimalNumber *amountUsed = [NSDecimalNumber decimalNumberWithString:@"11.00"];
		NSComparisonResult result = [_giftCard.amountUsed compare:amountUsed];
		XCTAssertEqual(result, NSOrderedSame);
		
		NSDecimalNumber *paymentDue = [originalPaymentDue decimalNumberBySubtracting:amountUsed];
		result = [_checkout.paymentDue compare:paymentDue];
		XCTAssertEqual(result, NSOrderedSame);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testApplyingInvalidGiftCardToCheckout
{
	[self createCart];
	[self createCheckout];
	
	// Check that we have a checkout with a paymentDue
	XCTAssertGreaterThan([_checkout.paymentDue integerValue], 1);
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingInvalidGiftCardToCheckout_2" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client applyGiftCardCode:@"000" toCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		XCTAssertNotNil(error);
		XCTAssertEqual(422, error.code);
		
		NSComparisonResult result = [_checkout.paymentDue compare:originalPaymentDue];
		XCTAssertEqual(result, NSOrderedSame);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testApplyingExpiredGiftCardToCheckout
{
	[self createCart];
	[self createCheckout];
	
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingInvalidGiftCardToCheckout_2" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client applyGiftCardCode:self.giftCardCodeExpired toCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		XCTAssertNotNil(error);
		XCTAssertEqual(422, error.code);
		
		NSComparisonResult result = [_checkout.paymentDue compare:originalPaymentDue];
		XCTAssertEqual(result, NSOrderedSame);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testRemovingGiftCardFromCheckout
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingGiftCardFromCheckout_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
	
	BUYGiftCard *giftCard = _checkout.giftCards[0];
	
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	NSDecimalNumber *giftCardValue = [giftCard.amountUsed copy];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingGiftCardFromCheckout_3" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeGiftCard:giftCard fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		//NOTE: Is this test failing? Make sure that you have configured giftCardCode above
		XCTAssertNil(error);
		XCTAssertTrue([checkout.giftCards count] == 0);
		
		NSDecimalNumber *paymentDue = [originalPaymentDue decimalNumberByAdding:giftCardValue];
		NSComparisonResult result = [_checkout.paymentDue compare:paymentDue];
		XCTAssertEqual(result, NSOrderedSame);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testRemovingInvalidGiftCardFromCheckout
{
	[self testApplyingGiftCardToCheckout];
	
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingInvalidGiftCardFromCheckout_2" useMocks:[self shouldUseMocks]];
	
	BUYGiftCard *giftCard = [[BUYGiftCard alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"id" : @(000) }];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeGiftCard:giftCard fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		XCTAssertNotNil(error);
		XCTAssertEqual(422, error.code);
		
		NSComparisonResult result = [_checkout.paymentDue compare:originalPaymentDue];
		XCTAssertEqual(result, NSOrderedSame);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testRemovingExpiredGiftCardFromCheckout
{
	[self testApplyingGiftCardToCheckout];
	
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingExpiredGiftCardFromCheckout_2" useMocks:[self shouldUseMocks]];
	
	BUYGiftCard *giftCard = [[BUYGiftCard alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"id" : self.giftCardIdExpired }];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeGiftCard:giftCard fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		XCTAssertNotNil(error);
		XCTAssertEqual(422, error.code);
		
		NSComparisonResult result = [_checkout.paymentDue compare:originalPaymentDue];
		XCTAssertEqual(result, NSOrderedSame);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testAddingTwoGiftCards
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingTwoGiftCardsToCheckout_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 1);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingTwoGiftCardsToCheckout_3" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardTwoToCheckout];
}

- (void)applyGiftCardTwoToCheckout
{
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client applyGiftCardCode:self.giftCardCode2 toCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		
		XCTAssertNil(error);
		_checkout = checkout;
		XCTAssertEqual([_checkout.giftCards count], 2);
		
		BUYGiftCard *giftCard2 = _checkout.giftCards[1];
		XCTAssertEqualObjects([self.giftCardCode2 substringWithRange:NSMakeRange(self.giftCardCode2.length - 4, 4)], giftCard2.lastCharacters);
		
		// Ensure the amount before applying the second gift card is greater than the new paymentDue amount
		XCTAssertGreaterThan([originalPaymentDue integerValue], [_checkout.paymentDue integerValue]);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testAddingThreeGiftCards
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingThreeGiftCardsToCheckout_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 1);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingThreeGiftCardsToCheckout_3" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardTwoToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 2);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testApplyingThreeGiftCardsToCheckout_4" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardThreeToCheckout];
}

- (void)applyGiftCardThreeToCheckout
{
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client applyGiftCardCode:self.giftCardCode3 toCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		//NOTE: Is this test failing? Make sure that you have configured giftCardCode above
		XCTAssertNil(error);
		_checkout = checkout;
		XCTAssertEqual([_checkout.giftCards count], 3);
		
		BUYGiftCard *giftCard3 = _checkout.giftCards[2];
		XCTAssertEqualObjects([self.giftCardCode3 substringWithRange:NSMakeRange(self.giftCardCode3.length - 4, 4)], giftCard3.lastCharacters);
		
		// Ensure the amount before applying the third gift card is greater than the new paymentDue amount
		XCTAssertGreaterThan([originalPaymentDue integerValue], [_checkout.paymentDue integerValue]);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testRemovingSecondGiftCard
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingSecondGiftCard_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 1);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingSecondGiftCard_3" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardTwoToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 2);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingSecondGiftCard_4" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardThreeToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 3);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingSecondGiftCard_5" useMocks:[self shouldUseMocks]];
	
	[self removeSecondGiftCard];
}

- (void)removeSecondGiftCard
{
	BUYGiftCard *giftCard2 = _checkout.giftCards[1];
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeGiftCard:giftCard2 fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		//NOTE: Is this test failing? Make sure that you have configured giftCardCode above
		XCTAssertNil(error);
		_checkout = checkout;
		XCTAssertEqual([_checkout.giftCards count], 2);
		
		BUYGiftCard *giftCard3 = _checkout.giftCards[1];
		XCTAssertEqualObjects([self.giftCardCode3 substringWithRange:NSMakeRange(self.giftCardCode3.length - 4, 4)], giftCard3.lastCharacters);
		
		// Ensure the amount after removing the second gift card is less than the new paymentDue amount
		XCTAssertLessThan([originalPaymentDue integerValue], [_checkout.paymentDue integerValue]);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testRemovingFirstGiftCard
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingFirstGiftCard_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 1);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingFirstGiftCard_3" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardTwoToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 2);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingFirstGiftCard_4" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardThreeToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 3);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingFirstGiftCard_5" useMocks:[self shouldUseMocks]];

	[self removeSecondGiftCard];
	
	XCTAssertEqual([_checkout.giftCards count], 2);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingFirstGiftCard_6" useMocks:[self shouldUseMocks]];
	
	[self removeFirstGiftCard];
}

- (void)removeFirstGiftCard
{
	BUYGiftCard *giftCard1 = _checkout.giftCards[0];
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeGiftCard:giftCard1 fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		//NOTE: Is this test failing? Make sure that you have configured giftCardCode above
		XCTAssertNil(error);
		_checkout = checkout;
		XCTAssertEqual([_checkout.giftCards count], 1);
		
		BUYGiftCard *giftCard3 = _checkout.giftCards[0];
		XCTAssertEqualObjects([self.giftCardCode3 substringWithRange:NSMakeRange(self.giftCardCode3.length - 4, 4)], giftCard3.lastCharacters);
		
		// Ensure the amount after removing the first gift card is less than the new paymentDue amount
		XCTAssertLessThan([originalPaymentDue integerValue], [_checkout.paymentDue integerValue]);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testRemovingAllGiftCards
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingAllGiftCards_2" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 1);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingAllGiftCards_3" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardTwoToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 2);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingAllGiftCards_4" useMocks:[self shouldUseMocks]];
	
	[self applyGiftCardThreeToCheckout];
	
	XCTAssertEqual([_checkout.giftCards count], 3);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingAllGiftCards_5" useMocks:[self shouldUseMocks]];
	
	[self removeSecondGiftCard];
	
	XCTAssertEqual([_checkout.giftCards count], 2);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingAllGiftCards_6" useMocks:[self shouldUseMocks]];
	
	[self removeFirstGiftCard];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testRemovingAllGiftCards_7" useMocks:[self shouldUseMocks]];
	
	[self removeAllGiftCards];
}

- (void)removeAllGiftCards
{
	BUYGiftCard *giftCard3 = _checkout.giftCards[0];
	NSDecimalNumber *originalPaymentDue = [_checkout.paymentDue copy];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeGiftCard:giftCard3 fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		//NOTE: Is this test failing? Make sure that you have configured giftCardCode above
		XCTAssertNil(error);
		_checkout = checkout;
		XCTAssertEqual([_checkout.giftCards count], 0);
		
		// Ensure the amount after removing all gift cards is less than the new paymentDue amount
		XCTAssertLessThan([originalPaymentDue integerValue], [_checkout.paymentDue integerValue]);
		
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testCheckoutWithInvalidShop
{
	[self createCart];
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testInvalidIntegrationBadShopUrl_0" useMocks:[self shouldUseMocks]];
	
	self.client = [[BUYClient alloc] initWithShopDomain:@"asdfdsasdfdsasdfdsadsfowinfaoinfw.myshopify.com" apiKey:self.apiKey appId:@"88234"];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
		XCTAssertNotNil(error);
		XCTAssertEqualObjects(error.domain, @"BUYShopifyErrorDomain");
		XCTAssertEqual(error.code, 404);
		[expectation fulfill];
		
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testFetchingShippingRatesWithoutShippingAddressShouldReturnPreconditionFailed
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testFetchingShippingRatesWithoutShippingAddress_1" useMocks:[self shouldUseMocks]];
	
	[self createCart];
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testFetchingShippingRatesWithoutShippingAddress_2" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation2 = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	[self.client getShippingRatesForCheckoutWithToken:_checkout.token completion:^(NSArray *returnedShippingRates, BUYStatus status, NSError *error) {
		XCTAssertEqual(BUYStatusPreconditionFailed, status);
		[expectation2 fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testFetchingShippingRatesForInvalidCheckoutShouldReturnNotFound
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testFetchingShippingRatesWithInvalidCheckout_1" useMocks:[self shouldUseMocks]];
	
	BUYCheckout *checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:nil];
	checkout.token = @"bananaaaa";
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client getShippingRatesForCheckoutWithToken:checkout.token completion:^(NSArray *returnedShippingRates, BUYStatus status, NSError *error) {
		XCTAssertEqual(BUYStatusNotFound, status);
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testCheckoutFlowUsingCreditCard
{
	[self createCart];
	[self createCheckout];
	[self fetchShippingRates];
	[self updateCheckout];
	
	[self completeCheckoutWithToken:[self addCreditCardToCheckout]];
}

- (void)testCheckoutWithAPartialAddress
{
	[self createCart];
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	_checkout.shippingAddress = [self partialShippingAddress];

	if ([_checkout.shippingAddress isPartialAddress]) {
		_checkout.partialAddressesValue = YES;
	}
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCheckoutWithAPartialAddress" useMocks:[self shouldUseMocks]];
	
	//Create the checkout
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	//Fetch the rates
	[self fetchShippingRates];
	
	//Select the first rate
	[self updateCheckout];
	
	//Update with full addresses
	_checkout.shippingAddress = [self shippingAddress];
	_checkout.billingAddress = [self billingAddress];
	
	[self updateCheckout];
	
	//We use a credit card here because we're not generating apple pay tokens in the tests
	id<BUYPaymentToken> token = [self addCreditCardToCheckout];
	[self completeCheckoutWithToken:token];
}

- (void)testCheckoutCreationWithApplicableDiscount
{
	[self createCart];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCreateCheckoutWithValidDiscount_1" useMocks:[self shouldUseMocks]];
	
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	_checkout.discount = [self applicableDiscount];
	
	// Create the checkout
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		// NOTE: Is this test failing? Make sure that you create the following discounts on your test shop:
		//
		// applicable 	- this should be valid
		// inapplicable - this should be invalid (i.e expired)
		//
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	XCTAssertNotNil(_checkout.discount);
	XCTAssertTrue(_checkout.discount.applicable);
}

- (void)testCheckoutCreationWithInapplicableDiscount
{
	[self createCart];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCreateCheckoutWithExpiredDiscount_1" useMocks:[self shouldUseMocks]];
	
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	_checkout.discount = [self inapplicableDiscount];
	
	//Create the checkout
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssert(error);
		XCTAssertEqual(422, error.code); //This is a validation error
		XCTAssertNil(returnedCheckout);
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testCheckoutCreationWithNonExistentDiscount
{
	[self createCart];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCreateCheckoutWithNonExistentDiscount_1" useMocks:[self shouldUseMocks]];
	
	_checkout = [[BUYCheckout alloc] initWithModelManager:_modelManager cart:_cart];
	_checkout.discount = [self nonExistentDiscount];
	
	//Create the checkout
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNotNil(error);
		XCTAssertEqual(error.code, 422);
		NSDictionary *info = [error userInfo];
		XCTAssertNotNil(info[@"errors"][@"checkout"][@"discount"][@"code"]);
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testCheckoutUpdateWithApplicableDiscount
{
	[self createCart];
	[self createCheckout];
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCreateCheckoutWithValidDiscount_1" useMocks:[self shouldUseMocks]];
	
	_checkout.discount = [self applicableDiscount];
	
	// Update the checkout
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client updateCheckout:_checkout completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		XCTAssertNil(error);
		XCTAssertNotNil(returnedCheckout);
		_checkout = returnedCheckout;
		[expectation fulfill];
	}];
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
	XCTAssertNotNil(_checkout.discount);
	XCTAssertTrue(_checkout.discount.applicable);
}

- (void)testGetCheckoutWithInvalidToken
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testGetCheckoutWithInvalidToken_0" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client getCheckoutWithToken:@"zzzzzzzzzzz" completion:^(BUYCheckout *checkout, NSError *error) {
		
		XCTAssertEqual(404, error.code);
		[expectation fulfill];

	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

#pragma mark - Test Data

- (BUYAddress *)billingAddress
{
	BUYAddress *address = [_modelManager insertAddressWithJSONDictionary:nil];
	address.address1 = @"150 Elgin Street";
	address.address2 = @"8th Floor";
	address.city = @"Ottawa";
	address.company = @"Shopify Inc.";
	address.firstName = @"Test Guy";
	address.lastName = @"マクドナルドス";
	address.phone = @"1-555-555-5555";
	address.countryCode = @"CA";
	address.provinceCode = @"ON";
	address.zip = @"K1N5T5";
	return address;
}

- (BUYAddress *)shippingAddress
{
	BUYAddress *address = [_modelManager insertAddressWithJSONDictionary:nil];
	address.address1 = @"150 Elgin Street";
	address.address2 = @"8th Floor";
	address.city = @"Ottawa";
	address.company = @"Shopify Inc.";
	address.firstName = @"Tobi";
	address.lastName = @"Lütke";
	address.phone = @"1-555-555-5555";
	address.countryCode = @"CA";
	address.provinceCode = @"ON";
	address.zip = @"K1N5T5";
	return address;
}

- (BUYAddress *)partialShippingAddress
{
	BUYAddress *address = [_modelManager insertAddressWithJSONDictionary:nil];
	address.address1 = nil;
	address.city = @"Ottawa";
	address.firstName = nil;
	address.lastName = nil;
	address.countryCode = @"CA";
	address.provinceCode = @"ON";
	address.zip = @"K1N5T5";
	address.phone = nil;
	return address;
}

- (BUYDiscount *)applicableDiscount
{
	return [_modelManager discountWithCode:self.discountCodeValid];
}

- (BUYDiscount *)inapplicableDiscount
{
	return [_modelManager discountWithCode:self.discountCodeExpired];
}

- (BUYDiscount *)nonExistentDiscount
{
	return [_modelManager discountWithCode:@"asdfasdfasdfasdf"];
}

- (void)testExpiringCheckout
{
	[self createCart];
	[self createCheckout];
	XCTAssertGreaterThanOrEqual([_checkout.lineItems count], 1);
	XCTAssertNotNil(_checkout.reservationTime);
	XCTAssertTrue([_checkout.reservationTime isKindOfClass:[NSNumber class]]);
	XCTAssertEqual(300, _checkout.reservationTime.intValue);
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testExpiringCheckout_2" useMocks:[self shouldUseMocks]];
	
	// Expire the checkout
	XCTestExpectation *expectation2 = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client removeProductReservationsFromCheckoutWithToken:_checkout.token completion:^(BUYCheckout *returnedCheckout, NSError *error) {
		
		XCTAssertNil(error);
		
		_checkout = returnedCheckout;
		XCTAssertGreaterThanOrEqual([_checkout.lineItems count], 1);
		XCTAssertEqual(0, _checkout.reservationTime.intValue);
		[expectation2 fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

- (void)testCallbackQueue
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testGetShop_0" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	XCTestExpectation *expectation2 = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	[self.client getShop:^(BUYShop *shop, NSError *error) {
		
		BOOL isMainThread = [NSThread isMainThread];
		XCTAssertTrue(isMainThread);
		[expectation fulfill];
	}];
	
	NSString *shopDomain = [self shouldUseMocks] ? BUYShopDomain_Placeholder : self.shopDomain;
	NSString *apiKey = [self shouldUseMocks] ? BUYAPIKey_Placeholder : self.apiKey;
	NSString *appId = [self shouldUseMocks] ? BUYAppId_Placeholder : self.appId;
	BUYClient *testClient = [[BUYClient alloc] initWithShopDomain:shopDomain apiKey:apiKey appId:appId];
	testClient.callbackQueue = [NSOperationQueue new];
	
	[testClient getShop:^(BUYShop *shop, NSError *error) {
		BOOL isMainThread = [NSThread isMainThread];
		XCTAssertFalse(isMainThread);
		[expectation2 fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
		XCTAssertNil(error);
	}];
}

@end
