//
// NamiFlowManagerBridge.swift
// RNNami
//
// Copyright © 2025 Nami ML Inc.. All rights reserved.
//

import Foundation
import NamiApple
import React

@objc(RNNamiFlowManager)
class RNNamiFlowManager: RCTEventEmitter {
    static var shared: RNNamiFlowManager?

    override init() {
        super.init()
    }

    override class func requiresMainQueueSetup() -> Bool {
        true
    }

    private var hasListeners = false
    private var eventHandlerActive = false
    private var stepHandoffActive = false
    override func startObserving() {
        hasListeners = true
    }

    override func stopObserving() {
        hasListeners = false
    }

    private func safeSend(withName name: String, body: Any?) {
        guard hasListeners else {
            print("[RNNamiFlowManager] Warning: no listeners, so event not being sent to JS.")
            return
        }
        sendEvent(withName: name, body: body)
    }

    override func supportedEvents() -> [String]! {
        return ["Handoff", "FlowEvent"]
    }

    // MARK: - Pending handoff completion token storage

    /// Serial queue used to synchronise all reads/writes of `pendingHandoffs`.
    private let handoffQueue = DispatchQueue(label: "com.namiml.rn.flow-manager.handoffs")
    /// Maps handoff UUID strings to their in-flight completion tokens.
    private var pendingHandoffs: [String: NamiHandoffCompletion] = [:]

    // MARK: - Step-handoff registration

    @objc func registerStepHandoff() {
        stepHandoffActive = true
        NamiFlowManager.registerStepHandoff { [weak self] tag, data, complete in
            guard let self = self, self.stepHandoffActive else { return }

            // Generate a UUID and store the completion token thread-safely.
            let handoffId = UUID().uuidString
            self.handoffQueue.sync {
                self.pendingHandoffs[handoffId] = complete
            }

            var payload: [String: Any] = [
                "handoffId": handoffId,
                "handoffTag": tag,
            ]
            if let data = data {
                payload["handoffData"] = data
            }

            self.safeSend(withName: "Handoff", body: payload)
        }
    }

    @objc func unregisterStepHandoff() {
        stepHandoffActive = false
    }

    // MARK: - Complete handoff (called from JS)

    /**
     Resolves a pending handoff by decoding the outcome dict sent from JavaScript
     and calling the stored `NamiHandoffCompletion` token.

     - Parameter handoffId: The UUID string previously emitted in the `Handoff` event.
     - Parameter outcome: Discriminated-union dict produced by the TS bridge, e.g.
       `{ "kind": "done" }` or `{ "kind": "login", "result": "success", … }`.
     */
    @objc func completeHandoff(_ handoffId: String, outcome: NSDictionary) {
        // Look up and remove the token atomically so it can only fire once.
        var token: NamiHandoffCompletion?
        handoffQueue.sync {
            token = pendingHandoffs.removeValue(forKey: handoffId)
        }

        guard let complete = token else {
            // Stale call — harmless after a Metro reload or duplicate invocation.
            print("[RNNamiFlowManager] Warning: completeHandoff called with unknown or already-consumed handoffId '\(handoffId)'. Ignoring.")
            return
        }

        guard let decoded = decodeOutcome(outcome, handoffId: handoffId) else {
            // decodeOutcome already logged the error; do NOT call complete.
            return
        }

        complete(decoded)
    }

    // MARK: - Outcome decoder

    /**
     Decodes a JS outcome dict into a typed `NamiHandoffOutcome`.

     Returns `nil` and logs a loud dev-mode error for any invalid payload so that
     `completeHandoff` never delivers a malformed outcome to the flow engine.
     */
    private func decodeOutcome(_ outcome: NSDictionary, handoffId: String) -> NamiHandoffOutcome? {
        guard let kind = outcome["kind"] as? String else {
            logDecodeError(handoffId: handoffId, message: "outcome dict is missing required 'kind' field. Received: \(outcome)")
            return nil
        }

        switch kind {
        case "done":
            return .done

        case "permission":
            guard let permissionString = outcome["permission"] as? String else {
                logDecodeError(handoffId: handoffId, message: "'permission' kind requires a 'permission' String field. Received: \(outcome)")
                return nil
            }
            guard let perm = NamiPermissionOutcome(rawValue: permissionString) else {
                logDecodeError(handoffId: handoffId, message: "Unknown NamiPermissionOutcome rawValue '\(permissionString)'. Received: \(outcome)")
                return nil
            }
            guard let granted = outcome["granted"] as? Bool else {
                logDecodeError(handoffId: handoffId, message: "'permission' kind requires a 'granted' Bool field. Received: \(outcome)")
                return nil
            }
            let detail = outcome["detail"] as? String
            return .permission(perm, granted: granted, detail: detail)

        case "purchase":
            guard let result = outcome["result"] as? String else {
                logDecodeError(handoffId: handoffId, message: "'purchase' kind requires a 'result' String field. Received: \(outcome)")
                return nil
            }
            switch result {
            case "success":
                guard let details = outcome["details"] as? [String: Any] else {
                    logDecodeError(handoffId: handoffId, message: "'purchase/success' requires a 'details' dict. Received: \(outcome)")
                    return nil
                }
                guard let purchaseSuccess = buildPurchaseSuccess(from: details, handoffId: handoffId) else {
                    // buildPurchaseSuccess already logged the specific error.
                    return nil
                }
                return .purchase(.success(purchaseSuccess))
            case "cancelled":
                return .purchase(.cancelled)
            case "failed":
                return .purchase(.failed(nil))
            default:
                logDecodeError(handoffId: handoffId, message: "Unknown 'purchase' result '\(result)'. Expected 'success', 'cancelled', or 'failed'. Received: \(outcome)")
                return nil
            }

        case "login":
            guard let result = outcome["result"] as? String else {
                logDecodeError(handoffId: handoffId, message: "'login' kind requires a 'result' String field. Received: \(outcome)")
                return nil
            }
            switch result {
            case "success":
                guard let details = outcome["details"] as? [String: Any] else {
                    logDecodeError(handoffId: handoffId, message: "'login/success' requires a 'details' dict. Received: \(outcome)")
                    return nil
                }
                guard let loginId = details["loginId"] as? String else {
                    logDecodeError(handoffId: handoffId, message: "'login/success' details missing required 'loginId' String. Received: \(details)")
                    return nil
                }
                let cdpId = details["cdpId"] as? String
                let rawAttrs = details["attributes"] as? [String: Any] ?? [:]

                // Validate that every attribute value is a supported primitive.
                for (key, value) in rawAttrs {
                    guard NamiHandoffAttributeValue(value) != nil else {
                        logDecodeError(handoffId: handoffId, message: "Login attribute '\(key)' has unsupported value type \(type(of: value)). Only String, Bool, Int, and Double are allowed. Received: \(value)")
                        return nil
                    }
                }

                let loginSuccess = NamiLoginSuccess(loginId: loginId, cdpId: cdpId, attributes: rawAttrs)
                return .login(.success(loginSuccess))
            case "cancelled":
                return .login(.cancelled)
            case "failed":
                return .login(.failed(nil))
            default:
                logDecodeError(handoffId: handoffId, message: "Unknown 'login' result '\(result)'. Expected 'success', 'cancelled', or 'failed'. Received: \(outcome)")
                return nil
            }

        default:
            logDecodeError(handoffId: handoffId, message: "Unknown outcome 'kind' '\(kind)'. Expected 'done', 'permission', 'purchase', or 'login'. Received: \(outcome)")
            return nil
        }
    }

    // MARK: - NamiPurchaseSuccess builder

    /**
     Builds a `NamiPurchaseSuccess` from the 'details' sub-dict of a purchase/success outcome.

     Mirrors the construction logic already used by `RNNamiPaywallManager.buySkuComplete`.
     Returns `nil` and logs a dev error on any missing or invalid field.
     */
    private func buildPurchaseSuccess(from details: [String: Any], handoffId: String) -> NamiPurchaseSuccess? {
        guard let product = details["product"] as? [String: Any] else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' details missing 'product' dict. Received: \(details)")
            return nil
        }
        guard let storeId = product["skuId"] as? String else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' product missing 'skuId' String. Received: \(product)")
            return nil
        }
        guard let namiId = product["id"] as? String else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' product missing 'id' String. Received: \(product)")
            return nil
        }
        let skuTypeString = product["type"] as? String ?? "unknown"
        let namiSkuType: NamiSKUType
        switch skuTypeString {
        case "one_time_purchase":
            namiSkuType = .one_time_purchase
        case "subscription":
            namiSkuType = .subscription
        default:
            namiSkuType = .unknown
        }
        let namiSku = NamiSKU(namiId: namiId, storeId: storeId, skuType: namiSkuType)

        guard let transactionID = details["transactionID"] as? String else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' details missing 'transactionID' String. Received: \(details)")
            return nil
        }
        guard let originalTransactionID = details["originalTransactionID"] as? String else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' details missing 'originalTransactionID' String. Received: \(details)")
            return nil
        }
        guard let currencyCode = details["currencyCode"] as? String else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' details missing 'currencyCode' String. Received: \(details)")
            return nil
        }

        // Accept price as either a Decimal-bridged NSDecimalNumber, a Double, or a String.
        let price: Decimal
        if let decimalNumber = details["price"] as? NSDecimalNumber {
            price = decimalNumber as Decimal
        } else if let doubleValue = details["price"] as? Double {
            price = Decimal(doubleValue)
        } else if let priceString = details["price"] as? String, let parsed = Decimal(string: priceString) {
            price = parsed
        } else {
            logDecodeError(handoffId: handoffId, message: "'purchase/success' details missing or unparseable 'price'. Received: \(details)")
            return nil
        }

        return NamiPurchaseSuccess(
            product: namiSku,
            transactionID: transactionID,
            originalTransactionID: originalTransactionID,
            price: price,
            currencyCode: currencyCode
        )
    }

    // MARK: - Error logging helper

    private func logDecodeError(handoffId: String, message: String) {
        let fullMessage = "[RNNamiFlowManager] completeHandoff decode error (handoffId='\(handoffId)'): \(message)"
        print(fullMessage)
        #if DEBUG
            assertionFailure(fullMessage)
        #endif
    }

    @objc func registerEventHandler() {
        eventHandlerActive = true
        NamiFlowManager.registerEventHandler { [weak self] payload in
            guard let self = self, self.eventHandlerActive else { return }
            self.safeSend(withName: "FlowEvent", body: payload)
        }
    }

    @objc func unregisterEventHandler() {
        eventHandlerActive = false
    }

    @objc func resume() {
        NamiFlowManager.resume()
    }

    @objc func pause() {
        NamiFlowManager.pause()
    }

    @objc func finish() {
        NamiFlowManager.finish()
    }

    @objc func isFlowOpen(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            let isFlowOpen = NamiFlowManager.isFlowOpen()
            resolve(isFlowOpen)
        }
    }
}
