import Foundation
import Capacitor
import PhonePePayment

@objc(PhonePePaymentSDK)
public class PhonePePaymentSDK: CAPPlugin {
    
    private var ppPayment: PPPayment?
    private var controller : UIViewController? {
        UIApplication.shared.windows.first?.rootViewController
    }
    private var call: CAPPluginCall?
    
    public override func load() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(handleOpenURL(_:)),
                                               name: Notification.Name.capacitorOpenURL,
                                               object: nil)
    }
    
    @objc func `init`(_ call: CAPPluginCall) {
        self.call = call
        let arguments = call.options
        guard let env = arguments?[Constants.environment] as? String, env.isNotEmpty() else {
            call.reject(PPError.env.getMessage())
            return
        }
        
        guard let environment = IonicEnvironment(rawValue: env) else {
            call.reject(PPError.ionicEnv.getMessage())
            return
        }
        
        guard let merchantId = arguments?[Constants.merchantId] as? String, merchantId.isNotEmpty() else {
            call.reject(PPError.merchantId.getMessage())
            return
        }
        
        guard let flowId = arguments?[Constants.flowId] as? String, flowId.isNotEmpty() else {
            call.reject(PPError.flowId.getMessage())
            return
        }
        
        
        let enableLogs = arguments?[Constants.enableLogs] as? Bool ?? false
        
        ppPayment = PPPayment(environment: environment[], flowId: flowId, merchantId: merchantId, enableLogging: enableLogs)
        ppPayment?.setAdditionalInfo(sdkType: .ionic)
        call.resolve([Constants.status: true])
    }
    
    @objc func startTransaction(_ call: CAPPluginCall) {
        self.call = call
        DispatchQueue.main.async {
            guard let viewController = self.controller else {
                call.reject(PPError.controller.getMessage())
                return
            }
            
            guard let ppPayment = self.ppPayment else {
                call.reject(PPError.initSDK.getMessage())
                return
            }
            
            self.makeTransactionRequest(call.options) { request in
                guard let request = request else {
                    return
                }
                
                ppPayment.startTransaction(request: request,
                                           on: viewController) { [weak self] _, state in
                    self?.handleResult(state: state, call: call)
                }
            }
        }
    }
    
    @objc func getUpiAppsForIos(_ call: CAPPluginCall) {
        call.resolve([Constants.status: PPPayment.getUPIAppsInstalled()])
    }
    
    
    // MARK: Private Methods
    private func makeTransactionRequest(_ arguments: [AnyHashable: Any], completion:  @escaping ((B2BPGTransactionRequest?) -> Void)) {
        
        guard let paymentRequest = makePaymentRequest(args: arguments) else {
            return
        }
        
        guard let paymentType = paymentRequest.paymentMode[Constants.type] as? String else {
            call?.reject(PPError.paymentType.getMessage())
            return
        }
        
        let bankId = paymentRequest.paymentMode[Constants.bankId] as? String ?? ""
        let vpa = paymentRequest.paymentMode[Constants.vpa] as? String ?? ""
        let targetApp = paymentRequest.paymentMode[Constants.targetApp] as? String ?? ""
        
        let mode: PaymentMode
        switch paymentType {
        case Constants.upiIntent:
            mode = .upiIntent(request: UPIIntentPaymentMode(targetApp: targetApp))
        case Constants.netBanking:
            mode = .netBanking(request: NetbankingPaymentMode(bankId: bankId))
        case Constants.upiCollect:
            mode = .upiCollect(request: UpiCollectPaymentMode(message: Constants.collectDetails, details: VPACollectDetails(vpa: vpa)))
        case Constants.ppeIntent:
            mode = .ppeIntent(request: PPEIntentPaymentMode())
        case Constants.payPage:
            mode = .paypage(request: PayPagePaymentMode())
        default:
            call?.reject(PPError.inValidType.getMessage())
            return
        }
        
        let transactionRequest = B2BPGTransactionRequest(merchantId: paymentRequest.merchantId,
                                                         orderId: paymentRequest.orderId,
                                                         token: paymentRequest.token,
                                                         appSchema: paymentRequest.appSchema,
                                                         paymentMode: mode)
        
        completion(transactionRequest)
    }
    
    private func makePaymentRequest(args: [AnyHashable: Any]?) -> PaymentRequest? {
        
        guard let jsonString = args?[Constants.request] as? String, let requestJson = jsonString.toJson()  else {
            call?.reject(PPError.json.getMessage())
            return nil
        }
        
        guard let orderId = requestJson[Constants.orderId] as? String, orderId.isNotEmpty() else {
            call?.reject(PPError.orderId.getMessage())
            return nil
        }
        
        guard let merchantId = requestJson[Constants.merchantId] as? String, merchantId.isNotEmpty() else {
            call?.reject(PPError.merchantId.getMessage())
            return nil
        }
        
        guard let token = requestJson[Constants.token] as? String, token.isNotEmpty() else {
            call?.reject(PPError.token.getMessage())
            return nil
        }
        
        guard let appSchema = args?[Constants.appSchema] as? String else {
            call?.reject(PPError.appSchema.getMessage())
            return nil
        }
        
        // Payment Mode Details
        guard let paymentMode = requestJson[Constants.paymentMode] as? [String: Any] else {
            call?.reject(PPError.paymentMode.getMessage())
            return nil
        }
        
        return PaymentRequest(merchantId: merchantId,
                              orderId: orderId,
                              token: token,
                              appSchema: appSchema,
                              paymentMode: paymentMode)
    }
    
    private func handleResult(state: PPResultState, call: CAPPluginCall) {
        var dict: [String: String] = [:]
        switch state {
        case .success:
            dict = [Constants.status: Constants.success]
        case .failure(let error):
            dict = [Constants.status: Constants.failure, Constants.error: error.localizedDescription]
        case .interrupted(let error):
            dict = [Constants.status: Constants.interrupted, Constants.error: error.localizedDescription]
        @unknown default:
            dict = [Constants.status: Constants.unknown]
        }
        
        call.resolve(dict)
    }
    
    @objc private func handleOpenURL(_ notification: Notification) {
        if let object = notification.object as? [AnyHashable: Any], let url = object[Constants.url] as? URL {
            let _ = PPPayment.checkDeeplink(url)
        }
    }
}


// MARK: Ionic Environment Enum
enum IonicEnvironment: String {
    case SANDBOX
    case PRODUCTION
    
    subscript() -> Environment {
        switch self {
        case .SANDBOX:
            return .sandbox
        case .PRODUCTION:
            return .production
        }
    }
}
