//
//  InAppReceipt.swift
//  SwiftyStoreKit
//
//  Created by phimage on 22/12/15.
// Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com)
//
// 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 Foundation

// https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

public class AppleReceiptValidator: ReceiptValidator {

	public enum VerifyReceiptURLType: String {
		case production = "https://buy.itunes.apple.com/verifyReceipt"
		case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"
	}

    /// You should always verify your receipt first with the `production` service
    /// Note: will auto change to `.sandbox` and validate again if received a 21007 status code from Apple
    public var service: VerifyReceiptURLType
    private let sharedSecret: String
    private let excludeOldTransactions: Bool

    /**
     * Reference Apple Receipt Validator
     *  - Parameter service: Either .production or .sandbox
     *  - Parameter sharedSecret: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
     */
    public init(service: VerifyReceiptURLType = .production, sharedSecret: String = "", excludeOldTransactions: Bool = false) {
		self.service = service
        self.sharedSecret = sharedSecret
        self.excludeOldTransactions = excludeOldTransactions
	}

	public func validate(receiptData: Data, completion: @escaping (VerifyReceiptResult) -> Void) {

		let storeURL = URL(string: service.rawValue)! // safe (until no more)
		let storeRequest = NSMutableURLRequest(url: storeURL)
		storeRequest.httpMethod = "POST"

        let receipt = receiptData.base64EncodedString(options: [])
		let requestContents: NSMutableDictionary = [ "receipt-data": receipt ]
        requestContents.setValue(self.sharedSecret, forKey: "password")
        requestContents.setValue(self.excludeOldTransactions, forKey: "exclude-old-transactions")

		// Encore request body
		do {
			storeRequest.httpBody = try JSONSerialization.data(withJSONObject: requestContents, options: [])
		} catch let e {
			completion(.error(error: .requestBodyEncodeError(error: e)))
			return
		}

		// Remote task
		let task = URLSession.shared.dataTask(with: storeRequest as URLRequest) { data, _, error -> Void in

			// there is an error
			if let networkError = error {
				completion(.error(error: .networkError(error: networkError)))
				return
			}

			// there is no data
			guard let safeData = data else {
				completion(.error(error: .noRemoteData))
				return
			}

			// cannot decode data
			guard let receiptInfo = try? JSONSerialization.jsonObject(with: safeData, options: .mutableLeaves) as? ReceiptInfo ?? [:] else {
				let jsonStr = String(data: safeData, encoding: String.Encoding.utf8)
				completion(.error(error: .jsonDecodeError(string: jsonStr)))
				return
			}

			// get status from info
			if let status = receiptInfo["status"] as? Int {
				/*
				* http://stackoverflow.com/questions/16187231/how-do-i-know-if-an-in-app-purchase-receipt-comes-from-the-sandbox
				* How do I verify my receipt (iOS)?
				* Always verify your receipt first with the production URL; proceed to verify
				* with the sandbox URL if you receive a 21007 status code. Following this
				* approach ensures that you do not have to switch between URLs while your
				* application is being tested or reviewed in the sandbox or is live in the
				* App Store.

				* Note: The 21007 status code indicates that this receipt is a sandbox receipt,
				* but it was sent to the production service for verification.
				*/
				let receiptStatus = ReceiptStatus(rawValue: status) ?? ReceiptStatus.unknown
				if case .testReceipt = receiptStatus {
                    self.service = .sandbox
                    self.validate(receiptData: receiptData, completion: completion)
				} else {
					if receiptStatus.isValid {
						completion(.success(receipt: receiptInfo))
					} else {
						completion(.error(error: .receiptInvalid(receipt: receiptInfo, status: receiptStatus)))
					}
				}
			} else {
				completion(.error(error: .receiptInvalid(receipt: receiptInfo, status: ReceiptStatus.none)))
			}
		}
		task.resume()
	}
}
