import Foundation
import Testing

@testable import NucleicCore

/// Locks the newest-wins write-back rule that keeps the sandbox's Claude login coherent with the
/// Keychain (LOGIN SYNC). Only the I/O-free core is exercised here — the real Keychain read/write
/// would touch the developer's login item, so `ClaudeLoginKeychain` splits the decision
/// (`shouldWriteBack`) and the parsing (`expiresAt`/`isValidLogin`) out for exactly this.
struct ClaudeLoginKeychainTests {
    /// A credentials-file JSON like the one `claude` writes, with the given expiry and tokens.
    private static func login(expiresAt: Double, access: String = "acc", refresh: String = "ref") -> String {
        """
        {"claudeAiOauth":{"accessToken":"\(access)","refreshToken":"\(refresh)",\
        "expiresAt":\(Int(expiresAt)),"scopes":["user:inference"],"subscriptionType":"max"}}
        """
    }

    // MARK: - Test-process guard

    /// The test bootstrap must disarm all real Keychain I/O: run headless, the login item's ACL
    /// prompt can't be shown and `SecItemCopyMatching` blocks forever, hanging the whole suite.
    /// `.isolatedContainerSettings` installs the process-wide guards; this pins that wiring.
    @Test(.isolatedContainerSettings) func testProcessDisablesRealKeychainIO() {
        #expect(ClaudeLoginKeychain.disabled)
    }

    // MARK: - Parsing

    @Test func parsesExpiresAtAndValidatesLogin() {
        let json = Self.login(expiresAt: 1_700_000_000_000)
        #expect(ClaudeLoginKeychain.expiresAt(json) == 1_700_000_000_000)
        #expect(ClaudeLoginKeychain.isValidLogin(json))
    }

    @Test func expiresAtToleratesNumericString() {
        let json = """
        {"claudeAiOauth":{"accessToken":"a","refreshToken":"r","expiresAt":"1700000000000"}}
        """
        #expect(ClaudeLoginKeychain.expiresAt(json) == 1_700_000_000_000)
    }

    @Test func invalidLoginsAreRejected() {
        // Missing refresh token, empty access token, wrong shape, and junk all fail validation.
        #expect(!ClaudeLoginKeychain.isValidLogin(#"{"claudeAiOauth":{"accessToken":"a"}}"#))
        #expect(!ClaudeLoginKeychain.isValidLogin(#"{"claudeAiOauth":{"accessToken":"","refreshToken":"r"}}"#))
        #expect(!ClaudeLoginKeychain.isValidLogin(#"{"other":{}}"#))
        #expect(!ClaudeLoginKeychain.isValidLogin("not json"))
        #expect(ClaudeLoginKeychain.expiresAt("not json") == nil)
    }

    // MARK: - Newest-wins decision

    @Test func writesBackWhenCandidateIsStrictlyNewer() {
        let current = Self.login(expiresAt: 1_000, refresh: "old")
        let candidate = Self.login(expiresAt: 2_000, refresh: "rotated")
        #expect(ClaudeLoginKeychain.shouldWriteBack(candidate: candidate, current: current))
    }

    @Test func skipsWhenCandidateIsOlderOrEqual() {
        let current = Self.login(expiresAt: 2_000)
        #expect(!ClaudeLoginKeychain.shouldWriteBack(candidate: Self.login(expiresAt: 1_000), current: current))
        // Equal expiry is not newer — a re-export of the same login must not thrash the Keychain.
        #expect(!ClaudeLoginKeychain.shouldWriteBack(candidate: Self.login(expiresAt: 2_000), current: current))
    }

    @Test func skipsIdenticalCredential() {
        let same = Self.login(expiresAt: 2_000)
        #expect(!ClaudeLoginKeychain.shouldWriteBack(candidate: same, current: same))
    }

    @Test func skipsInvalidCandidate() {
        let current = Self.login(expiresAt: 1_000)
        // A truncated/half-written file must never be pushed back, even though it "looks newer".
        #expect(!ClaudeLoginKeychain.shouldWriteBack(candidate: #"{"claudeAiOauth":{"accessToken":"a"}}"#, current: current))
    }

    @Test func skipsWhenCurrentIsUnreadableOrUnparseable() {
        let candidate = Self.login(expiresAt: 2_000)
        // A nil current (denied read / logged-out slot) or an unparseable one means we can't confirm
        // the candidate is newer — so we never downgrade or fabricate the Keychain.
        #expect(!ClaudeLoginKeychain.shouldWriteBack(candidate: candidate, current: nil))
        #expect(!ClaudeLoginKeychain.shouldWriteBack(candidate: candidate, current: "not json"))
    }
}
