//
//  BUYClientTest_Customer.h
//  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 XCTest;
#import "BUYClientTestBase.h"
#import "BUYClient+Customers.h"
#import "BUYAccountCredentials.h"
#import "BUYError+BUYAdditions.h"
#import <OHHTTPStubs/OHHTTPStubs.h>
#import "OHHTTPStubsResponse+Helpers.h"

@interface BUYClientTest_Customer : BUYClientTestBase

@property (strong, nonatomic) BUYCustomer *customer;
@property (strong, nonatomic) BUYAddress *createdAddress;

@end

@implementation BUYClientTest_Customer

#pragma mark - Lifecycle -

- (void)setUp
{
	[super setUp];
	[self loginDefaultCustomer];
}

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

- (void)loginDefaultCustomer
{
	if (self.customer) {
		return;
	}
	
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerLogin" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client loginCustomerWithCredentials:[self credentialsForLogin] callback:^(BUYCustomer *customer, BUYCustomerToken *token, NSError *error) {
		
		XCTAssertNil(error);
		XCTAssertNotNil(customer);
		XCTAssertEqualObjects(customer.email, self.customerEmail);
		
		self.customer = customer;
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
		XCTAssertNil(error);
	}];
}

#pragma mark - Creation -

- (void)testCustomerDuplicateEmail
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerDuplicateEmail" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCustomerWithCredentials:[self credentialsForCreation] callback:^(BUYCustomer *customer, BUYCustomerToken *token, NSError *error) {
		
		XCTAssertNil(customer);
		XCTAssertNotNil(error);
		
		NSArray *errors = [BUYError errorsFromSignUpJSON:error.userInfo];
		XCTAssertEqual(errors.count, 1);
		
		BUYError *customerError = errors[0];
		
		XCTAssertEqualObjects(customerError.code, @"taken");
		XCTAssertEqualObjects(customerError.options[@"rescue_from_duplicate"], @YES);
		XCTAssertEqualObjects(customerError.options[@"value"], self.customerEmail);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
		XCTAssertNil(error);
	}];
}

- (void)testCustomerInvalidEmailPassword
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerInvalidEmailPassword" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client createCustomerWithCredentials:[self credentialsForFailure] callback:^(BUYCustomer *customer, BUYCustomerToken *token, NSError *error) {
		
		XCTAssertNil(customer);
		XCTAssertNotNil(error);
		
		NSArray<BUYError *> *errors = [BUYError errorsFromSignUpJSON:error.userInfo];
		XCTAssertEqual(errors.count, 2);

		BUYError *emailError = errors[0];
		XCTAssertEqualObjects(emailError.code, @"invalid");
		
		BUYError *passwordError = errors[1];
		XCTAssertEqualObjects(passwordError.code, @"too_short");
		XCTAssertEqualObjects(passwordError.options[@"count"], @5);

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

#pragma mark - Auth -

- (void)testCustomerLogin
{
	[self loginDefaultCustomer];
}

- (void)testCustomerLogout
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerLogout" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client logoutCustomerCallback:^(BUYStatus status, NSError * _Nullable error) {
		
		XCTAssertNil(error);
		XCTAssertEqual(status, 204);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {}];
}

#pragma mark - Orders -

- (void)testCustomerOrders
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerGetOrders" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client getOrdersForCustomerCallback:^(NSArray<BUYOrder *> * _Nullable orders, NSError * _Nullable error) {
		
		XCTAssertNotNil(orders);
		XCTAssertNil(error);
		
		XCTAssertTrue(orders.count > 0);
		XCTAssertTrue([orders.firstObject isKindOfClass:[BUYOrder class]]);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10.0 handler:^(NSError *error) {}];
}

- (void)testCustomerOrderWithID
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerGetOrder" useMocks:[self shouldUseMocks]];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client getOrderWithID:self.customerOrderIDs.firstObject callback:^(BUYOrder * _Nullable order, NSError * _Nullable error) {
		
		XCTAssertNil(error);
		XCTAssertTrue([order isKindOfClass:[BUYOrder class]]);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10.0 handler:^(NSError *error) {}];
}

- (void)testCustomerUpdate
{
	[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerLogin" useMocks:[self shouldUseMocks]];

	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];

	[self.client updateCustomer:_customer callback:^(BUYCustomer * _Nullable customer, NSError * _Nullable error) {

		XCTAssertNil(error);
		XCTAssertNotNil(customer);
 
		[expectation fulfill];
 	}];

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

#pragma mark - Address -

- (void)testGetAddresses
{
	[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
		return [self shouldUseMocks];
	} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
		return [OHHTTPStubsResponse responseWithKey:@"testCustomerAddresses"];
	}];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client  getAddressesCallback:^(NSArray<BUYAddress *> * _Nullable addresses, NSError * _Nullable error) {
		
		XCTAssertNotNil(addresses);
		XCTAssertTrue(addresses.count > 0);
		XCTAssertTrue([addresses.firstObject isKindOfClass:[BUYAddress class]]);
		XCTAssertNil(error);
		
		NSArray *defaultAddresses = [addresses filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isDefault == 1"]];
		XCTAssertEqual(1, defaultAddresses.count);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {}];
}

- (void)testAddressCRUD
{
	[self createAddress];
	[self getAddress];
	[self updateAddress];
	[self deleteAddress];
}

- (void)createAddress
{
	[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
		return [self shouldUseMocks];
	} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
		return [OHHTTPStubsResponse responseWithKey:@"testCustomerAddress1"];
	}];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	BUYAddress *address = [self address];
	
	[self.client createAddress:address callback:^(BUYAddress * _Nullable returnedAddress, NSError * _Nullable error) {
		
		[OHHTTPStubs stubUsingResponseWithKey:@"testCustomerLogin" useMocks:[self shouldUseMocks]];
		
		XCTAssertNotNil(returnedAddress);
		XCTAssertNil(error);
		
		XCTAssertNotNil(returnedAddress.identifier);
		
		XCTAssertEqualObjects(address.address1,     returnedAddress.address1);
		XCTAssertEqualObjects(address.city,         returnedAddress.city);
		XCTAssertEqualObjects(address.province,     returnedAddress.province);
		XCTAssertEqualObjects(address.provinceCode, returnedAddress.provinceCode);
		XCTAssertEqualObjects(address.country,      returnedAddress.country);
		XCTAssertEqualObjects(address.countryCode,  returnedAddress.countryCode);
		XCTAssertEqualObjects(address.zip,          returnedAddress.zip);
		
		self.createdAddress = returnedAddress;
		
		[expectation fulfill];
	}];

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

- (void)getAddress
{
	[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
		return [self shouldUseMocks];
	} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
		return [OHHTTPStubsResponse responseWithKey:@"testCustomerAddress1"];
	}];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client getAddressWithID:self.createdAddress.identifier callback:^(BUYAddress * _Nullable address, NSError * _Nullable error) {
		
		XCTAssertNotNil(address);
		XCTAssertNil(error);
		XCTAssertEqualObjects(address.identifier, self.createdAddress.identifier);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {}];
}

- (void)updateAddress
{
	[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
		return [self shouldUseMocks];
	} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
		return [OHHTTPStubsResponse responseWithKey:@"testCustomerAddress2"];
	}];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	
	BUYAddress *modifiedAddress = [self addressByModyfyingAddress:self.createdAddress];
	
	[self.client updateAddress:modifiedAddress callback:^(BUYAddress * _Nullable returnedAddress, NSError * _Nullable error) {
		
		XCTAssertNotNil(returnedAddress);
		XCTAssertNil(error);
		
		XCTAssertEqualObjects(modifiedAddress.address1,     returnedAddress.address1);
		XCTAssertEqualObjects(modifiedAddress.city,         returnedAddress.city);
		XCTAssertEqualObjects(modifiedAddress.province,     returnedAddress.province);
		XCTAssertEqualObjects(modifiedAddress.provinceCode, returnedAddress.provinceCode);
		XCTAssertEqualObjects(modifiedAddress.country,      returnedAddress.country);
		XCTAssertEqualObjects(modifiedAddress.countryCode,  returnedAddress.countryCode);
		XCTAssertEqualObjects(modifiedAddress.zip,          returnedAddress.zip);
		
		self.createdAddress = returnedAddress;
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {}];
}

- (void)deleteAddress
{
	[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
		return [self shouldUseMocks];
	} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
		return [OHHTTPStubsResponse responseWithKey:@"testCustomerAddressDelete"];
	}];
	
	XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
	[self.client  deleteAddressWithID:self.createdAddress.identifier callback:^(BUYStatus status, NSError * _Nullable error) {
		
		XCTAssertEqual(status, 204);
		XCTAssertNil(error);
		
		[expectation fulfill];
	}];
	
	[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {}];
}

#pragma mark - Address -

- (BUYAddress *)address
{
	BUYAddress *address  = [[BUYAddress alloc] initWithModelManager:self.client.modelManager JSONDictionary:nil];
	address.address1     = @"3892 Streewell Rd.";
	address.city         = @"Toronto";
	address.province     = @"Ontario";
	address.provinceCode = @"ON";
	address.country      = @"Canada";
	address.countryCode  = @"CA";
	address.zip          = @"L8S 2W2";
	
	return address;
}

- (BUYAddress *)addressByModyfyingAddress:(BUYAddress *)oldAddress;
{
	BUYAddress *address  = [[BUYAddress alloc] initWithModelManager:self.client.modelManager JSONDictionary:nil];
	address.identifier   = oldAddress.identifier;
	address.address1     = @"8493 Southwest St.";
	address.city         = @"Vancouver";
	address.province     = @"British Columbia";
	address.provinceCode = @"BC";
	address.country      = @"Canada";
	address.countryCode  = @"CA";
	address.zip          = @"T3G 4D9";
	
	return address;
}

#pragma mark - Credentials -

- (BUYAccountCredentials *)credentialsForLogin
{
	BUYAccountCredentialItem *email    = [BUYAccountCredentialItem itemWithEmail:self.customerEmail];
	BUYAccountCredentialItem *password = [BUYAccountCredentialItem itemWithPassword:self.customerPassword];
	return [BUYAccountCredentials credentialsWithItems:@[email, password]];
}

- (BUYAccountCredentials *)credentialsForCreation
{
	BUYAccountCredentialItem *email     = [BUYAccountCredentialItem itemWithEmail:self.customerEmail];
	BUYAccountCredentialItem *password  = [BUYAccountCredentialItem itemWithPassword:self.customerPassword];
	return [BUYAccountCredentials credentialsWithItems:@[email, password]];
}

- (BUYAccountCredentials *)credentialsForFailure
{
	BUYAccountCredentialItem *email     = [BUYAccountCredentialItem itemWithEmail:@"a"];
	BUYAccountCredentialItem *password  = [BUYAccountCredentialItem itemWithPassword:@"b"];
	return [BUYAccountCredentials credentialsWithItems:@[email, password]];
}

@end