//
//  StripeAPI.swift
//  StripeiOS
//
//  Created by Yuki Tokuhiro on 9/22/20.
//  Copyright © 2020 Stripe, Inc. All rights reserved.
//

import Foundation
import PassKit

/// A top-level class that imports the rest of the Stripe SDK.
public class StripeAPI: NSObject {
    /// Set this to your Stripe publishable API key, obtained from https://dashboard.stripe.com/apikeys.
    /// Set this as early as possible in your application's lifecycle, preferably in your AppDelegate or SceneDelegate.
    /// New instances of STPAPIClient will be initialized with this value.
    /// @warning Make sure not to ship your test API keys to the App Store! This will log a warning if you use your test key in a release build.
    @objc public static var defaultPublishableKey: String?

    /// Set this to your Stripe publishable API key, obtained from https://dashboard.stripe.com/apikeys.
    /// Set this as early as possible in your application's lifecycle, preferably in your AppDelegate or SceneDelegate.
    /// New instances of STPAPIClient will be initialized with this value.
    /// @warning Make sure not to ship your test API keys to the App Store! This will log a warning if you use your test key in a release build.
    public func setDefaultPublishableKey(_ publishableKey: String) {
        StripeAPI.defaultPublishableKey = publishableKey
    }

    /// A Boolean value that determines whether additional device data is sent to Stripe for fraud prevention.
    /// If YES, additional device signals will be sent to Stripe.
    /// For more details on the information we collect, visit https://stripe.com/docs/disputes/prevention/advanced-fraud-detection
    /// Disabling this setting will reduce Stripe's ability to protect your business from fraudulent payments.
    /// The default value is YES.
    @objc public static var advancedFraudSignalsEnabled: Bool = true

    // MARK: - Apple Pay

    /// Japanese users can enable JCB for Apple Pay by setting this to `YES`, after they have been approved by JCB.
    /// The default value is NO.
    /// @note JCB is only supported on iOS 10.1+
    @objc public class var jcbPaymentNetworkSupported: Bool {
        get {
            if #available(iOS 10.1, *) {
                return self.additionalEnabledApplePayNetworks.contains(.JCB)
            } else {
                return false
            }
        }
        set(JCBPaymentNetworkSupported) {
            if #available(iOS 10.1, *) {
                if JCBPaymentNetworkSupported
                    && !self.additionalEnabledApplePayNetworks.contains(.JCB)
                {
                    self.additionalEnabledApplePayNetworks =
                        self.additionalEnabledApplePayNetworks + [PKPaymentNetwork.JCB]
                } else if !JCBPaymentNetworkSupported {
                    var updatedNetworks = self.additionalEnabledApplePayNetworks
                    updatedNetworks.removeAll {
                        $0 as AnyObject === PKPaymentNetwork.JCB as AnyObject
                    }
                    self.additionalEnabledApplePayNetworks = updatedNetworks
                }
            }
        }
    }
    /// The SDK accepts Amex, Mastercard, Visa, and Discover for Apple Pay.
    /// Set this property to enable other card networks in addition to these.
    /// For example, `additionalEnabledApplePayNetworks = [.JCB]` enables JCB (note this requires onboarding from JCB and Stripe).
    @objc public static var additionalEnabledApplePayNetworks: [PKPaymentNetwork] = []

    /// Whether or not this device is capable of using Apple Pay. This checks both
    /// whether the device supports Apple Pay, as well as whether or not they have
    /// stored Apple Pay cards on their device.
    /// - Parameter paymentRequest: The return value of this method depends on the
    /// `supportedNetworks` property of this payment request, which by default should be
    /// `[.amex, .masterCard, .visa, .discover]`.
    /// - Returns: whether or not the user is currently able to pay with Apple Pay.
    @objc
    public class func canSubmitPaymentRequest(_ paymentRequest: PKPaymentRequest) -> Bool {
        if !self.deviceSupportsApplePay() {
            return false
        }
        if paymentRequest.merchantIdentifier.isEmpty {
            return false
        }
        // "In versions of iOS prior to version 12.0 and watchOS prior to version 5.0, the amount of the grand total must be greater than zero."
        if #available(iOS 12, *) {
            return paymentRequest.paymentSummaryItems.last?.amount.floatValue ?? 0.0 >= 0
        } else {
            return paymentRequest.paymentSummaryItems.last?.amount.floatValue ?? 0.0 > 0
        }
    }

    @objc class func supportedPKPaymentNetworks() -> [PKPaymentNetwork] {
        var additionalOSSupportedNetworks: [PKPaymentNetwork] = []
        if #available(iOS 12.0, *) {
            additionalOSSupportedNetworks.append(.maestro)
        }
        return [
            .amex,
            .masterCard,
            .visa,
            .discover,
        ] + additionalEnabledApplePayNetworks + additionalOSSupportedNetworks
    }

    /// Whether or not this can make Apple Pay payments via a card network supported
    /// by Stripe.
    /// The Stripe supported Apple Pay card networks are:
    /// American Express, Visa, Mastercard, Discover, Maestro.
    /// Japanese users can enable JCB by setting `JCBPaymentNetworkSupported` to YES,
    /// after they have been approved by JCB.
    /// - Returns: YES if the device is currently able to make Apple Pay payments via one
    /// of the supported networks. NO if the user does not have a saved card of a
    /// supported type, or other restrictions prevent payment (such as parental controls).
    @objc dynamic public class func deviceSupportsApplePay() -> Bool {
        return PKPaymentAuthorizationController.canMakePayments(
            usingNetworks: self.supportedPKPaymentNetworks())
    }

    /// A convenience method to build a `PKPaymentRequest` with sane default values.
    /// You will still need to configure the `paymentSummaryItems` property to indicate
    /// what the user is purchasing, as well as the optional `requiredShippingAddressFields`,
    /// `requiredBillingAddressFields`, and `shippingMethods` properties to indicate
    /// what contact information your application requires.
    /// Note that this method sets the payment request's countryCode to "US" and its
    /// currencyCode to "USD".
    /// - Parameter merchantIdentifier: Your Apple Merchant ID.
    /// - Returns: a `PKPaymentRequest` with proper default values. Returns nil if running on < iOS8.
    /// @deprecated Use `paymentRequestWithMerchantIdentifier:country:currency:` instead.
    /// Apple Pay is available in many countries and currencies, and you should use
    /// the appropriate values for your business.
    @available(
        *, deprecated,
        message: "Use `paymentRequestWithMerchantIdentifier:country:currency:` instead."
    )
    @objc
    public class func paymentRequest(withMerchantIdentifier merchantIdentifier: String)
        -> PKPaymentRequest
    {
        return self.paymentRequest(
            withMerchantIdentifier: merchantIdentifier, country: "US", currency: "USD")
    }

    /// A convenience method to build a `PKPaymentRequest` with sane default values.
    /// You will still need to configure the `paymentSummaryItems` property to indicate
    /// what the user is purchasing, as well as the optional `requiredShippingContactFields`,
    /// `requiredBillingContactFields`, and `shippingMethods` properties to indicate
    /// what additional contact information your application requires.
    /// - Parameters:
    ///   - merchantIdentifier: Your Apple Merchant ID.
    ///   - countryCode:        The two-letter code for the country where the payment
    /// will be processed. This should be the country of your Stripe account.
    ///   - currencyCode:       The three-letter code for the currency used by this
    /// payment request. Apple Pay interprets the amounts provided by the summary items
    /// attached to this request as amounts in this currency.
    /// - Returns: a `PKPaymentRequest` with proper default values.
    @objc
    public class func paymentRequest(
        withMerchantIdentifier merchantIdentifier: String,
        country countryCode: String,
        currency currencyCode: String
    ) -> PKPaymentRequest {
        let paymentRequest = PKPaymentRequest()
        paymentRequest.merchantIdentifier = merchantIdentifier
        paymentRequest.supportedNetworks = self.supportedPKPaymentNetworks()
        paymentRequest.merchantCapabilities = .capability3DS
        paymentRequest.countryCode = countryCode.uppercased()
        paymentRequest.currencyCode = currencyCode.uppercased()
        paymentRequest.requiredBillingContactFields = Set([.postalAddress])
        return paymentRequest
    }

    // MARK: - URL callbacks

    /// Call this method in your app delegate whenever you receive an URL in your
    /// app delegate for a Stripe callback.
    /// For convenience, you can pass all URL's you receive in your app delegate
    /// to this method first, and check the return value
    /// to easily determine whether it is a callback URL that Stripe will handle
    /// or if your app should process it normally.
    /// If you are using a universal link URL, you will receive the callback in `application:continueUserActivity:restorationHandler:`
    /// To learn more about universal links, see https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html
    /// If you are using a native scheme URL, you will receive the callback in `application:openURL:options:`
    /// To learn more about native url schemes, see https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html#//apple_ref/doc/uid/TP40007072-CH6-SW10
    /// - Parameter url: The URL that you received in your app delegate
    /// - Returns: YES if the URL is expected and will be handled by Stripe. NO otherwise.
    @objc(handleStripeURLCallbackWithURL:) @discardableResult public static func handleURLCallback(
        with url: URL
    ) -> Bool {
        return STPURLCallbackHandler.shared().handleURLCallback(url)
    }
}

// MARK: Deprecated top-level Stripe functions.
// These are included so Xcode can offer guidance on how to replace top-level Stripe usage.

/// :nodoc:
@available(
    *, deprecated,
    message:
        "Use StripeAPI.defaultPublishableKey instead. (StripeAPI.defaultPublishableKey = \"pk_12345_xyzabc\")"
)
public func setDefaultPublishableKey(_ publishableKey: String) {
    StripeAPI.defaultPublishableKey = publishableKey
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.advancedFraudSignalsEnabled instead."
)
public var advancedFraudSignalsEnabled: Bool {
    get {
        StripeAPI.advancedFraudSignalsEnabled
    }
    set {
        StripeAPI.advancedFraudSignalsEnabled = newValue
    }
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.jcbPaymentNetworkSupported instead."
)
public var jcbPaymentNetworkSupported: Bool {
    get {
        StripeAPI.jcbPaymentNetworkSupported
    }
    set {
        StripeAPI.jcbPaymentNetworkSupported = newValue
    }
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.additionalEnabledApplePayNetworks instead."
)
public var additionalEnabledApplePayNetworks: [PKPaymentNetwork] {
    get {
        StripeAPI.additionalEnabledApplePayNetworks
    }
    set {
        StripeAPI.additionalEnabledApplePayNetworks = newValue
    }
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.canSubmitPaymentRequest(_:) instead."
)
public func canSubmitPaymentRequest(_ paymentRequest: PKPaymentRequest) -> Bool {
    return StripeAPI.canSubmitPaymentRequest(paymentRequest)
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.deviceSupportsApplePay() instead."
)
public func deviceSupportsApplePay() -> Bool {
    return StripeAPI.deviceSupportsApplePay()
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.paymentRequest(withMerchantIdentifier:country:currency:) instead."
)
public func paymentRequest(
    withMerchantIdentifier merchantIdentifier: String,
    country countryCode: String,
    currency currencyCode: String
) -> PKPaymentRequest {
    return StripeAPI.paymentRequest(
        withMerchantIdentifier: merchantIdentifier, country: countryCode, currency: currencyCode)
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.paymentRequest(withMerchantIdentifier:country:currency:) instead."
)
func paymentRequest(withMerchantIdentifier merchantIdentifier: String)
    -> PKPaymentRequest
{
    return StripeAPI.paymentRequest(
        withMerchantIdentifier: merchantIdentifier, country: "US", currency: "USD")
}

/// :nodoc:
@available(
    *, deprecated,
    message: "Use StripeAPI.handleURLCallback(with:) instead."
)
public func handleURLCallback(with url: URL) -> Bool {
    return StripeAPI.handleURLCallback(with: url)
}
