import Foundation
import WatchConnectivity

struct WatchReplyDraft: Sendable {
    var replyId: String
    var promptId: String
    var actionId: String
    var actionLabel: String?
    var sessionKey: String?
    var note: String?
    var sentAtMs: Int
}

struct WatchReplySendResult: Sendable, Equatable {
    var deliveredImmediately: Bool
    var queuedForDelivery: Bool
    var transport: String
    var errorMessage: String?
}

final class WatchConnectivityReceiver: NSObject, @unchecked Sendable {
    private let store: WatchInboxStore
    private let session: WCSession?

    init(store: WatchInboxStore) {
        self.store = store
        if WCSession.isSupported() {
            self.session = WCSession.default
        } else {
            self.session = nil
        }
        super.init()
    }

    func activate() {
        guard let session = self.session else { return }
        session.delegate = self
        session.activate()
    }

    private func ensureActivated() async {
        guard let session = self.session else { return }
        if session.activationState == .activated {
            return
        }
        session.activate()
        for _ in 0..<8 {
            if session.activationState == .activated {
                return
            }
            try? await Task.sleep(nanoseconds: 100_000_000)
        }
    }

    func requestExecApprovalSnapshot() async {
        await self.ensureActivated()
        guard let session = self.session else { return }
        let request = WatchExecApprovalSnapshotRequestMessage(
            requestId: UUID().uuidString,
            sentAtMs: Self.nowMs())
        let payload = Self.encodeSnapshotRequestPayload(request)
        if session.isReachable {
            do {
                try await withCheckedThrowingContinuation(isolation: nil) {
                    (continuation: CheckedContinuation<Void, Error>) in
                    session.sendMessage(payload, replyHandler: { _ in
                        continuation.resume(returning: ())
                    }, errorHandler: { error in
                        continuation.resume(throwing: error)
                    })
                }
                return
            } catch {
                // Fall through to queued delivery.
            }
        }
        _ = session.transferUserInfo(payload)
    }

    func sendReply(_ draft: WatchReplyDraft) async -> WatchReplySendResult {
        await self.ensureActivated()
        guard let session = self.session else {
            return WatchReplySendResult(
                deliveredImmediately: false,
                queuedForDelivery: false,
                transport: "none",
                errorMessage: "watch session unavailable")
        }

        var payload: [String: Any] = [
            "type": WatchPayloadType.reply.rawValue,
            "replyId": draft.replyId,
            "promptId": draft.promptId,
            "actionId": draft.actionId,
            "sentAtMs": draft.sentAtMs,
        ]
        if let actionLabel = draft.actionLabel?.trimmingCharacters(in: .whitespacesAndNewlines),
           !actionLabel.isEmpty
        {
            payload["actionLabel"] = actionLabel
        }
        if let sessionKey = draft.sessionKey?.trimmingCharacters(in: .whitespacesAndNewlines),
           !sessionKey.isEmpty
        {
            payload["sessionKey"] = sessionKey
        }
        if let note = draft.note?.trimmingCharacters(in: .whitespacesAndNewlines), !note.isEmpty {
            payload["note"] = note
        }

        return await self.sendPayload(payload, session: session)
    }

    func sendExecApprovalResolve(
        approvalId: String,
        decision: WatchExecApprovalDecision) async -> WatchReplySendResult
    {
        await self.ensureActivated()
        guard let session = self.session else {
            return WatchReplySendResult(
                deliveredImmediately: false,
                queuedForDelivery: false,
                transport: "none",
                errorMessage: "watch session unavailable")
        }

        let payload = Self.encodeExecApprovalResolvePayload(
            WatchExecApprovalResolveMessage(
                approvalId: approvalId,
                decision: decision,
                replyId: UUID().uuidString,
                sentAtMs: Self.nowMs()))
        return await self.sendPayload(payload, session: session)
    }

    private func sendPayload(_ payload: [String: Any], session: WCSession) async -> WatchReplySendResult {
        if session.isReachable {
            do {
                try await withCheckedThrowingContinuation(isolation: nil) {
                    (continuation: CheckedContinuation<Void, Error>) in
                    session.sendMessage(payload, replyHandler: { _ in
                        continuation.resume(returning: ())
                    }, errorHandler: { error in
                        continuation.resume(throwing: error)
                    })
                }
                return WatchReplySendResult(
                    deliveredImmediately: true,
                    queuedForDelivery: false,
                    transport: "sendMessage",
                    errorMessage: nil)
            } catch {
                // Fall through to queued delivery below.
            }
        }

        _ = session.transferUserInfo(payload)
        return WatchReplySendResult(
            deliveredImmediately: false,
            queuedForDelivery: true,
            transport: "transferUserInfo",
            errorMessage: nil)
    }

    private static func nowMs() -> Int {
        Int(Date().timeIntervalSince1970 * 1000)
    }

    private static func normalizeObject(_ value: Any) -> [String: Any]? {
        if let object = value as? [String: Any] {
            return object
        }
        if let object = value as? [AnyHashable: Any] {
            var normalized: [String: Any] = [:]
            normalized.reserveCapacity(object.count)
            for (key, item) in object {
                guard let stringKey = key as? String else {
                    continue
                }
                normalized[stringKey] = item
            }
            return normalized
        }
        return nil
    }

    private static func parseActions(_ value: Any?) -> [WatchPromptAction] {
        guard let raw = value as? [Any] else {
            return []
        }
        return raw.compactMap { item in
            guard let obj = Self.normalizeObject(item) else {
                return nil
            }
            let id = (obj["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
            let label = (obj["label"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
            guard !id.isEmpty, !label.isEmpty else {
                return nil
            }
            let style = (obj["style"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
            return WatchPromptAction(id: id, label: label, style: style)
        }
    }

    private static func parseNotificationPayload(_ payload: [String: Any]) -> WatchNotifyMessage? {
        guard let type = payload["type"] as? String,
              type == WatchPayloadType.notify.rawValue
        else {
            return nil
        }

        let title = (payload["title"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        let body = (payload["body"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

        guard title.isEmpty == false || body.isEmpty == false else {
            return nil
        }

        let id = (payload["id"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let sentAtMs = (payload["sentAtMs"] as? Int) ?? (payload["sentAtMs"] as? NSNumber)?.intValue
        let promptId = (payload["promptId"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let sessionKey = (payload["sessionKey"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let kind = (payload["kind"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let details = (payload["details"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let expiresAtMs = (payload["expiresAtMs"] as? Int) ?? (payload["expiresAtMs"] as? NSNumber)?.intValue
        let risk = (payload["risk"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let actions = Self.parseActions(payload["actions"])

        return WatchNotifyMessage(
            id: id,
            title: title,
            body: body,
            sentAtMs: sentAtMs,
            promptId: promptId,
            sessionKey: sessionKey,
            kind: kind,
            details: details,
            expiresAtMs: expiresAtMs,
            risk: risk,
            actions: actions)
    }

    private static func parseExecApprovalDecision(_ value: Any?) -> WatchExecApprovalDecision? {
        let raw = (value as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        return WatchExecApprovalDecision(rawValue: raw)
    }

    private static func parseExecApprovalItem(_ value: Any?) -> WatchExecApprovalItem? {
        guard let payload = value.flatMap(Self.normalizeObject) else {
            return nil
        }
        let id = (payload["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        let commandText = (payload["commandText"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        guard !id.isEmpty, !commandText.isEmpty else {
            return nil
        }
        let commandPreview = (payload["commandPreview"] as? String)?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let host = (payload["host"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        let nodeId = (payload["nodeId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        let agentId = (payload["agentId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        let expiresAtMs = (payload["expiresAtMs"] as? Int) ?? (payload["expiresAtMs"] as? NSNumber)?.intValue
        let riskRaw = (payload["risk"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        let risk = WatchRiskLevel(rawValue: riskRaw)
        let allowedDecisions = (payload["allowedDecisions"] as? [Any] ?? []).compactMap {
            Self.parseExecApprovalDecision($0)
        }
        return WatchExecApprovalItem(
            id: id,
            commandText: commandText,
            commandPreview: commandPreview,
            host: host,
            nodeId: nodeId,
            agentId: agentId,
            expiresAtMs: expiresAtMs,
            allowedDecisions: allowedDecisions,
            risk: risk)
    }

    private static func parseExecApprovalPromptPayload(
        _ payload: [String: Any]) -> WatchExecApprovalPromptMessage?
    {
        guard let type = payload["type"] as? String,
              type == WatchPayloadType.execApprovalPrompt.rawValue,
              let approval = Self.parseExecApprovalItem(payload["approval"])
        else {
            return nil
        }
        let sentAtMs = (payload["sentAtMs"] as? Int) ?? (payload["sentAtMs"] as? NSNumber)?.intValue
        let deliveryId = (payload["deliveryId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        let resetResolvingState = payload["resetResolvingState"] as? Bool
        return WatchExecApprovalPromptMessage(
            approval: approval,
            sentAtMs: sentAtMs,
            deliveryId: deliveryId,
            resetResolvingState: resetResolvingState)
    }

    private static func parseExecApprovalResolvedPayload(
        _ payload: [String: Any]) -> WatchExecApprovalResolvedMessage?
    {
        guard let type = payload["type"] as? String,
              type == WatchPayloadType.execApprovalResolved.rawValue
        else {
            return nil
        }
        let approvalId = (payload["approvalId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        guard !approvalId.isEmpty else { return nil }
        let decision = Self.parseExecApprovalDecision(payload["decision"])
        let resolvedAtMs = (payload["resolvedAtMs"] as? Int)
            ?? (payload["resolvedAtMs"] as? NSNumber)?.intValue
        let source = (payload["source"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        return WatchExecApprovalResolvedMessage(
            approvalId: approvalId,
            decision: decision,
            resolvedAtMs: resolvedAtMs,
            source: source)
    }

    private static func parseExecApprovalExpiredPayload(
        _ payload: [String: Any]) -> WatchExecApprovalExpiredMessage?
    {
        guard let type = payload["type"] as? String,
              type == WatchPayloadType.execApprovalExpired.rawValue
        else {
            return nil
        }
        let approvalId = (payload["approvalId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        let rawReason = (payload["reason"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        guard !approvalId.isEmpty,
              let reason = WatchExecApprovalCloseReason(rawValue: rawReason)
        else {
            return nil
        }
        let expiredAtMs = (payload["expiredAtMs"] as? Int) ?? (payload["expiredAtMs"] as? NSNumber)?.intValue
        return WatchExecApprovalExpiredMessage(
            approvalId: approvalId,
            reason: reason,
            expiredAtMs: expiredAtMs)
    }

    private static func parseExecApprovalSnapshotPayload(
        _ payload: [String: Any]) -> WatchExecApprovalSnapshotMessage?
    {
        guard let type = payload["type"] as? String,
              type == WatchPayloadType.execApprovalSnapshot.rawValue
        else {
            return nil
        }
        let approvals = (payload["approvals"] as? [Any] ?? []).compactMap { item in
            Self.parseExecApprovalItem(item)
        }
        let sentAtMs = (payload["sentAtMs"] as? Int) ?? (payload["sentAtMs"] as? NSNumber)?.intValue
        let snapshotId = (payload["snapshotId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
        return WatchExecApprovalSnapshotMessage(
            approvals: approvals,
            sentAtMs: sentAtMs,
            snapshotId: snapshotId)
    }

    private static func encodeSnapshotRequestPayload(
        _ request: WatchExecApprovalSnapshotRequestMessage) -> [String: Any]
    {
        var payload: [String: Any] = [
            "type": WatchPayloadType.execApprovalSnapshotRequest.rawValue,
            "requestId": request.requestId,
        ]
        if let sentAtMs = request.sentAtMs {
            payload["sentAtMs"] = sentAtMs
        }
        return payload
    }

    private static func encodeExecApprovalResolvePayload(
        _ message: WatchExecApprovalResolveMessage) -> [String: Any]
    {
        var payload: [String: Any] = [
            "type": WatchPayloadType.execApprovalResolve.rawValue,
            "approvalId": message.approvalId,
            "decision": message.decision.rawValue,
            "replyId": message.replyId,
        ]
        if let sentAtMs = message.sentAtMs {
            payload["sentAtMs"] = sentAtMs
        }
        return payload
    }
}

extension WatchConnectivityReceiver: WCSessionDelegate {
    func session(
        _: WCSession,
        activationDidCompleteWith _: WCSessionActivationState,
        error _: (any Error)?)
    {
        Task {
            await self.requestExecApprovalSnapshot()
        }
    }

    func session(_: WCSession, didReceiveMessage message: [String: Any]) {
        self.consumeIncomingPayload(message, transport: "sendMessage")
    }

    func session(
        _: WCSession,
        didReceiveMessage message: [String: Any],
        replyHandler: @escaping ([String: Any]) -> Void)
    {
        replyHandler(["ok": true])
        self.consumeIncomingPayload(message, transport: "sendMessage")
    }

    func session(_: WCSession, didReceiveUserInfo userInfo: [String: Any]) {
        self.consumeIncomingPayload(userInfo, transport: "transferUserInfo")
    }

    func session(_: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
        self.consumeIncomingPayload(applicationContext, transport: "applicationContext")
    }

    private func consumeIncomingPayload(_ payload: [String: Any], transport: String) {
        if let incoming = Self.parseNotificationPayload(payload) {
            Task { @MainActor in
                self.store.consume(message: incoming, transport: transport)
            }
            return
        }
        if let prompt = Self.parseExecApprovalPromptPayload(payload) {
            Task { @MainActor in
                self.store.consume(execApprovalPrompt: prompt, transport: transport)
            }
            return
        }
        if let resolved = Self.parseExecApprovalResolvedPayload(payload) {
            Task { @MainActor in
                self.store.consume(execApprovalResolved: resolved)
            }
            return
        }
        if let expired = Self.parseExecApprovalExpiredPayload(payload) {
            Task { @MainActor in
                self.store.consume(execApprovalExpired: expired)
            }
            return
        }
        if let snapshot = Self.parseExecApprovalSnapshotPayload(payload) {
            Task { @MainActor in
                self.store.consume(execApprovalSnapshot: snapshot, transport: transport)
            }
        }
    }
}
