import FawryFramework
import UIKit
import Foundation

@objc(RnFawryPaySdk)
class RnFawryPaySdk: RCTEventEmitter {
    
    static let FAWRY_EVENT_EMITTER_NAME = "RnFawryPaySdk"
    static let FAWRY_EVENT_PAYMENT_COMPLETED = "FAWRY_EVENT_PAYMENT_COMPLETED"
    static let FAWRY_EVENT_ON_SUCCESS = "FAWRY_EVENT_ON_SUCCESS"
    static let FAWRY_EVENT_ON_FAIL = "FAWRY_EVENT_ON_FAIL"
    static let FAWRY_EVENT_CARD_MANAGER_FAIL = "FAWRY_EVENT_CARD_MANAGER_FAIL"
    static let FAWRY_EVENT_ADDRESS_MANAGER_FAIL = "FAWRY_EVENT_ADDRESS_MANAGER_FAIL"
    
    
    @objc
    func openCardsManager(
        _ baseUrl: String,
        lang: String,
        merchantInfo: NSDictionary,
        customerInfo: NSDictionary
    ) -> Void {
        DispatchQueue.main.async {
            
            
            let customerInfo = self.getCustomerInfo(customerInfo: customerInfo)
            let merchantInfo = self.getMerchantInfo(merchantInfo: merchantInfo)
            
            let launchModel = FawryLaunchModel(customer: customerInfo,
                                               merchant: merchantInfo,
                                               chargeItems: nil,
                                               shippingAddress: nil)
            
            FrameworkHelper.shared?.showController(
                on: self.getPresentedViewController()!,
                mode: .savedCards,
                launchModel: launchModel,
                appLanguage: self.getSelectedLanguage(lang: lang),
                completionBlock: { (status) in
                    
                }, onPreCompletionHandler: { (error) in
                    
                    print("SDK Launch on pre completion \(error?.message)")
                }, errorBlock: { (error) in
                    
                    //self.sendEvent(withName: RnFawryPaySdk.FAWRY_EVENT_CARD_MANAGER_FAIL, body: error?.toJSON())
                    self.sendEvent(withName: RnFawryPaySdk.FAWRY_EVENT_CARD_MANAGER_FAIL, body: error?.message)
                    
                    
                    //print("Add Card has error \(error?.message)")
                }, onSuccessHandler: { (response) in
                    
                    //print("Add Card Success Handler: \(response)")
                })
        }
        
    }
    
    @objc
    func openAddressManager(
        _ baseUrl: String,
        lang: String,
        merchantInfo: NSDictionary,
        customerInfo: NSDictionary,
        beid: String,
        addressHierarchy: String
    ) -> Void {
        DispatchQueue.main.async {
            
            let addressModeType: AddressMode = (addressHierarchy.lowercased() == "matrix") ? .matrix : .geolocation
            let customerInfo = self.getCustomerInfo(customerInfo: customerInfo)
            let selectedLangAfterParsing = self.getSelectedLanguage(lang: lang)
            
            let merchantInfo = self.getMerchantInfo(merchantInfo: merchantInfo)
            let launchModel = FawryLaunchModel(customer: customerInfo,
                                               merchant: merchantInfo,
                                               chargeItems: nil,
                                               shippingAddress: nil)
            
            FrameworkHelper.shared?.showController(on: self.getPresentedViewController()!,
                                                   mode: .addressManager,
                                                   launchModel: launchModel,
                                                   baseURL: baseUrl,
                                                   appLanguage: selectedLangAfterParsing,
                                                   completionBlock: { (status) in
                
                print("SDK Loaded with status \(status)")
            }, onPreCompletionHandler: { (error) in
                
                print("SDK Launch on pre completion \(error?.message)")
            }, errorBlock: { (error) in
                
                print("Address Manager has error \(error?.message)")
                
                //self.sendEvent(withName: RnFawryPaySdk.FAWRY_EVENT_CARD_MANAGER_FAIL, body: error?.toJSON())
                self.sendEvent(withName: RnFawryPaySdk.FAWRY_EVENT_ADDRESS_MANAGER_FAIL, body: error?.message)
                
                
            }, onSuccessHandler: { (response) in
                
                if let address = response as? AddressList{
                    print(address)
                }
                
            })
        }
    }
    
    @objc
    func startPayment(
        _ baseUrl: String,
        lang: String,
        addressHierarchy: String,
        allow3DPayment: Bool,
        apiPath: String,
        customerInfo: NSDictionary,
        items: [NSDictionary],
        merchantInfo: NSDictionary,
        showLoyaltyContainer: Bool,
        showTipsView: Bool,
        showVoucherContainer: Bool,
        skipReceipt: Bool,
        optionalPaymentParams: NSDictionary
    ) -> Void {
        
        // Determine address mode based on address hierarchy
        let addressModeType: AddressMode = (addressHierarchy.lowercased() == "matrix") ? .matrix : .geolocation
        
        // Parse customer, merchant, and items information
        let customerInfoAfterParse = self.getCustomerInfo(customerInfo: customerInfo)
        let merchantInfoAfterParse = self.getMerchantInfo(merchantInfo: merchantInfo)
        let itemsAfterParse = self.getItems(items: items)
        
        // Extract optional payment parameters
        let beid = optionalPaymentParams["beid"] as? String
        let scheduledTime = optionalPaymentParams["scheduledTime"] as? String
        let shippingAddress = optionalPaymentParams["shippingAddress"] as? NSDictionary
        let branchCode = optionalPaymentParams["branchCode"] as? String
        let branchName = optionalPaymentParams["branchName"] as? String
        let serviceTypeCode = optionalPaymentParams["serviceTypeCode"] as? String
        let tableId = optionalPaymentParams["tableId"] as? Int

        
        merchantInfoAfterParse.branch = branchName
        merchantInfoAfterParse.branchCode = branchCode
        merchantInfoAfterParse.serviceType = serviceTypeCode
        merchantInfoAfterParse.tableId = tableId
        
        
        let id = shippingAddress?["id"] as? Int
        let address = shippingAddress?["address"] as? String
        let addressTypeDict = shippingAddress?["addressType"] as? [String: Any]
        let addressTypeCode = addressTypeDict?["code"] as? String
        let addressTypeId = addressTypeDict?["id"] as? String
        let receiverName = shippingAddress?["receiverName"] as? String
        let receiverMobile = shippingAddress?["receiverMobile"] as? String
        let isDefault = shippingAddress?["isDefault"] as? Bool
        let buildingNumber = shippingAddress?["buildingNumber"] as? String
        let geolocationDict = shippingAddress?["geolocation"] as? [String: Any]
        let areaName = geolocationDict?["areaName"] as? String
        let cityName = geolocationDict?["cityName"] as? String
        let govName = geolocationDict?["govName"] as? String
        let latitude = geolocationDict?["latitude"] as? Double
        let longitude = geolocationDict?["longitude"] as? Double
        let streetName = geolocationDict?["streetName"] as? String
        let selected = shippingAddress?["selected"] as? Bool
        let status = shippingAddress?["status"] as? String
        let floorNumber = shippingAddress?["floorNumber"] as? String
        let apartmentNumber = shippingAddress?["apartmentNumber"] as? String
        let landmark = shippingAddress?["landmark"] as? String


        
        let addressType = addressTypeCode
        
        
        let sendingAddress = AddressList(
            isDefault: isDefault,
            id: id,
            governorate: govName,
            city: cityName,
            area: areaName,
            street: streetName,
            isDefaultAddress: isDefault,
            receiverName: receiverName,
            latitude: latitude,
            longitude: longitude,
            receiverMobile: receiverMobile,
            building: buildingNumber,
            floor: floorNumber,
            apartment: apartmentNumber,
            landmark: landmark,
            addressType: addressType
        )
        
        // Create FawryLaunchModel using parsed data
        let launchModel = FawryLaunchModel(
            customer: customerInfoAfterParse,
            merchant: merchantInfoAfterParse,
            chargeItems: itemsAfterParse,
            shippingAddress: sendingAddress,
            scheduledTime: Date(timeIntervalSince1970: Double(scheduledTime ?? "") ?? 0),
            beId: beid,
            showTipsView: showTipsView,
            showLoyaltyContainer: showLoyaltyContainer,
            showVoucherContainer: showVoucherContainer,
            apiPath: apiPath,
            skipReceipt: skipReceipt,
            addressMode: addressModeType
        )
        
        // Get the current view controller
        let currentViewConroller = self.getPresentedViewController()!
        
        // Parse selected language
        let selectedLangAfterParsing = self.getSelectedLanguage(lang: lang)
        
        // Perform the SDK-related operations asynchronously on the main thread
        DispatchQueue.main.async {
            // Use FrameworkHelper to show the payment controller
            FrameworkHelper.shared?.showController(
                on: currentViewConroller,
                mode: .choosePaymentMethod,
                launchModel: launchModel,
                baseURL: baseUrl,
                appLanguage: selectedLangAfterParsing,
                enable3Ds: allow3DPayment,
                translationDict: nil,
                completionBlock: { (status) in
                    print("Payment Method: SDK Loaded with status \(status)")
                }, onPreCompletionHandler: { (error) in
                    print("Payment Method: SDK Launch on pre completion \(error?.message)")
                }, errorBlock: { (error) in
                    print("Payment Method: has error \(error?.message)")
                    self.sendEvent(withName: RnFawryPaySdk.FAWRY_EVENT_ON_FAIL, body: error?.message)
                }, onSuccessHandler: { (response) in
                    let merchantRefNumber = (response as? PaymentChargeResponse)?.merchantRefNumber ?? ""
                    print("Payment Method: Success Handler: \(merchantRefNumber)")
                    if let response = response as? PaymentChargeResponse{
                        self.sendEvent(withName: RnFawryPaySdk.FAWRY_EVENT_ON_SUCCESS, body: response.toJSON())
                    }
                }
            )
        }
    }
    
    func getCustomerInfo(customerInfo: NSDictionary) -> LaunchCustomerModel {

         print("--- Customer Info Dictionary ---")
    printNSDictionary(customerInfo)
    
        let result = LaunchCustomerModel(
            customerName: customerInfo["customerName"] as? String,
            customerEmail: customerInfo["customerEmail"] as? String,
            customerMobile: customerInfo["customerMobile"] as? String,
            customerProfileId: customerInfo["customerProfileId"] as? String,
            password: "",
            token: customerInfo["customerToken"] as? String,
            customerCif: customerInfo["customerCif"] as? String
        )
        
        let filledObject = RnUtils.fillObject(customerInfo, objectType: LaunchCustomerModel.self)
        return filledObject
    }

    func printNSDictionary(_ dictionary: NSDictionary) {
    for (key, value) in dictionary {
        if let keyString = key as? String {
            print("\(keyString): \(value)")
        } else {
            print("Key is not a string.")
        }
    }
}
    
    func getMerchantInfo(merchantInfo: NSDictionary) -> LaunchMerchantModel {
        let result = LaunchMerchantModel(
            merchantCode: merchantInfo["merchantCode"] as? String,
            submerchantCode: merchantInfo["subMerchantCode"] as? String,
            merchantRefNum: merchantInfo["merchantRefNum"] as? String,
            secureKey: merchantInfo["merchantSecretCode"] as? String
        )
        return result
    }
    
    func getItems(items: [NSDictionary]) -> [ChargeItemsParamsModel] {
        var result: [ChargeItemsParamsModel] = []

        for item in items {
            // Extract values from the dictionary
            let itemId = item["itemId"] as? String
            let description = item["description"] as? String
            let priceStr = item["price"] as? String
            let price = Double(priceStr ?? "")
            let quantityStr = item["quantity"] as? String
            let quantity = Int(quantityStr ?? "")
            let imageUrl = item["imageUrl"] as? String
            let originalPriceStr = item["originalPrice"] as? String
            let originalPrice = Double(originalPriceStr ?? "")
            let widthStr = item["width"] as? String
            let width = Int(widthStr ?? "")
            let heightStr = item["height"] as? String
            let height = Int(heightStr ?? "")
            let lengthStr = item["length"] as? String
            let length = Int(lengthStr ?? "")
            let weightStr = item["weight"] as? String
            let weight = Int(weightStr ?? "")
            let variantCode = item["variantCode"] as? String
            let earningRuleId = item["earningRuleId"] as? String
            let specialRequest = item["specialRequest"] as? String

            let tax = (item["tax"] as? Double) ?? 0.0

            // Create an instance of ChargeItemsParamsModel
            let chargeItem = ChargeItemsParamsModel(
                itemId: itemId,
                charge_description: description,
                price: price,
                quantity: quantity,
                imageUrl: imageUrl,
                originalPrice: originalPrice,
                width: width,
                height: height,
                length: length,
                weight: weight,
                variantCode: variantCode,
                reservationCodes: nil,
                earningRuleId: earningRuleId,
                addons: nil,
                specialRequest: specialRequest,
                tax: tax
            )

            result.append(chargeItem)
        }

        return result
    }

    
    
    
    
    func getSelectedLanguage(lang:String) -> String
    {
        if lang == "ARABIC" {
            return AppLanguage.Arabic
        }
        else{
            return AppLanguage.English
            
        }
    }
    
    func getPresentedViewController() -> UIViewController?
    {
        let presentedViewController = RCTPresentedViewController()
        let topController = UIApplication.shared.topMostViewController()
        return presentedViewController ?? topController
    }
    
    override func supportedEvents() -> [String]! {
        return [RnFawryPaySdk.FAWRY_EVENT_PAYMENT_COMPLETED,RnFawryPaySdk.FAWRY_EVENT_ON_SUCCESS,RnFawryPaySdk.FAWRY_EVENT_ON_FAIL,RnFawryPaySdk.FAWRY_EVENT_CARD_MANAGER_FAIL, RnFawryPaySdk.FAWRY_EVENT_ADDRESS_MANAGER_FAIL]
    }
    
    static override func requiresMainQueueSetup() -> Bool{
        return true;
    }
}

extension UIViewController {
    func topMostViewController() -> UIViewController {
        if self.presentedViewController == nil {
            return self
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController!.topMostViewController()
        }
        if let tab = self.presentedViewController as? UITabBarController {
            if let selectedTab = tab.selectedViewController {
                return selectedTab.topMostViewController()
            }
            return tab.topMostViewController()
        }
        return self.presentedViewController!.topMostViewController()
    }
}
extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return UIWindow.key!.rootViewController?.topMostViewController()
    }
}
extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

extension Encodable {
    func toJSON(_ encoder: JSONEncoder = JSONEncoder()) -> NSString {
        guard let data = try? encoder.encode(self) else { return NSString(string: "") }
        let result = String(decoding: data, as: UTF8.self)
        return NSString(string: result)
    }
}

class RnUtils {
    
    static func getOptionalString(_ value: Any?) -> String? {
        guard let value = value else {
            return nil
        }
        return String(describing: value)
    }

    static func getRequiredString(_ value: Any?, defaultValue: String = "") -> String {
        if let optionalString = getOptionalString(value) {
            return optionalString
        } else {
            return defaultValue
        }
    }
    
    static func getOptionalInt(_ value: Any?) -> Int? {
        return Int(getRequiredString(value))
    }
    
    static func getRequiredInt(_ value: Any?, defaultValue: Int = 0) -> Int {
        return Int(getRequiredString(value)) ?? defaultValue
    }
    
    static func getOptionalDouble(_ value: Any?) -> Double? {
        return Double(getRequiredString(value))
    }
    
    static func getRequiredDouble(_ value: Any?, defaultValue: Double = 0.0) -> Double {
        return Double(getRequiredString(value)) ?? defaultValue
    }
    
    static func getOptionalLong(_ value: Any?) -> Int64? {
        return Int64(getRequiredString(value))
    }
    
    static func getRequiredLong(_ value: Any?, defaultValue: Int64 = 0) -> Int64 {
        return Int64(getRequiredString(value)) ?? defaultValue
    }
    
    static func getOptionalBoolean(_ value: Any?) -> Bool? {
        return Bool(getRequiredString(value).lowercased())
    }
    
    static func getRequiredBoolean(_ value: Any?, defaultValue: Bool = false) -> Bool {
        return Bool(getRequiredString(value).lowercased()) ?? defaultValue
    }
    
    static func getOptionalDate(_ value: Any?) -> Date? {
        let timestamp = getRequiredLong(value)
        return timestamp != 0 ? Date(timeIntervalSince1970: TimeInterval(timestamp)) : nil
    }
    
    static func getRequiredDate(_ value: Any?, defaultValue: Date = Date(timeIntervalSince1970: 0)) -> Date {
        let timestamp = getRequiredLong(value)
        return timestamp != 0 ? Date(timeIntervalSince1970: TimeInterval(timestamp)) : defaultValue
    }
    
    static func fillObject<T: NSObject>(_ dict: NSDictionary, objectType: T.Type) -> T {
        let object = T()

        let mirror = Mirror(reflecting: object)

        for case let (label?, value) in mirror.children {
            switch value {
            case is String:
                let filledValue = getRequiredString(dict[label])
                object.setValue(filledValue, forKey: label)
                
            case is String?:
                let filledValue = getOptionalString(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Int:
                let filledValue = getRequiredInt(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Int?:
                let filledValue = getOptionalInt(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Double:
                let filledValue = getRequiredDouble(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Double?:
                let filledValue = getOptionalDouble(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Bool:
                let filledValue = getRequiredBoolean(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Bool?:
                let filledValue = getOptionalBoolean(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Date:
                let filledValue = getRequiredDate(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Date?:
                let filledValue = getOptionalDate(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Int64:
                let filledValue = getRequiredLong(dict[label])
                object.setValue(filledValue, forKey: label)

            case is Int64?:
                let filledValue = getOptionalLong(dict[label])
                object.setValue(filledValue, forKey: label)

            default:
                break
            }
        }

        return object
    }

    
    static func fillDecodableObject<T: Decodable>(_ dict: NSDictionary, objectType: T.Type) -> T? {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
            let decoder = JSONDecoder()
            let object = try decoder.decode(objectType, from: jsonData)
            return object
        } catch {
            print("Error decoding object: \(error)")
            return nil
        }
    }
    
   
}
