//
// Copyright (c) 2022 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Adyen
import Foundation
import PassKit
import React

@objc(AdyenApplePay)
internal class ApplePayModule: BaseModuleSender {

    private let paymentAuthorizationService: PKPaymentAuthorizationService

    internal var shippingContactHandler: ((PKPaymentRequestShippingContactUpdate) -> Void)?
    internal var shippingMethodHandler: ((PKPaymentRequestShippingMethodUpdate) -> Void)?
    internal var authorizationHandler: ((PKPaymentAuthorizationResult) -> Void)?
    private var _couponCodeHandler: Any?
    @available(iOS 15.0, *)
    internal var couponCodeHandler: ((PKPaymentRequestCouponCodeUpdate) -> Void)? {
        get { _couponCodeHandler as? (PKPaymentRequestCouponCodeUpdate) -> Void }
        set { _couponCodeHandler = newValue }
    }

    internal var currentApplePayPayment: ApplePayPayment?
    internal var currentShippingMethods: [PKShippingMethod] = []

    override init() {
        self.paymentAuthorizationService = PKPaymentAuthorizationServiceAdapter()
        super.init()
    }

    init(pkPaymentAuthorizationService: PKPaymentAuthorizationService = PKPaymentAuthorizationServiceAdapter()) {
        self.paymentAuthorizationService = pkPaymentAuthorizationService
        super.init()
    }

    override func supportedEvents() -> [String]! {
        super.supportedEvents() + EventName.applePayEvents.map(\.rawValue)
    }

    @objc
    func open(_ paymentMethodsDict: NSDictionary, configuration: NSDictionary) {
        let parser = RootConfigurationParser(configuration: configuration)
        let applePayParser = ApplepayConfigurationParser(configuration: configuration)
        let applePayComponent: ApplePayComponent
        do {
            let paymentMethod = try parsePaymentMethod(from: paymentMethodsDict, for: ApplePayPaymentMethod.self)
            let context = try parser.fetchContext(session: BaseModule.session)
            guard let payment = context.payment else { throw ModuleException.noPayment }
            let applepayConfig = try applePayParser.buildConfiguration(payment: payment)
            applePayComponent = try Adyen.ApplePayComponent(paymentMethod: paymentMethod,
                                                            context: context,
                                                            configuration: applepayConfig)
        } catch {
            return sendError(error: error)
        }

        currentShippingMethods = applePayParser.shippingMethods ?? []
        currentComponent = applePayComponent
        applePayComponent.delegate = BaseModule.session ?? self
        applePayComponent.applePayDelegate = self
        applePayComponent.authorizationDelegate = self
        present(component: applePayComponent)
    }

    override func cleanUp() {
        shippingContactHandler = nil
        shippingMethodHandler = nil
        if #available(iOS 15.0, *) { couponCodeHandler = nil }
        authorizationHandler = nil
        currentApplePayPayment = nil
        currentShippingMethods = []
        super.cleanUp()
    }

    @objc
    func isAvailable(_ paymentMethodDict: NSDictionary,
                     configuration: NSDictionary,
                     resolver: @escaping RCTPromiseResolveBlock,
                     rejecter _: @escaping RCTPromiseRejectBlock) {
        let parser = RootConfigurationParser(configuration: configuration)
        let applePayParser = ApplepayConfigurationParser(configuration: configuration)

        let paymentRequest: PKPaymentRequest
        guard
            let context = try? parser.fetchContext(session: BaseModule.session),
            let payment = context.payment else {
            return resolver(false)
        }

        do {
            paymentRequest = try applePayParser.buildPaymentRequest(payment: payment)
        } catch {
            return resolver(false)
        }

        guard let paymentMethod: ApplePayPaymentMethod = try? paymentMethodDict.decode() else {
            return resolver(false)
        }

        let supportedNetworks = paymentMethod.supportedNetworks
        guard applePayParser.allowOnboarding || paymentAuthorizationService.canMakePayments(usingNetworks: supportedNetworks) else {
            return resolver(false)
        }

        paymentRequest.supportedNetworks = supportedNetworks
        guard let _ = paymentAuthorizationService.getAuthorizationViewController(paymentRequest: paymentRequest) else {
            return resolver(false)
        }

        return resolver(true)
    }

}

protocol PKPaymentAuthorizationService {
    func canMakePayments(usingNetworks: [PKPaymentNetwork]) -> Bool
    func getAuthorizationViewController(paymentRequest: PKPaymentRequest) -> PKPaymentAuthorizationViewController?
}

struct PKPaymentAuthorizationServiceAdapter: PKPaymentAuthorizationService {
    func canMakePayments(usingNetworks: [PKPaymentNetwork]) -> Bool {
        PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: usingNetworks)
    }

    func getAuthorizationViewController(paymentRequest: PKPaymentRequest) -> PKPaymentAuthorizationViewController? {
        PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)
    }
}
