//
//  MFCardV3Module.swift
//  MyfatoorahReactnative
//
//  The V3 card-view NativeModule. Exposes promise methods that resolve a specific
//  `MFCardV3View` by its reactTag (via the UIManager, on the main queue) and call
//  the SDK — no singleton. Also an RCTEventEmitter for per-view height / widget /
//  error events (tagged with reactTag; JS filters by node handle).
//

import Foundation
import MFSDK

@objc(MFCardV3Module)
class MFCardV3Module: RCTEventEmitter {

    // MARK: - Event emitter plumbing

    private static let cardResizedEvent = "onMFV3CardResized"
    private static let widgetEvent = "onMFV3WidgetEvent"
    private static let errorEvent = "onMFV3Error"

    /// Weak handle so the view (delegate) can push events through the emitter.
    private static weak var shared: MFCardV3Module?
    private var hasListeners = false

    override init() {
        super.init()
        MFCardV3Module.shared = self
    }

    override func supportedEvents() -> [String]! {
        return [MFCardV3Module.cardResizedEvent, MFCardV3Module.widgetEvent, MFCardV3Module.errorEvent]
    }

    override func startObserving() { hasListeners = true }
    override func stopObserving() { hasListeners = false }

    override static func requiresMainQueueSetup() -> Bool { return false }

    // MARK: - Static emit helpers (called from MFCardV3View's delegate)

    static func emitCardResized(cardId: String?, height: CGFloat) {
        guard let cardId = cardId, let module = shared, module.hasListeners else { return }
        module.sendEvent(withName: cardResizedEvent, body: ["cardId": cardId, "height": Double(height)])
    }

    static func emitWidgetEvent(cardId: String?, name: String, id: Int?, paymentMethodName: String?) {
        guard let cardId = cardId, let module = shared, module.hasListeners else { return }
        var body: [String: Any] = ["cardId": cardId, "name": name]
        if let id = id { body["id"] = id }
        if let paymentMethodName = paymentMethodName { body["paymentMethodName"] = paymentMethodName }
        module.sendEvent(withName: widgetEvent, body: body)
    }

    static func emitError(cardId: String?, code: String, message: String) {
        guard let cardId = cardId, let module = shared, module.hasListeners else { return }
        module.sendEvent(withName: errorEvent, body: ["cardId": cardId, "code": code, "message": message])
    }

    // MARK: - Session creation

    @objc
    func create(_ request: NSDictionary,
                resolve: @escaping RCTPromiseResolveBlock,
                reject: @escaping RCTPromiseRejectBlock) {
        guard let amount = decimalAmount(request["Amount"]) else {
            reject("invalid_request", "Amount is required and must be a valid decimal", nil)
            return
        }
        let currency = currencyISO(request["Currency"])
        let mode = MFV3PaymentMode(rawValue: request["Mode"] as? String ?? "") ?? .completePayment
        let require3DS = request["Require3DS"] as? Bool ?? true
        let saveCard = request["SaveCard"] as? Bool ?? false
        let customerReference = request["CustomerReference"] as? String

        MFV3Session.create(amount: amount,
                           currency: currency,
                           mode: mode,
                           require3DS: require3DS,
                           saveCard: saveCard,
                           customerReference: customerReference) { result in
            MFCardV3Module.resolveSession(result, resolve: resolve, reject: reject)
        }
    }

    @objc
    func createVerify(_ request: NSDictionary,
                      resolve: @escaping RCTPromiseResolveBlock,
                      reject: @escaping RCTPromiseRejectBlock) {
        guard let customerReference = request["CustomerReference"] as? String else {
            reject("invalid_request", "CustomerReference is required for verify", nil)
            return
        }
        let currency = currencyISO(request["Currency"])
        let require3DS = request["Require3DS"] as? Bool ?? true

        MFV3Session.createVerify(customerReference: customerReference,
                                 currency: currency,
                                 require3DS: require3DS) { result in
            MFCardV3Module.resolveSession(result, resolve: resolve, reject: reject)
        }
    }

    // MARK: - View-bound methods (resolved by reactTag)

    @objc
    func load(_ cardId: NSString,
              session: NSDictionary,
              resolve: @escaping RCTPromiseResolveBlock,
              reject: @escaping RCTPromiseRejectBlock) {
        guard let data = try? JSONSerialization.data(withJSONObject: session, options: []),
              let sessionData = try? JSONDecoder().decode(MFV3SessionData.self, from: data) else {
            reject("invalid_session", "Could not parse session", nil)
            return
        }
        resolveCardView(cardId, reject: reject) { view in
            view.cardView.load(session: sessionData)
            resolve(nil)
        }
    }

    @objc
    func submit(_ cardId: NSString,
                currency: NSString,
                resolve: @escaping RCTPromiseResolveBlock,
                reject: @escaping RCTPromiseRejectBlock) {
        let mfCurrency = currencyISO(currency)
        resolveCardView(cardId, reject: reject) { view in
            view.cardView.submit(currency: mfCurrency) { result in
                switch result {
                case .success(let paymentResult):
                    resolve(MFV3Serializers.jsonString(from: paymentResult))
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, nil)
                }
            }
        }
    }

    @objc
    func verify(_ cardId: NSString,
                resolve: @escaping RCTPromiseResolveBlock,
                reject: @escaping RCTPromiseRejectBlock) {
        resolveCardView(cardId, reject: reject) { view in
            view.cardView.verify { result in
                switch result {
                case .success(let verifyResult):
                    resolve(MFV3Serializers.jsonString(from: verifyResult))
                case .failure(let error):
                    reject(error.statusCode, error.errorDescription, nil)
                }
            }
        }
    }

    // MARK: - Helpers

    private func resolveCardView(_ cardId: NSString,
                                 reject: @escaping RCTPromiseRejectBlock,
                                 _ body: @escaping (MFCardV3View) -> Void) {
        DispatchQueue.main.async {
            guard let view = MFCardV3View.view(for: cardId as String) else {
                reject("view_not_found", "MFCardViewV3 not mounted for id \(cardId)", nil)
                return
            }
            body(view)
        }
    }

    private func currencyISO(_ value: Any?) -> MFCurrencyISO? {
        guard let raw = value as? String, !raw.isEmpty else { return nil }
        return MFCurrencyISO(rawValue: raw)
    }

    /// Parses a money amount, correcting the Double→Decimal rounding error that the RN
    /// bridge introduces for some values (e.g. 99.99 arrives as 99.9899…). Reuses the
    /// bridge's existing `cleanDecimal` (NSDecimalRound to 4 places) — the same helper
    /// the V2 path already uses for InvoiceValue — so 99.9899… rounds back to 99.99.
    private func decimalAmount(_ value: Any?) -> Decimal? {
        if let n = value as? NSNumber {
            return MFExecPaymentContainer.cleanDecimal(n.decimalValue, to: 4)
        }
        // Also accept a string amount (already exact) for callers that prefer it.
        if let s = value as? String, let d = Decimal(string: s) {
            return MFExecPaymentContainer.cleanDecimal(d, to: 4)
        }
        return nil
    }

    private static func resolveSession(_ result: Result<MFV3SessionData, MFFailResponse>,
                                       resolve: @escaping RCTPromiseResolveBlock,
                                       reject: @escaping RCTPromiseRejectBlock) {
        switch result {
        case .success(let session):
            resolve(MFV3Serializers.jsonString(from: session))
        case .failure(let error):
            reject(error.statusCode, error.errorDescription, nil)
        }
    }
}
