import Foundation
import OpenClawKit
import Testing
@testable import OpenClaw

struct MacNodeModeCoordinatorTests {
    @Test @MainActor func `fresh node uses durable dedicated identity for local auto approval`() throws {
        let defaults = try #require(UserDefaults(suiteName: "MacNodeModeCoordinatorTests.fresh.\(UUID().uuidString)"))

        #expect(MacNodeModeCoordinator.resolveNodeIdentityProfile(
            defaults: defaults,
            isExistingInstallation: false) == .node)
        #expect(MacNodeModeCoordinator.resolveNodeIdentityProfile(
            defaults: defaults,
            isExistingInstallation: true) == .node)
    }

    @Test @MainActor func `upgraded node durably preserves its shipped primary identity`() throws {
        let defaults = try #require(UserDefaults(suiteName: "MacNodeModeCoordinatorTests.upgrade.\(UUID().uuidString)"))

        #expect(MacNodeModeCoordinator.resolveNodeIdentityProfile(
            defaults: defaults,
            isExistingInstallation: true) == .primary)
        #expect(MacNodeModeCoordinator.resolveNodeIdentityProfile(
            defaults: defaults,
            isExistingInstallation: false) == .primary)
    }

    @Test func `remote mode does not advertise browser proxy`() {
        let caps = MacNodeModeCoordinator.resolvedCaps(
            browserControlEnabled: true,
            cameraEnabled: false,
            locationMode: .off,
            connectionMode: .remote)
        let commands = MacNodeModeCoordinator.resolvedCommands(caps: caps)

        #expect(!caps.contains(OpenClawCapability.browser.rawValue))
        #expect(!commands.contains(OpenClawBrowserCommand.proxy.rawValue))
        #expect(commands.contains(OpenClawCanvasCommand.present.rawValue))
        #expect(commands.contains(OpenClawSystemCommand.notify.rawValue))
    }

    @Test func `local mode advertises browser proxy when enabled`() {
        let caps = MacNodeModeCoordinator.resolvedCaps(
            browserControlEnabled: true,
            cameraEnabled: false,
            locationMode: .off,
            connectionMode: .local)
        let commands = MacNodeModeCoordinator.resolvedCommands(caps: caps)

        #expect(caps.contains(OpenClawCapability.browser.rawValue))
        #expect(commands.contains(OpenClawBrowserCommand.proxy.rawValue))
    }

    @Test func `tls pin store key uses default wss port`() throws {
        let url = try #require(URL(string: "wss://gateway.example.ts.net"))
        #expect(MacNodeModeCoordinator.tlsPinStoreKey(for: url) == "gateway.example.ts.net:443")
    }

    @Test func `remote tls params prefer configured fingerprint over stored pin`() throws {
        let url = try #require(URL(string: "wss://gateway.example.com"))
        let root: [String: Any] = [
            "gateway": [
                "remote": [
                    "tlsFingerprint": "sha256:configured",
                ],
            ],
        ]

        let params = try #require(MacNodeModeCoordinator.tlsParams(
            for: url,
            connectionMode: .remote,
            root: root,
            storedFingerprint: "stored"))

        #expect(params.expectedFingerprint == "sha256:configured")
        #expect(params.allowTOFU == false)
        #expect(params.storeKey == "gateway.example.com:443")
    }

    @Test func `remote tls params allow first use only when no configured or stored pin exists`() throws {
        let url = try #require(URL(string: "wss://gateway.example.com"))

        let params = try #require(MacNodeModeCoordinator.tlsParams(
            for: url,
            connectionMode: .remote,
            root: [:],
            storedFingerprint: nil))

        #expect(params.expectedFingerprint == nil)
        #expect(params.allowTOFU == true)
    }

    @Test func `local tls params ignore remote configured fingerprint`() throws {
        let url = try #require(URL(string: "wss://127.0.0.1:18789"))
        let root: [String: Any] = [
            "gateway": [
                "remote": [
                    "tlsFingerprint": "sha256:remote",
                ],
            ],
        ]

        let params = try #require(MacNodeModeCoordinator.tlsParams(
            for: url,
            connectionMode: .local,
            root: root,
            storedFingerprint: "stored-local"))

        #expect(params.expectedFingerprint == "stored-local")
        #expect(params.allowTOFU == false)
    }

    @Test func `tls session cache reuses session box for unchanged params`() throws {
        let url = try #require(URL(string: "wss://gateway.example.com"))
        var cache = MacNodeGatewayTLSSessionCache()
        let params = try #require(MacNodeModeCoordinator.tlsParams(
            for: url,
            connectionMode: .remote,
            root: ["gateway": ["remote": ["tlsFingerprint": "sha256:configured"]]],
            storedFingerprint: "stored"))

        let first = cache.sessionBox(url: url, params: params)
        let second = cache.sessionBox(url: url, params: params)

        #expect(ObjectIdentifier(first.session) == ObjectIdentifier(second.session))
    }

    @Test func `tls session cache rebuilds session box when params change`() throws {
        let url = try #require(URL(string: "wss://gateway.example.com"))
        var cache = MacNodeGatewayTLSSessionCache()
        let firstParams = try #require(MacNodeModeCoordinator.tlsParams(
            for: url,
            connectionMode: .remote,
            root: ["gateway": ["remote": ["tlsFingerprint": "sha256:configured"]]],
            storedFingerprint: "stored"))
        let secondParams = try #require(MacNodeModeCoordinator.tlsParams(
            for: url,
            connectionMode: .remote,
            root: ["gateway": ["remote": ["tlsFingerprint": "sha256:rotated"]]],
            storedFingerprint: "stored"))

        let first = cache.sessionBox(url: url, params: firstParams)
        let second = cache.sessionBox(url: url, params: secondParams)

        #expect(ObjectIdentifier(first.session) != ObjectIdentifier(second.session))
    }

    @Test func `auto repairs trusted tailscale serve pin mismatch`() throws {
        let url = try #require(URL(string: "wss://gateway.example.ts.net"))
        let failure = GatewayTLSValidationFailure(
            kind: .pinMismatch,
            host: "gateway.example.ts.net",
            storeKey: "gateway.example.ts.net:443",
            expectedFingerprint: "old",
            observedFingerprint: "new",
            systemTrustOk: true)

        #expect(MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
    }

    @Test func `does not auto repair untrusted remote pin mismatch`() throws {
        let url = try #require(URL(string: "wss://gateway.example.com"))
        let failure = GatewayTLSValidationFailure(
            kind: .pinMismatch,
            host: "gateway.example.com",
            storeKey: "gateway.example.com:443",
            expectedFingerprint: "old",
            observedFingerprint: "new",
            systemTrustOk: true)

        #expect(!MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
    }

    @Test func `auto repairs trusted loopback pin mismatch`() throws {
        let url = try #require(URL(string: "wss://127.0.0.1:18789"))
        let failure = GatewayTLSValidationFailure(
            kind: .pinMismatch,
            host: "127.0.0.1",
            storeKey: "127.0.0.1:18789",
            expectedFingerprint: "old",
            observedFingerprint: "new",
            systemTrustOk: true)

        #expect(MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
    }

    @Test func `does not auto repair untrusted loopback pin mismatch`() throws {
        let url = try #require(URL(string: "wss://127.0.0.1:18789"))
        let failure = GatewayTLSValidationFailure(
            kind: .pinMismatch,
            host: "127.0.0.1",
            storeKey: "127.0.0.1:18789",
            expectedFingerprint: "old",
            observedFingerprint: "new",
            systemTrustOk: false)

        #expect(!MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
    }
}
