//
//  MyFatoorahModule.swift
//
//  Created by Muhammad Bassiouny on 1/25/23.
//

import Foundation
import MFSDK

@objc(MFModule)
class MFModule: RCTEventEmitter {
    let cardPaymentView = MFCardView.cardView.cardPaymentView
    let applePayButton = MFApplePayButtonView.applePay.applePayButton
    static let applePay = MFApplePay()
    
    private override init() {
        super.init()
        MFSettings.shared.delegate = self
        MFModule.applePay.didLoad = { [weak self] in
            self?.sendEvent(withName: MFHelper.applePayLoaded, body: true)
        }
    }

    override func invalidate() {
        MFModule.applePay.didLoad = nil
        super.invalidate()
    }
    
    //MARK: - Setup
    @objc
    func `init`(_ apiKey: NSString,
                country: NSString,
                environment: NSString) {
        let country = toMFCountry(code: country as String)
        if let env = MFEnvironment(rawValue: environment.lowercased as String) {
            MFSettings.shared.configure(token: apiKey as String, country: country, environment: env)
        }
    }
    
    @objc
    func setUpActionBar(
        _ toolBarTitle: NSString,
        toolBarTitleColor: NSNumber,
        toolBarBackgroundColor: NSNumber,
        isShowToolBar: Bool
    ) {
        let titleColor = MFHelper.getColor(number: toolBarTitleColor)
        let backgroundColor = MFHelper.getColor(number: toolBarBackgroundColor)
        let them = MFTheme(
            navigationTintColor: titleColor,
            navigationBarTintColor: backgroundColor,
            navigationTitle:toolBarTitle as String,
            cancelButtonTitle: "cancel",
            showNavigationBar: isShowToolBar
        )
        MFSettings.shared.setTheme(theme: them)
    }
    
    //MARK: - Payment
    @objc
    func InitiatePayment(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFInitiatePaymentContainer.self, from: data).MFInitiatePayRequest {
            MFPaymentRequest.shared.initiatePayment(request: request, apiLanguage: language) { response in
                switch response {
                case .success(let initiatePaymentResponse):
                    resolve(initiatePaymentResponse.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func SendPayment(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFSendPaymentContainer.self, from: data).MFSendPayRequest {
            MFPaymentRequest.shared.sendPayment(request: request, apiLanguage: language) { response in
                switch response {
                case .success(let sendPaymentResponse):
                    resolve(sendPaymentResponse.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func ExecutePayment(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest {
            MFPaymentRequest.shared.executePayment(request: request, apiLanguage: language) { (response, invoiceId) in
                switch response {
                case .success(let executePaymentResponse):
                    resolve(executePaymentResponse.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func ExecuteDirectPayment(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFDirectPaymentContainer.self, from: data),
           let executePayReq = request.executePaymentRequest?.MFExecPayRequest {
            var card: MFCardInfo
            if let cardInfo = request.card {
                card = MFCardInfo(cardNumber: cardInfo.number, cardExpiryMonth: cardInfo.expiryMonth, cardExpiryYear: cardInfo.expiryYear, cardHolderName: cardInfo.cardHolderName, cardSecurityCode: cardInfo.securityCode, saveToken: request.saveToken ?? false)
                card.bypass = request.bypass3DS ?? false
            } else if let token =  request.token {
                card = MFCardInfo(cardToken: token)
                card.bypass = request.bypass3DS ?? false
            } else {
                reject("Error in Parsing Request", "Error in Parsing Request", nil)
                return
            }
            
            MFPaymentRequest.shared.executeDirectPayment(request: executePayReq, cardInfo: card, apiLanguage: .english) { (response, invoiceId) in
                switch response {
                case .success(let directPaymentResponse):
                    resolve(directPaymentResponse.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func GetPaymentStatus(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFGetPaymentStatusContainer.self, from: data).MFGetPaymentRequest {
            MFPaymentRequest.shared.getPaymentStatus(paymentStatus: request, apiLanguage: language) { response in
                switch response {
                case .success(let paymentStatusResponse):
                    resolve(paymentStatusResponse.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func cancelToken(
        _ token: NSString,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        MFPaymentRequest.shared.cancelToken(token: token as String, apiLanguage: language) { result in
            switch result {
            case .success(let tokenResult):
                resolve(tokenResult)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    @objc
    func cancelRecurringPayment(
        _ recurringId: NSString,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        MFPaymentRequest.shared.cancelRecurringPayment(recurringId: recurringId as String, apiLanguage: language) { result in
            switch result {
            case .success(let recurringResult):
                resolve(recurringResult)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    @objc
    func InitiateSession(_ request: NSDictionary,
                         resolve: @escaping RCTPromiseResolveBlock,
                         reject: @escaping RCTPromiseRejectBlock) {
        let language: MFAPILanguage = .english
        
        let data = try? JSONSerialization.data(withJSONObject: request, options: [])
        let request = try? JSONDecoder().decode(MFInitiateSessionContainer.self, from: data ?? Data()).MFInitiateSessionReq
        MFPaymentRequest.shared.initiateSession(request: request, apiLanguage: language) { response in
            switch response {
            case .success(let session):
                resolve(session.toJSONString)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    //MARK: - Card View
    @objc
    func LoadCardView(_ request: NSDictionary,
                      resolve: @escaping RCTPromiseResolveBlock,
                      reject: @escaping RCTPromiseRejectBlock) {
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFInitiateSessionResponse.self, from: data) {
            DispatchQueue.main.async {
                self.cardPaymentView.load(initiateSession: request) { [weak self] bin in
                    self?.sendEvent(withName: MFHelper.onCardBinChangedEventName, body: bin)
                }
            }
            resolve("CardView Loaded")
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func Validate(_ currency: NSString?,
                  resolve: @escaping RCTPromiseResolveBlock,
                  reject: @escaping RCTPromiseRejectBlock) {
        var mfCurrency: MFCurrencyISO?
        if let currency = currency {
            mfCurrency = MFCurrencyISO(rawValue: currency as String)
        }
        cardPaymentView.submit(mfCurrency) { result in
            switch result {
            case .success(let result):
                resolve(result.cardBrand)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    @objc
    func Pay(
        _ request: NSDictionary,
        language: NSString,
        currency: NSString?,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        var mfCurrency: MFCurrencyISO?
        if let currency = currency {
            mfCurrency = MFCurrencyISO(rawValue: currency as String)
        }
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest {
            cardPaymentView.pay(request, language,mfCurrency) { response, invoiceId in
                switch response {
                case .success(let paymentStatus):
                    resolve(paymentStatus.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func ExecutePaymentWithSavedToken(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFTokenPaymentContainer.self, from: data),
           let executepaymentRequest = request.executePaymentRequest?.MFExecPayRequest,
           let token = request.token {
            let updateSessionRequest = MFUpdateSessionRequest(
                sessionId: executepaymentRequest.sessionId,
                tokenType: .mfToken,
                token: token,
                securityCode: request.securityCode
            )
            MFPaymentRequest.shared.executeTokenPayment(
                updateSessionRequest: updateSessionRequest,
                paymentRequest: executepaymentRequest,
                apiLanguage: language) { response, invoiceId in
                    switch response {
                    case .success(let paymentStatus):
                        resolve(paymentStatus.toJSONString)
                    case .failure(let error):
                        reject(error.statusCode, error.errorDescription, error)
                    }
                }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    //MARK: - Apple Pay Embedded
    @objc
    func ApplePayPayment(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest {
            
            MFPaymentRequest.shared.initiateSession(apiLanguage: language) { [weak self] response in
                switch response {
                case .success(let session):
                    self?.applePayButton.load(session, request, language, completion: { response, invoiceId in
                        switch response {
                        case .success(let executePaymentResponse):
                            resolve(executePaymentResponse.toJSONString)
                        case .failure(let error):
                            reject(error.statusCode, error.errorDescription, error)
                        }
                    })
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @available(*, deprecated, message: "ApplePayDisplay is deprecated. Use the DisplayApplePayButt instead.")
    @objc
    func ApplePayDisplay(
        _ request: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest {
            MFPaymentRequest.shared.initiateSession(apiLanguage: language) { [weak self] response in
                switch response {
                case .success(let session):
                    self?.applePayButton.display(session, request, language) { response in
                        switch response {
                        case .success(let cardBrand):
                            resolve(cardBrand.cardBrand)
                        case .failure(let error):
                            reject(error.statusCode, error.errorDescription, error)
                        }
                    }
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func DisplayApplePayButton(
        _ initSessionRequest: NSDictionary,
        executePaymentRequest: NSDictionary,
        language: NSString,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let sessionData = try? JSONSerialization.data(withJSONObject: initSessionRequest, options: []),
           let session = try? JSONDecoder().decode(MFInitiateSessionResponse.self, from: sessionData),
           let executePaymentData = try? JSONSerialization.data(withJSONObject: executePaymentRequest, options: []),
           let executePaymentRequest = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: executePaymentData).MFExecPayRequest {
            applePayButton.display(session, executePaymentRequest, language) { response in
                switch response {
                case .success(let response):
                    resolve(response.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func ApplePayExecutePayment(_ request: NSDictionary?,
                                resolve: @escaping RCTPromiseResolveBlock,
                                reject: @escaping RCTPromiseRejectBlock) {
        let executePaymentRequest: MFExecutePaymentRequest?
        
        if request == nil {
            executePaymentRequest = nil
        } else if let request = request, let data = try? JSONSerialization.data(withJSONObject: request, options: []),
                  let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest {
            executePaymentRequest = request
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
            return
        }
        
        applePayButton.executePayment(request: executePaymentRequest) { response, _  in
            switch response {
            case .success(let executePaymentResponse):
                resolve(executePaymentResponse.toJSONString)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    @objc
    func LoadApplePay(
        _ initSessionRequest: NSDictionary,
        executePaymentRequest: NSDictionary,
        language: NSString,
        merchantName: NSString?,
        resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        let language = MFAPILanguage(rawValue: language as String) ?? .english
        if let name = merchantName {
            MFModule.applePay.merchantName = String(name)
        }
        
        if let sessionData = try? JSONSerialization.data(withJSONObject: initSessionRequest, options: []),
           let session = try? JSONDecoder().decode(MFInitiateSessionResponse.self, from: sessionData),
           let executePaymentData = try? JSONSerialization.data(withJSONObject: executePaymentRequest, options: []),
           let executePaymentRequest = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: executePaymentData).MFExecPayRequest {
            MFModule.applePay.setupApplePay(session, executePaymentRequest, language) { result in
                switch result {
                case .success(let loaded):
                    resolve(loaded)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func OpenApplePaymentSheet(_ resolve: @escaping RCTPromiseResolveBlock,
                               reject: @escaping RCTPromiseRejectBlock) {
        MFModule.applePay.openPaymentSheet { response in
            switch response {
            case .success(let successResponse):
                resolve(successResponse.toJSONString)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    @objc
    func CompleteApplePayment(_ request: NSDictionary?,
                              resolve: @escaping RCTPromiseResolveBlock,
                              reject: @escaping RCTPromiseRejectBlock) {
        let executePaymentRequest: MFExecutePaymentRequest?
        if request == nil {
            executePaymentRequest = nil
        } else if let request = request, let data = try? JSONSerialization.data(withJSONObject: request, options: []),
                  let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest {
            executePaymentRequest = request
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
            return
        }
        MFModule.applePay.executePayment(request: executePaymentRequest) { response, _  in
            switch response {
            case .success(let executePaymentResponse):
                resolve(executePaymentResponse.toJSONString)
            case .failure(let error):
                reject(error.statusCode, error.errorDescription, error)
            }
        }
    }
    
    //MARK: - Apple Pay Native
    
    @objc
    func getApplePayToken(_ applePayRequest: NSDictionary,
                          resolve: @escaping RCTPromiseResolveBlock,
                          reject: @escaping RCTPromiseRejectBlock) {
        if let data = try? JSONSerialization.data(withJSONObject: applePayRequest, options: []),
           let request = try? JSONDecoder().decode(MFApplePayRequestContainer.self, from: data).ApplePayRequest {
            MFPaymentRequest.shared.getApplePayToken(request: request) { response in
                switch response {
                case .success(let response):
                    resolve(response.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
    
    @objc
    func applePayNativePayment(_ applePayRequest: NSDictionary,
                               request: NSDictionary,
                               resolve: @escaping RCTPromiseResolveBlock,
                               reject: @escaping RCTPromiseRejectBlock) {
        if let data = try? JSONSerialization.data(withJSONObject: request, options: []),
           let request = try? JSONDecoder().decode(MFExecPaymentContainer.self, from: data).MFExecPayRequest,
           let appleData = try? JSONSerialization.data(withJSONObject: applePayRequest, options: []),
           let appleRequest = try? JSONDecoder().decode(MFApplePayRequestContainer.self, from: appleData).ApplePayRequest {
            MFPaymentRequest.shared.payByApplePay(paymentRequest: request, applePayRequest: appleRequest, .english) { response, _  in
                switch response {
                case .success(let executePaymentResponse):
                    resolve(executePaymentResponse.toJSONString)
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, error)
                }
            }
        } else {
            reject("Error in Parsing Request", "Error in Parsing Request", nil)
        }
    }
}

//MARK: - Helpers
extension MFModule {
    @objc
    override static func requiresMainQueueSetup() -> Bool {
        return true
    }
    
    override func supportedEvents() -> [String]! {
        return [MFHelper.onCardBinChangedEventName, MFHelper.invoiceIdEventName, MFHelper.applePayLoaded]
    }
    
    private func toMFCountry(code: String?) -> MFCountry {
        switch code {
        case "KWT":
            return .kuwait
        case "SAU":
            return .saudiArabia
        case "BHR":
            return .bahrain
        case "ARE":
            return .unitedArabEmirates
        case "QAT":
            return .qatar
        case "OMN":
            return .oman
        case "JOD":
            return .jordan
        case "EGY":
            return .egypt
        default:
            return .kuwait
        }
    }
}

extension MFModule: MFPaymentDelegate {
    func didInvoiceCreated(invoiceId: String) {
        sendEvent(withName: MFHelper.invoiceIdEventName, body: invoiceId)
    }
}

