import Foundation
import OpenClawKit
import Testing

struct WatchChatStatusLocalizationTests {
    @Test func `snapshot parser keeps semantic status presentation`() throws {
        let snapshot = try #require(WatchAppSnapshotMessage.parsePayload(Self.semanticPayload()))

        #expect(snapshot.gatewayStatus.code == .gatewayProblem)
        #expect(snapshot.gatewayStatus.localizationKey == "Gateway update required")
        #expect(snapshot.gatewayStatus.verbatim == nil)
        #expect(snapshot.agentAvatarURL == "https://example.com/avatar.png")
        #expect(snapshot.talkStatus.code == .talkOff)
        #expect(snapshot.chatStatus?.code == .chatConnectIPhone)
    }

    @Test func `legacy snapshot text decodes but is not written again`() throws {
        let legacyJSON = """
        {
          "gatewayStatusText": "Ancien statut",
          "gatewayConnected": false,
          "agentName": "Main",
          "sessionKey": "main",
          "talkStatusText": "Ancien mode vocal",
          "talkEnabled": true,
          "talkListening": false,
          "talkSpeaking": false,
          "pendingApprovalCount": 0,
          "chatStatusText": "Ancien chat"
        }
        """
        let snapshot = try JSONDecoder().decode(
            WatchAppSnapshotMessage.self,
            from: Data(legacyJSON.utf8))

        #expect(snapshot.gatewayStatus.verbatim == "Ancien statut")
        #expect(snapshot.talkStatus.verbatim == "Ancien mode vocal")
        #expect(snapshot.chatStatus?.verbatim == "Ancien chat")

        let encoded = try JSONEncoder().encode(snapshot)
        let object = try #require(
            JSONSerialization.jsonObject(with: encoded) as? [String: Any])
        #expect(object["gatewayStatusText"] == nil)
        #expect(object["talkStatusText"] == nil)
        #expect(object["chatStatusText"] == nil)
        #expect(object["chatStatus"] != nil)
    }

    @Test func `watch locale resolves semantic chat status independently from phone`() throws {
        let snapshot = try #require(WatchAppSnapshotMessage.parsePayload(Self.semanticPayload()))
        let rendered = WatchAppSnapshotMessage.localizedChatStatusText(
            status: snapshot.chatStatus,
            chatCount: 0,
            hasAppSnapshot: true,
            localize: { key in
                key == .connectIPhoneChat
                    ? "Connectez le chat de l'iPhone"
                    : key.rawValue
            })

        #expect(rendered == "Connectez le chat de l'iPhone")
        #expect(rendered != "Connect iPhone chat to read messages")
    }

    @Test func `payload parser preserves legacy text for unknown semantic statuses`() throws {
        var payload = Self.semanticPayload()
        payload["gatewayStatus"] = ["code": "futureGateway"]
        payload["gatewayStatusText"] = "Future gateway state"
        payload["gatewayConnected"] = true
        payload["talkStatus"] = ["code": "futureTalk"]
        payload["talkStatusText"] = "Future talk state"
        payload["talkSpeaking"] = true

        let snapshot = try #require(WatchAppSnapshotMessage.parsePayload(payload))

        #expect(snapshot.gatewayStatus.code == .legacy)
        #expect(snapshot.gatewayStatus.verbatim == "Future gateway state")
        #expect(snapshot.talkStatus.code == .legacy)
        #expect(snapshot.talkStatus.verbatim == "Future talk state")
    }

    @Test func `persisted snapshot preserves legacy text for unknown semantic statuses`() throws {
        let json = """
        {
          "gatewayStatus": {"code": "futureGateway"},
          "gatewayStatusText": "Future gateway state",
          "gatewayConnected": true,
          "agentName": "Main",
          "sessionKey": "main",
          "talkStatus": {"code": "futureTalk"},
          "talkStatusText": "Future talk state",
          "talkEnabled": true,
          "talkListening": false,
          "talkSpeaking": true,
          "pendingApprovalCount": 0
        }
        """

        let snapshot = try JSONDecoder().decode(
            WatchAppSnapshotMessage.self,
            from: Data(json.utf8))

        #expect(snapshot.gatewayStatus.code == .legacy)
        #expect(snapshot.gatewayStatus.verbatim == "Future gateway state")
        #expect(snapshot.talkStatus.code == .legacy)
        #expect(snapshot.talkStatus.verbatim == "Future talk state")
    }

    @Test func `persisted semantic command status follows current watch locale`() {
        let status = WatchAppCommandStatus(command: .sendChat, code: .sent)
        let english = status.localizedText(localize: Self.english)
        let french = status.localizedText(localize: Self.french)

        #expect(english == "Chat: sent")
        #expect(french == "Discussion : envoyée")
        #expect(status.command == .sendChat)
    }

    @Test func `legacy approval outcomes recover semantic localization`() throws {
        let allowed = try #require(
            WatchExecApprovalOutcome.decodeLegacyLocalizedText("Approval allowed once."))
        let custom = try #require(
            WatchExecApprovalOutcome.decodeLegacyLocalizedText("Gateway custom outcome"))

        #expect(allowed.code == .allowedOnce)
        #expect(allowed.verbatim == nil)
        #expect(custom.code == .verbatim)
        #expect(custom.verbatim == "Gateway custom outcome")
    }

    @Test func `semantic approval outcomes win before legacy decision fallbacks`() {
        let parsedOutcome = WatchExecApprovalResolvedMessage.parseTransportOutcome("allowedAlways")
        let allowedAlways = WatchExecApprovalOutcome.resolved(
            outcome: parsedOutcome,
            legacyText: "Approval denied.",
            decision: .deny,
            source: "another-reviewer")
        let deniedElsewhere = WatchExecApprovalOutcome.resolved(
            outcome: nil,
            legacyText: nil,
            decision: .deny,
            source: "another-reviewer")
        let unknownElsewhere = WatchExecApprovalOutcome.resolved(
            outcome: nil,
            legacyText: nil,
            decision: nil,
            source: "another-reviewer")

        #expect(allowedAlways.code == .allowedAlways)
        #expect(deniedElsewhere.code == .denied)
        #expect(unknownElsewhere.code == .resolvedElsewhere)
        #expect(WatchExecApprovalResolvedMessage.parseTransportOutcome("future") == nil)
    }

    @Test func `gateway presentation localizes key and keeps backend override verbatim`() {
        let localized = OpenClawWatchAppStatus(
            code: .gatewayProblem,
            localizationKey: "Gateway update required")
        let backendOverride = OpenClawWatchAppStatus(
            code: .gatewayProblem,
            verbatim: "Gateway says update channel beta")
        let localizedTalkFailure = OpenClawWatchAppStatus(
            code: .talkFailure,
            localizationKey: "Paused")

        #expect(localized.localizedText(
            localizePresentation: { key, _ in
                key == "Gateway update required" ? "Mise à jour requise" : key
            }) == "Mise à jour requise")
        #expect(backendOverride.localizedText() == "Gateway says update channel beta")
        #expect(localizedTalkFailure.localizedText(
            localizePresentation: { key, _ in
                key == "Paused" ? "En pause" : key
            }) == "En pause")
    }

    private static func semanticPayload() -> [String: Any] {
        [
            "type": OpenClawWatchPayloadType.appSnapshot.rawValue,
            "gatewayStatus": [
                "code": OpenClawWatchAppStatusCode.gatewayProblem.rawValue,
                "localizationKey": "Gateway update required",
            ],
            "gatewayConnected": false,
            "agentName": "Main",
            "agentAvatarUrl": "https://example.com/avatar.png",
            "sessionKey": "main",
            "talkStatus": [
                "code": OpenClawWatchAppStatusCode.talkOff.rawValue,
            ],
            "talkEnabled": false,
            "talkListening": false,
            "talkSpeaking": false,
            "pendingApprovalCount": 0,
            "chatStatus": [
                "code": OpenClawWatchAppStatusCode.chatConnectIPhone.rawValue,
            ],
        ]
    }

    private static func english(_ key: WatchStatusLocalizationKey) -> String {
        switch key {
        case .chat:
            "Chat"
        case .sentFormat:
            "%@: sent"
        default:
            key.rawValue
        }
    }

    private static func french(_ key: WatchStatusLocalizationKey) -> String {
        switch key {
        case .chat:
            "Discussion"
        case .sentFormat:
            "%@ : envoyée"
        default:
            key.rawValue
        }
    }
}
