import Foundation
import UIKit
import ChottuLinkSDK

// Reference to Unity's C function for messaging
@_silgen_name("UnitySendMessage")
func UnitySendMessage(_ obj: UnsafePointer<CChar>, _ method: UnsafePointer<CChar>, _ msg: UnsafePointer<CChar>) -> Void

private func sendUnity(_ method: String, _ param: String) {
    let go = "___ChottuLinkListener"
    go.withCString { g in
        method.withCString { m in
            param.withCString { p in
                UnitySendMessage(g, m, p)
            }
        }
    }
}

// -------------------------------------------------------------------------------------------------
// MARK: - Bootstrap state for killed-state delivery
// -------------------------------------------------------------------------------------------------
class ChottuLinkBootstrap {
    static var isInitialized: Bool = false
    static var pendingLaunchURL: URL?
    static var apiKey: String = ""
}

// MARK: - Unity JSON -> ChottuLinkSDK models (SDK types are not public Decodable; parse dictionaries directly)

private enum UnityBridgeJsonError: Error, LocalizedError {
    case invalidUtf8
    case notJSONObject
    case missingField(String)

    var errorDescription: String? {
        switch self {
        case .invalidUtf8: return "Invalid UTF-8"
        case .notJSONObject: return "JSON root must be an object"
        case .missingField(let k): return "Missing or invalid field: \(k)"
        }
    }
}

private func jsonDictionary(fromUTF8Payload raw: String) throws -> [String: Any] {
    guard let data = raw.data(using: .utf8) else { throw UnityBridgeJsonError.invalidUtf8 }
    let obj = try JSONSerialization.jsonObject(with: data)
    guard let dict = obj as? [String: Any] else { throw UnityBridgeJsonError.notJSONObject }
    return dict
}

private func jsonDictionary(fromCString cString: UnsafePointer<CChar>) throws -> [String: Any] {
    try jsonDictionary(fromUTF8Payload: String(cString: cString))
}

/// `metadata` from Unity is a JSON object -> [String: Any] (nested values stay JSON-serializable types).
private func metadataObject(from json: [String: Any], key: String) -> [String: Any]? {
    guard json[key] != nil else { return nil }
    guard let value = json[key], !(value is NSNull) else { return nil }
    return value as? [String: Any]
}

private func sendSuccessEnvelope(_ data: Any) {
    let envelope: [String: Any] = ["data": data]
    do {
        let json = try JSONSerialization.data(withJSONObject: envelope)
        let text = String(data: json, encoding: .utf8) ?? "{\"data\":null}"
        sendUnity("OnSuccess", text)
    } catch {
        sendUnity("OnSuccess", "{\"data\":null}")
    }
}

private func sendErrorEnvelope(code: String, message: String, details: String) {
    let envelope: [String: Any] = [
        "code": code,
        "message": message,
        "details": details
    ]
    do {
        let json = try JSONSerialization.data(withJSONObject: envelope)
        let text = String(data: json, encoding: .utf8) ?? "{\"code\":\"native_error\",\"message\":\"Unknown error\",\"details\":\"\"}"
        sendUnity("OnError", text)
    } catch {
        sendUnity("OnError", "{\"code\":\"native_error\",\"message\":\"Unknown error\",\"details\":\"\"}")
    }
}

private func nilToNull(_ value: String?) -> Any {
    return value ?? NSNull()
}

private func sdkErrorMessage(_ error: Error) -> String {
    if let localized = error as? LocalizedError, let description = localized.errorDescription, !description.isEmpty {
        return description
    }
    return error.localizedDescription
}

/// Unity `DllImport` may call from a background thread. Do not use `@MainActor` on `@_cdecl` exports.
/// ChottuLinkSDK is `@MainActor`; we hop to the main queue then enter the MainActor domain explicitly.
/// Copy C strings to `String` before calling — never capture `UnsafePointer` inside the work item.
func performOnMain(_ work: @escaping @MainActor () -> Void) {
    if Thread.isMainThread {
        MainActor.assumeIsolated {
            work()
        }
    } else {
        DispatchQueue.main.sync {
            MainActor.assumeIsolated {
                work()
            }
        }
    }
}

// -------------------------------------------------------------------------------------------------
// MARK: - Global C-callable wrappers
// -------------------------------------------------------------------------------------------------

@_cdecl("cl_init")
public func cl_init(_ apiKeyCString: UnsafePointer<CChar>) {
    let apiKey = String(cString: apiKeyCString)
    performOnMain {
        ChottuLinkBootstrap.apiKey = apiKey
        ChottuLinkBootstrap.isInitialized = false
        let config = CLConfiguration(apiKey: apiKey, isDebugEnabled: false, delegate: ChottuLinkUnity.shared)
        ChottuLink.initialize(config: config)
    }
}

@_cdecl("cl_createDynamicLink")
public func cl_createDynamicLink(_ jsonCString: UnsafePointer<CChar>) {
    let jsonStr = String(cString: jsonCString)
    performOnMain {
    guard let data = jsonStr.data(using: .utf8) else {
        sendUnity("OnError", "Invalid JSON (UTF-8 encoding failed)")
        return
    }

    let dict: [String: Any]
    do {
        let obj = try JSONSerialization.jsonObject(with: data)
        guard let parsed = obj as? [String: Any] else {
            sendUnity("OnError", "Invalid JSON (expected object)")
            return
        }
        dict = parsed
    } catch {
        sendUnity("OnError", "Invalid JSON (\(error.localizedDescription))")
        return
    }

    var linkUrl = ""
    var domain = ""

    if let link = dict["link"] as? String {
        linkUrl = link
    }

    if let tempDomain = dict["domain"] as? String {
        domain = tempDomain
    }

    var params = CLDynamicLinkBuilder(destinationURL: linkUrl, domain: domain)

    if let _androidBehaviour = dict["androidBehaviour"] as? Int {
        params = params.setAndroidBehaviour(_androidBehaviour == 1 ? CLDynamicLinkBehaviour.browser : CLDynamicLinkBehaviour.app)
    }

    if let _iosBehaviour = dict["iosBehaviour"] as? Int {
        params = params.setIOSBehaviour(_iosBehaviour == 1 ? CLDynamicLinkBehaviour.browser : CLDynamicLinkBehaviour.app)
    }

    if let _utmSource = dict["utmSource"] as? String {
        params = params.setUTMSource(_utmSource)
    }

    if let _utmMedium = dict["utmMedium"] as? String {
        params = params.setUTMMedium(_utmMedium)
    }
    if let _utmCampaign = dict["utmCampaign"] as? String {
        params = params.setUTMCampaign(_utmCampaign)
    }

    if let _utmContent = dict["utmContent"] as? String {
        params = params.setUTMContent(_utmContent)
    }

    if let _utmTerm = dict["utmTerm"] as? String {
        params = params.setUTMTerm(_utmTerm)
    }

    if let _linkName = dict["linkName"] as? String {
        params = params.setLinkName(_linkName)
    }

    if let _selectedPath = dict["selectedPath"] as? String {
        params = params.setSelectedPath(_selectedPath)
    }

    if let _socialDescription = dict["socialDescription"] as? String {
        params = params.setSocialDescription(_socialDescription)
    }

    if let _socialImageUrl = dict["socialImageUrl"] as? String {
        params = params.setSocialImageUrl(_socialImageUrl)
    }

    if let _socialTitle = dict["socialTitle"] as? String {
        params = params.setSocialTitle(_socialTitle)
    }

        Task { @MainActor in
            do {
                if let url = try await ChottuLink.createDynamicLink(for: params.build()) {
                    sendUnity("OnSuccess", url)
                } else {
                    sendUnity("OnError", "Unknown error")
                }
            } catch {
                sendUnity("OnError", sdkErrorMessage(error))
            }
        }
    }
}

@_cdecl("cl_resolveLink")
public func cl_resolveLink(_ shortCString: UnsafePointer<CChar>) {
    let shortUrl = String(cString: shortCString)
    Task { @MainActor in
        do {
            let data = try await ChottuLink.getAppLinkDataFromUrl(from: shortUrl)
            guard let link = data.link else {
                sendUnity("OnError", "No deep link found")
                return
            }
            let shortLink = data.shortLink?.absoluteString ?? ""
            let json: [String: Any] = [
                "link": link.absoluteString,
                "shortLink": shortLink,
                "shortLinkRaw": data.shortLinkRaw?.absoluteString ?? "",
                "isDeferred": data.isDeferred
            ]
            let jsonData = try JSONSerialization.data(withJSONObject: json)
            let jsonString = String(data: jsonData, encoding: .utf8) ?? ""
            sendUnity("OnSuccess", jsonString)
        } catch {
            sendUnity("OnError", sdkErrorMessage(error))
        }
    }
}

@_cdecl("cl_identify")
public func cl_identify(_ jsonCString: UnsafePointer<CChar>) {
    let payload = String(cString: jsonCString)
    performOnMain {
        do {
            let json = try jsonDictionary(fromUTF8Payload: payload)
            guard let id = json["id"] as? String, !id.isEmpty else {
                throw UnityBridgeJsonError.missingField("id")
            }
            let customerMeta = CLCustomerMeta(
                id: id,
                name: json["name"] as? String,
                email: json["email"] as? String,
                phone: json["phone"] as? String,
                emailSha256: json["emailSha256"] as? String,
                phoneSha256: json["phoneSha256"] as? String
            )
            ChottuLink.identify(customerMeta)
            sendSuccessEnvelope("ok")
        } catch {
            sendErrorEnvelope(code: "invalid_payload", message: error.localizedDescription, details: "identify")
        }
    }
}

@_cdecl("cl_trackLead")
public func cl_trackLead(_ jsonCString: UnsafePointer<CChar>) {
    let payload = String(cString: jsonCString)
    performOnMain {
        do {
            let json = try jsonDictionary(fromUTF8Payload: payload)
            let leadMeta = CLLeadMeta(
                eventName: json["eventName"] as? String,
                metadata: metadataObject(from: json, key: "metadata")
            )
            ChottuLink.trackLead(leadMeta)
            sendSuccessEnvelope("ok")
        } catch {
            sendErrorEnvelope(code: "invalid_payload", message: error.localizedDescription, details: "trackLead")
        }
    }
}

@_cdecl("cl_trackConversion")
public func cl_trackConversion(_ jsonCString: UnsafePointer<CChar>) {
    let payload = String(cString: jsonCString)
    performOnMain {
        do {
            let json = try jsonDictionary(fromUTF8Payload: payload)
            let revenue: Double
            if let d = json["revenue"] as? Double {
                revenue = d
            } else if let i = json["revenue"] as? Int {
                revenue = Double(i)
            } else if let n = json["revenue"] as? NSNumber {
                revenue = n.doubleValue
            } else {
                throw UnityBridgeJsonError.missingField("revenue")
            }
            let conversionMeta = CLConversionMeta(
                revenue: revenue,
                currency: json["currency"] as? String,
                eventName: json["eventName"] as? String,
                productId: json["productId"] as? String,
                transactionId: json["transactionId"] as? String,
                metadata: metadataObject(from: json, key: "metadata")
            )
            ChottuLink.trackConversion(conversionMeta)
            sendSuccessEnvelope("ok")
        } catch {
            sendErrorEnvelope(code: "invalid_payload", message: error.localizedDescription, details: "trackConversion")
        }
    }
}

@_cdecl("cl_trackEvent")
public func cl_trackEvent(_ jsonCString: UnsafePointer<CChar>) {
    let payload = String(cString: jsonCString)
    performOnMain {
        do {
            let json = try jsonDictionary(fromUTF8Payload: payload)
            guard let name = json["eventName"] as? String, !name.isEmpty else {
                throw UnityBridgeJsonError.missingField("eventName")
            }
            ChottuLink.trackEvent(name: name, data: metadataObject(from: json, key: "metadata"))
            sendSuccessEnvelope("ok")
        } catch {
            sendErrorEnvelope(code: "invalid_payload", message: error.localizedDescription, details: "trackEvent")
        }
    }
}

@_cdecl("cl_flush")
public func cl_flush() {
    performOnMain {
        ChottuLink.flush()
        sendSuccessEnvelope("ok")
    }
}

@_cdecl("cl_logout")
public func cl_logout() {
    performOnMain {
        ChottuLink.logout()
        sendSuccessEnvelope("ok")
    }
}

@_cdecl("cl_optOut")
public func cl_optOut(_ enabled: Bool) {
    performOnMain {
        ChottuLink.optOut(enabled)
        sendSuccessEnvelope("ok")
    }
}

@_cdecl("cl_isOptedOut")
public func cl_isOptedOut() {
    performOnMain {
        sendSuccessEnvelope(ChottuLink.isOptedOut)
    }
}

@_cdecl("cl_getAttributionData")
public func cl_getAttributionData() {
    performOnMain {
        guard let attribution = ChottuLink.getAttributionData() else {
            sendSuccessEnvelope([String: Any]())
            return
        }

        let payload: [String: Any] = [
            "attributionType": attribution.attributionType,
            "installId": nilToNull(attribution.installId),
            "matchFound": attribution.matchFound,
            "matchType": nilToNull(attribution.matchType),
            "shortUrl": nilToNull(attribution.shortUrl),
            "clickedShortUrl": nilToNull(attribution.clickedShortUrl),
            "destinationUrl": nilToNull(attribution.destinationUrl),
            "destinationWithUtm": nilToNull(attribution.destinationWithUtm),
            "utmSource": nilToNull(attribution.utmSource),
            "utmMedium": nilToNull(attribution.utmMedium),
            "utmCampaign": nilToNull(attribution.utmCampaign),
            "utmTerm": nilToNull(attribution.utmTerm),
            "utmContent": nilToNull(attribution.utmContent),
            "isAttributed": attribution.isAttributed
        ]
        sendSuccessEnvelope(payload)
    }
}

@_cdecl("cl_getApiKey")
public func cl_getApiKey() {
    performOnMain {
        sendSuccessEnvelope(ChottuLinkBootstrap.apiKey)
    }
}

// -------------------------------------------------------------------------------------------------
// MARK: - Delegate handling init + live deep links
// -------------------------------------------------------------------------------------------------
@MainActor
final class ChottuLinkUnity: NSObject, ChottuLinkDelegate {
    static let shared = ChottuLinkUnity()

    func chottuLink(didInitializeWith configuration: CLConfiguration) {
        ChottuLinkBootstrap.isInitialized = true
        if let pending = ChottuLinkBootstrap.pendingLaunchURL {
            ChottuLink.handleLink(pending)
            ChottuLinkBootstrap.pendingLaunchURL = nil
        }
    }

    func chottuLink(didFailToInitializeWith error: any Error) {
        ChottuLinkBootstrap.isInitialized = false
        sendErrorEnvelope(
            code: "init_failed",
            message: sdkErrorMessage(error),
            details: "initialize"
        )
    }

    func chottuLink(didResolveDeepLink link: URL, metadata: [String : Any]?) {
        let json: [String: Any] = [
            "link": link.absoluteString,
            "shortLink": (metadata?["originalURL"] as? String) ?? "",
            "shortLinkRaw": (metadata?["shortLinkRaw"] as? String) ?? "",
            "isDeferred": (metadata?["isDeferred"] as? Bool) ?? false
        ]
        
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: json)
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                sendUnity("OnDeepLink", jsonString)
            } else {
                // Fallback: send plain URL
                sendUnity("OnDeepLink", link.absoluteString)
            }
        } catch {
            // Fallback: send plain URL
            sendUnity("OnDeepLink", link.absoluteString)
        }
    }

    func chottuLink(didFailToResolveDeepLink originalURL: URL?, error: any Error) {
        sendUnity("OnError", sdkErrorMessage(error))
    }
}
