import FawryPaySDK
import Foundation

typealias FawryPayListener = (String, String) -> Void

@objcMembers
class RnFawryPaySdk: HybridRnFawryPaySdkSpec {

    static let NAME = "RnFawryPaySdk"
    static let EVENT_PAYMENT_COMPLETED = "EVENT_PAYMENT_COMPLETED"
    static let EVENT_ON_SUCCESS = "EVENT_ON_SUCCESS"
    static let EVENT_ON_FAIL = "EVENT_ON_FAIL"
    static let EVENT_CardManager_FAIL = "EVENT_CardManager_FAIL"
    static let EVENT_COMPLETION_BLOCK = "EVENT_COMPLETION_BLOCK"
    static let EVENT_PRE_COMPLETION = "EVENT_PRE_COMPLETION"
    static let EVENT_READY = "EVENT_READY"
    static let EVENT_CARD_ADDED = "EVENT_CARD_ADDED"
    static let EVENT_CARD_MANAGER_DISMISSED = "EVENT_CARD_MANAGER_DISMISSED"

    func supportedEvents() -> [String]! {
        return [
            RnFawryPaySdk.EVENT_PAYMENT_COMPLETED,
            RnFawryPaySdk.EVENT_ON_SUCCESS,
            RnFawryPaySdk.EVENT_ON_FAIL,
            RnFawryPaySdk.EVENT_CardManager_FAIL,
            RnFawryPaySdk.EVENT_COMPLETION_BLOCK,
            RnFawryPaySdk.EVENT_PRE_COMPLETION,
            RnFawryPaySdk.EVENT_READY,
            RnFawryPaySdk.EVENT_CARD_ADDED,
            RnFawryPaySdk.EVENT_CARD_MANAGER_DISMISSED
        ]
    }

    private var fawryListener: FawryPayListener? = nil

    func addListener(listener: @escaping (_ eventName: String, _ payload: String) -> Void) throws -> () -> Void {
        fawryListener = listener

        DispatchQueue.main.async { [unowned self] in
            self.fawryListener?(RnFawryPaySdk.EVENT_READY, "SDK Ready")
        }

        return {
            self.fawryListener = nil
        }
    }

    private func emit(event: String, payload: Any?) {
        guard let fawryListener = fawryListener else { return }
        let jsonPayload = JSONConverter.toJson(payload)
        fawryListener(event, jsonPayload)
    }

    func multiply(a: Double, b: Double) throws -> Double {
        return a * b
    }

    func startPayment(model: FawryLaunchModel) throws {
        let fawryPayModel = FawryModelConverter.convert(model)
        FawryModelConverter.printModel(fawryPayModel)

        // Load fonts
        let cairoBold = UIFont(name: "Cairo-Bold", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .bold)
        let cairoSemiBold = UIFont(name: "Cairo-SemiBold", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .semibold)
        let cairoRegular = UIFont(name: "Cairo-Regular", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .regular)
        let fawryProBold = UIFont(name: "FawryPro-Bold", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .bold)

        getPresentedViewController { currentViewController in
            guard let viewController = currentViewController else {
                print("⚠️ No view controller found")
                self.emit(event: RnFawryPaySdk.EVENT_ON_FAIL, payload: ["error": "No view controller found"])
                return
            }

          AnonymousFrameWorkHelper.sharedInstance.launchAnonymousSDK(
                on: viewController,
                launchModel: fawryPayModel,
                baseURL: model.baseUrl,
                appLanguage: self.mapLanguage(model.lang),
                enable3Ds: model.allow3DPayment,
                authCaptureModePayment: model.authCaptureMode,
                cairoBold: cairoBold,
                cairoSemiBold: cairoSemiBold,
                cairoRegular: cairoRegular,
                fawryProBold: fawryProBold,

                completionBlock: { status in
                    self.emit(
                        event: RnFawryPaySdk.EVENT_COMPLETION_BLOCK,
                        payload: "Initializing"
                    )
                },

                onPreCompletionHandler: { error in
                    self.emit(
                        event: RnFawryPaySdk.EVENT_PRE_COMPLETION,
                        payload: error?.message ?? "Unknown Error"
                    )
                },

                errorBlock: { error in
                    self.emit(
                        event: RnFawryPaySdk.EVENT_ON_FAIL,
                        payload: error?.message ?? "Unknown Error"
                    )
                },

                onPaymentCompletedHandler: { chargeResponse in
                    if let chargeResponse = chargeResponse as? PaymentChargeResponse {
                        self.emit(
                            event: RnFawryPaySdk.EVENT_PAYMENT_COMPLETED,
                            payload: chargeResponse
                        )
                    } else if let errorResponse = chargeResponse as? FawryError {
                        self.emit(
                            event: RnFawryPaySdk.EVENT_ON_FAIL,
                            payload: errorResponse.message
                        )
                    }
                },

                onSuccessHandler: { response in
                    if let chargeResponse = response as? PaymentChargeResponse {
                        self.emit(
                            event: RnFawryPaySdk.EVENT_ON_SUCCESS,
                            payload: chargeResponse
                        )
                    }
                }
            )
        }
    }

  func openCardsManager(baseUrl: String, lang: FawryLanguages, merchantInfo: MerchantInfo, customerInfo: CustomerInfo,tokenizationSignature: String?) throws {
        let customer = FawryModelConverter.convert(customerInfo)
        let merchant = FawryModelConverter.convert(merchantInfo)
        let launchModel = FawryPaySDK.FawryLaunchModel(
            customer: customer,
            merchant: merchant,
            chargeItems: [],
            tokenizationSignature: tokenizationSignature
        )

        // Load fonts
        let cairoBold = UIFont(name: "Cairo-Bold", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .bold)
        let cairoSemiBold = UIFont(name: "Cairo-SemiBold", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .semibold)
        let cairoRegular = UIFont(name: "Cairo-Regular", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .regular)
        let fawryProBold = UIFont(name: "FawryPro-Bold", size: 16) ?? UIFont.systemFont(ofSize: 16, weight: .bold)

        getPresentedViewController { currentViewController in
            guard let viewController = currentViewController else {
                print("⚠️ No view controller found")
                self.emit(
                    event: RnFawryPaySdk.EVENT_CardManager_FAIL,
                    payload: ["error": "No view controller found"]
                )
                return
            }

          AnonymousFrameWorkHelper.sharedInstance.launchCardManagerSDK(
                on: viewController,
                launchModel: launchModel,
                baseURL: baseUrl,
                appLanguage: self.mapLanguage(lang),
                cairoBold: cairoBold,
                cairoSemiBold: cairoSemiBold,
                cairoRegular: cairoRegular,
                fawryProBold: fawryProBold,
                enable3Ds: false,
                errorBlock: { error in
                    self.emit(
                        event: RnFawryPaySdk.EVENT_CardManager_FAIL,
                        payload: ["error": error?.message ?? "Unknown error"]
                    )
                },
                onAddedNewCard: { card in
                    self.emit(
                        event: RnFawryPaySdk.EVENT_CARD_ADDED,
                        payload: card
                    )
                },
                dismissBlock: {
                    self.emit(
                        event: RnFawryPaySdk.EVENT_CARD_MANAGER_DISMISSED,
                        payload: ["message": "Card manager dismissed"]
                    )
                }
            )
        }
    }

    private func mapLanguage(_ lang: FawryLanguages) -> String {
        switch lang {
        case .arabic: return AppLanguage.Arabic
        default: return AppLanguage.English
        }
    }

    private func getPresentedViewController(completion: @escaping (UIViewController?) -> Void) {
        DispatchQueue.main.async {
            var topController = UIApplication.shared
                .connectedScenes
                .compactMap { $0 as? UIWindowScene }
                .flatMap { $0.windows }
                .first(where: { $0.isKeyWindow })?
                .rootViewController

            while let presented = topController?.presentedViewController {
                topController = presented
            }

            completion(topController)
        }
    }

    deinit {
        fawryListener = nil
    }
}

// MARK: - JSON Converter

class JSONConverter {
    private static var context: [ObjectIdentifier: Any] = [:]

    public static func toJson(_ object: Any, handleCircularReferences: Bool = false) -> String {
        context.removeAll()

        if let string = object as? String {
            return string
        }

        do {
            let jsonObject = try convertToJsonObject(
                object,
                handleCircularReferences: handleCircularReferences
            )
            let data = try JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted])
            return String(data: data, encoding: .utf8) ?? "{}"
        } catch {
            return "{\"error\":\"\(error.localizedDescription)\"}"
        }
    }

    private static func convertToJsonObject(_ object: Any, handleCircularReferences: Bool) throws -> Any {
        if handleCircularReferences {
            let objectId = ObjectIdentifier(object as AnyObject)
            if let existing = context[objectId] {
                return existing
            }
        }

        let mirror = Mirror(reflecting: object)

        if mirror.displayStyle == .optional {
            if mirror.children.isEmpty {
                return NSNull()
            }
            let (_, value) = mirror.children.first!
            return try convertToJsonObject(value, handleCircularReferences: handleCircularReferences)
        }

        switch object {
        case let num as NSNumber: return num
        case let str as String: return str
        case let bool as Bool: return bool
        case let date as Date: return ISO8601DateFormatter().string(from: date)
        case let url as URL: return url.absoluteString
        case is NSNull: return NSNull()
        case let array as [Any]:
            if handleCircularReferences {
                let objectId = ObjectIdentifier(array as AnyObject)
                context[objectId] = array
            }
            return try array.map { try convertToJsonObject($0, handleCircularReferences: handleCircularReferences) }
        case let dict as [String: Any]:
            if handleCircularReferences {
                let objectId = ObjectIdentifier(dict as AnyObject)
                context[objectId] = dict
            }
            return try dict.mapValues { try convertToJsonObject($0, handleCircularReferences: handleCircularReferences) }
        default: break
        }

        if mirror.displayStyle == .struct || mirror.displayStyle == .class {
            var dictionary = [String: Any]()

            if handleCircularReferences {
                let objectId = ObjectIdentifier(object as AnyObject)
                context[objectId] = dictionary
            }

            for child in mirror.children {
                guard let key = child.label else { continue }
                dictionary[key] = try convertToJsonObject(
                    child.value,
                    handleCircularReferences: handleCircularReferences
                )
            }

            if let superclassMirror = mirror.superclassMirror {
                for child in superclassMirror.children {
                    guard let key = child.label else { continue }
                    dictionary[key] = try convertToJsonObject(
                        child.value,
                        handleCircularReferences: handleCircularReferences
                    )
                }
            }

            return dictionary
        }

        return String(describing: object)
    }
}
