import Foundation

#if DEBUG
extension ClaudeOAuthCredentialsStore {
    /// Mirrors the production ownership decision without installing a synthetic credential fixture.
    static var directClaudeCodeKeychainAccessAllowedForTesting: Bool {
        self.keychainAccessAllowed
    }

    @TaskLocal static var taskBeforeClaudeKeychainPromptLockOverride: (@Sendable () -> Void)?
    @TaskLocal static var taskInteractiveClaudeKeychainReadOverride: (@Sendable () throws -> Data)?

    static func withInteractiveClaudeKeychainReadOverridesForTesting<T>(
        beforePromptLock: (@Sendable () -> Void)? = nil,
        read: (@Sendable () throws -> Data)? = nil,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskBeforeClaudeKeychainPromptLockOverride.withValue(beforePromptLock) {
            try await self.$taskInteractiveClaudeKeychainReadOverride.withValue(read) {
                try await operation()
            }
        }
    }

    @TaskLocal static var taskClaudeKeychainDataOverride: Data?
    @TaskLocal static var taskClaudeKeychainFingerprintOverride: ClaudeKeychainFingerprint?
    @TaskLocal static var taskMemoryCacheStoreOverride: MemoryCacheStore?
    @TaskLocal static var taskClaudeKeychainFingerprintStoreOverride: ClaudeKeychainFingerprintStore?
    @TaskLocal static var taskPendingCacheClearStoreOverride: ClaudeOAuthPendingCacheClearStore?
    @TaskLocal static var taskImplicitPendingCacheClearStoreOverride: ClaudeOAuthPendingCacheClearStore?
    @TaskLocal static var taskDirectKeychainReadConsentRevocationMarkerStoreOverride:
        DirectKeychainReadConsentRevocationMarkerStore?
    @TaskLocal static var taskUseEnvironmentCredentialsURLForTesting = false

    typealias OAuthCacheOperation = KeychainCacheStore.Operation
    typealias OAuthCacheOperationRecorder = KeychainCacheStore.OperationRecorder

    final class PendingCacheClearMemoryStore: ClaudeOAuthPendingCacheClearStore, @unchecked Sendable {
        private let lock = NSLock()
        private var unscopedPending: Bool
        private var pendingProfileIdentifiers: Set<String> = []
        private var legacyCleanupProfileIdentifiers: Set<String> = []
        private var legacyRecheckProfileIdentifiers: Set<String> = []

        init(isPending: Bool = false) {
            self.unscopedPending = isPending
        }

        var isPending: Bool {
            self.lock.lock()
            defer { self.lock.unlock() }
            return self.unscopedPending ||
                !self.pendingProfileIdentifiers.isEmpty ||
                !self.legacyCleanupProfileIdentifiers.isEmpty ||
                !self.legacyRecheckProfileIdentifiers.isEmpty
        }

        func markPending() {
            self.lock.lock()
            self.unscopedPending = true
            self.lock.unlock()
        }

        func withCacheTransaction(_ operation: (inout Bool) -> Void) {
            self.lock.lock()
            defer { self.lock.unlock() }
            operation(&self.unscopedPending)
        }

        func isPending(profileIdentifier: String) -> Bool {
            self.lock.lock()
            defer { self.lock.unlock() }
            return self.unscopedPending ||
                self.pendingProfileIdentifiers.contains(profileIdentifier) ||
                self.legacyCleanupProfileIdentifiers.contains(profileIdentifier) ||
                self.legacyRecheckProfileIdentifiers.contains(profileIdentifier)
        }

        func markPending(profileIdentifier: String) {
            self.lock.lock()
            self.pendingProfileIdentifiers.insert(profileIdentifier)
            self.lock.unlock()
        }

        func withCacheTransaction(profileIdentifier: String, _ operation: (inout Bool) -> Void) {
            self.withCacheTransaction(
                profileIdentifier: profileIdentifier,
                includingLegacyCleanup: { profilePending, legacyCleanupPending in
                    var pending = profilePending || legacyCleanupPending
                    operation(&pending)
                    if pending {
                        if !profilePending, !legacyCleanupPending {
                            profilePending = true
                        }
                    } else {
                        profilePending = false
                        legacyCleanupPending = false
                    }
                })
        }

        func withCacheTransaction(
            profileIdentifier: String,
            includingLegacyCleanup operation: (inout Bool, inout Bool) -> Void)
        {
            self.withCacheTransaction(
                profileIdentifier: profileIdentifier,
                includingLegacyState: { profilePending, legacyCleanupPending, legacyRecheckPending in
                    operation(&profilePending, &legacyCleanupPending)
                    if legacyCleanupPending {
                        legacyRecheckPending = false
                    }
                })
        }

        func withCacheTransaction(
            profileIdentifier: String,
            includingLegacyState operation: (inout Bool, inout Bool, inout Bool) -> Void)
        {
            self.lock.lock()
            defer { self.lock.unlock() }
            var profilePending = self.unscopedPending ||
                self.pendingProfileIdentifiers.contains(profileIdentifier)
            var legacyCleanupPending = self.unscopedPending ||
                self.legacyCleanupProfileIdentifiers.contains(profileIdentifier)
            var legacyRecheckPending = self.legacyRecheckProfileIdentifiers.contains(profileIdentifier)
            operation(&profilePending, &legacyCleanupPending, &legacyRecheckPending)
            self.unscopedPending = false
            if profilePending {
                self.pendingProfileIdentifiers.insert(profileIdentifier)
            } else {
                self.pendingProfileIdentifiers.remove(profileIdentifier)
            }
            if legacyCleanupPending {
                self.legacyCleanupProfileIdentifiers.insert(profileIdentifier)
            } else {
                self.legacyCleanupProfileIdentifiers.remove(profileIdentifier)
            }
            if legacyRecheckPending {
                self.legacyRecheckProfileIdentifiers.insert(profileIdentifier)
            } else {
                self.legacyRecheckProfileIdentifiers.remove(profileIdentifier)
            }
        }
    }

    final class ClaudeKeychainFingerprintStore: @unchecked Sendable {
        var fingerprint: ClaudeKeychainFingerprint?

        init(fingerprint: ClaudeKeychainFingerprint? = nil) {
            self.fingerprint = fingerprint
        }
    }

    final class MemoryCacheStore: @unchecked Sendable {
        var record: ClaudeOAuthCredentialRecord?
        var timestamp: Date?
        var profileIdentifier: String?
    }

    final class DirectKeychainReadConsentRevocationMarkerStore: @unchecked Sendable {
        private let lock = NSLock()
        private var value: String?

        var marker: String? {
            self.lock.withLock { self.value }
        }

        func advance() {
            self.lock.withLock { self.value = UUID().uuidString }
        }
    }

    static func withDirectKeychainReadConsentRevocationMarkerStoreForTesting<T>(
        _ store: DirectKeychainReadConsentRevocationMarkerStore,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskDirectKeychainReadConsentRevocationMarkerStoreOverride.withValue(store) {
            try operation()
        }
    }

    static func withClaudeKeychainOverridesForTesting<T>(
        data: Data?,
        fingerprint: ClaudeKeychainFingerprint?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskClaudeKeychainDataOverride.withValue(data) {
            try self.$taskClaudeKeychainFingerprintOverride.withValue(fingerprint) {
                try operation()
            }
        }
    }

    static func withClaudeKeychainOverridesForTesting<T>(
        data: Data?,
        fingerprint: ClaudeKeychainFingerprint?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskClaudeKeychainDataOverride.withValue(data) {
            try await self.$taskClaudeKeychainFingerprintOverride.withValue(fingerprint) {
                try await operation()
            }
        }
    }

    static func withClaudeKeychainFingerprintStoreOverrideForTesting<T>(
        _ store: ClaudeKeychainFingerprintStore?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskClaudeKeychainFingerprintStoreOverride.withValue(store) {
            try operation()
        }
    }

    static func withClaudeKeychainFingerprintStoreOverrideForTesting<T>(
        _ store: ClaudeKeychainFingerprintStore?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskClaudeKeychainFingerprintStoreOverride.withValue(store) {
            try await operation()
        }
    }

    static func withIsolatedMemoryCacheForTesting<T>(operation: () throws -> T) rethrows -> T {
        let store = MemoryCacheStore()
        let preAlertStore = ClaudeOAuthKeychainPreAlertGate.StateStore()
        return try ClaudeOAuthKeychainPreAlertGate.withStateStoreOverrideForTesting(preAlertStore) {
            try self.$taskMemoryCacheStoreOverride.withValue(store) {
                try self.withAutoIsolatedPendingCacheClearStoreIfNeededForTesting {
                    try operation()
                }
            }
        }
    }

    static func withIsolatedMemoryCacheForTesting<T>(operation: () async throws -> T) async rethrows -> T {
        let store = MemoryCacheStore()
        let preAlertStore = ClaudeOAuthKeychainPreAlertGate.StateStore()
        return try await ClaudeOAuthKeychainPreAlertGate.withStateStoreOverrideForTesting(preAlertStore) {
            try await self.$taskMemoryCacheStoreOverride.withValue(store) {
                try await self.withAutoIsolatedPendingCacheClearStoreIfNeededForTesting {
                    try await operation()
                }
            }
        }
    }

    private static func withAutoIsolatedPendingCacheClearStoreIfNeededForTesting<T>(
        operation: () throws -> T) rethrows -> T
    {
        if self.taskPendingCacheClearStoreOverride != nil {
            return try operation()
        }
        let pendingStore = PendingCacheClearMemoryStore()
        return try self.$taskPendingCacheClearStoreOverride.withValue(pendingStore) {
            try operation()
        }
    }

    private static func withAutoIsolatedPendingCacheClearStoreIfNeededForTesting<T>(
        operation: () async throws -> T) async rethrows -> T
    {
        if self.taskPendingCacheClearStoreOverride != nil {
            return try await operation()
        }
        let pendingStore = PendingCacheClearMemoryStore()
        return try await self.$taskPendingCacheClearStoreOverride.withValue(pendingStore) {
            try await operation()
        }
    }

    final class CredentialsFileFingerprintStore: @unchecked Sendable {
        private var fingerprints: [String: CredentialsFileFingerprint] = [:]
        private var quarantines: [String: CredentialsFileFingerprint] = [:]
        private var legacyFingerprint: CredentialsFileFingerprint?

        init(fingerprint: CredentialsFileFingerprint? = nil) {
            self.legacyFingerprint = fingerprint
        }

        func load(
            profileIdentifier: String,
            historicalProfileIdentifier: String) -> CredentialsFileFingerprint?
        {
            if let fingerprint = self.fingerprints[profileIdentifier] {
                if profileIdentifier == historicalProfileIdentifier {
                    self.legacyFingerprint = nil
                }
                return fingerprint
            }
            guard profileIdentifier == historicalProfileIdentifier,
                  let fingerprint = self.legacyFingerprint
            else {
                return nil
            }
            self.fingerprints[profileIdentifier] = fingerprint
            self.legacyFingerprint = nil
            return fingerprint
        }

        func save(
            _ fingerprint: CredentialsFileFingerprint?,
            profileIdentifier: String,
            historicalProfileIdentifier: String)
        {
            if profileIdentifier == historicalProfileIdentifier {
                self.legacyFingerprint = nil
            }
            self.fingerprints[profileIdentifier] = fingerprint
        }

        func loadQuarantine(profileIdentifier: String) -> CredentialsFileFingerprint? {
            self.quarantines[profileIdentifier]
        }

        func saveQuarantine(_ fingerprint: CredentialsFileFingerprint?, profileIdentifier: String) {
            self.quarantines[profileIdentifier] = fingerprint
        }

        func reset() {
            self.fingerprints.removeAll()
            self.quarantines.removeAll()
            self.legacyFingerprint = nil
        }
    }

    enum SecurityCLIReadOverride {
        case data(Data?)
        case timedOut
        case nonZeroExit
        case dynamic(@Sendable (SecurityCLIReadRequest) -> Data?)
    }

    @TaskLocal static var taskKeychainAccessOverride: Bool?
    @TaskLocal static var taskCredentialsFileFingerprintStoreOverride: CredentialsFileFingerprintStore?
    @TaskLocal static var taskSecurityCLIReadOverride: SecurityCLIReadOverride?
    @TaskLocal static var taskSecurityCLIReadAccountOverride: String?

    public struct TestingOverridesSnapshot: Sendable {
        let keychainOverrideStore: ClaudeKeychainOverrideStore?
        let keychainData: Data?
        let keychainFingerprint: ClaudeKeychainFingerprint?
        let memoryCacheStore: MemoryCacheStore?
        let fingerprintStore: ClaudeKeychainFingerprintStore?
        let keychainAccessOverride: Bool?
        let credentialsFileFingerprintStore: CredentialsFileFingerprintStore?
        let securityCLIReadOverride: SecurityCLIReadOverride?
        let securityCLIReadAccountOverride: String?
        let pendingCacheClearStore: ClaudeOAuthPendingCacheClearStore?
        let oauthCacheOperationRecorder: OAuthCacheOperationRecorder?

        init(
            keychainOverrideStore: ClaudeKeychainOverrideStore?,
            keychainData: Data?,
            keychainFingerprint: ClaudeKeychainFingerprint?,
            memoryCacheStore: MemoryCacheStore?,
            fingerprintStore: ClaudeKeychainFingerprintStore?,
            keychainAccessOverride: Bool?,
            credentialsFileFingerprintStore: CredentialsFileFingerprintStore?,
            securityCLIReadOverride: SecurityCLIReadOverride?,
            securityCLIReadAccountOverride: String?,
            pendingCacheClearStore: ClaudeOAuthPendingCacheClearStore?,
            oauthCacheOperationRecorder: OAuthCacheOperationRecorder?)
        {
            self.keychainOverrideStore = keychainOverrideStore
            self.keychainData = keychainData
            self.keychainFingerprint = keychainFingerprint
            self.memoryCacheStore = memoryCacheStore
            self.fingerprintStore = fingerprintStore
            self.keychainAccessOverride = keychainAccessOverride
            self.credentialsFileFingerprintStore = credentialsFileFingerprintStore
            self.securityCLIReadOverride = securityCLIReadOverride
            self.securityCLIReadAccountOverride = securityCLIReadAccountOverride
            self.pendingCacheClearStore = pendingCacheClearStore
            self.oauthCacheOperationRecorder = oauthCacheOperationRecorder
        }
    }

    static func withKeychainAccessOverrideForTesting<T>(
        _ disabled: Bool?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskKeychainAccessOverride.withValue(disabled) {
            try operation()
        }
    }

    static func withKeychainAccessOverrideForTesting<T>(
        _ disabled: Bool?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskKeychainAccessOverride.withValue(disabled) {
            try await operation()
        }
    }

    static func withCredentialsFileFingerprintStoreOverrideForTesting<T>(
        _ store: CredentialsFileFingerprintStore?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {
            try operation()
        }
    }

    static func withCredentialsFileFingerprintStoreOverrideForTesting<T>(
        _ store: CredentialsFileFingerprintStore?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {
            try await operation()
        }
    }

    static func withIsolatedCredentialsFileTrackingForTesting<T>(
        operation: () throws -> T) rethrows -> T
    {
        let store = CredentialsFileFingerprintStore()
        return try self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {
            try operation()
        }
    }

    /// Test-only opt-in for fixtures that supply an explicit temporary Claude profile directory.
    /// Default test execution still resolves the normal credentials path to an isolated nonexistent file.
    static func withEnvironmentCredentialsURLForTesting<T>(operation: () throws -> T) rethrows -> T {
        try self.$taskUseEnvironmentCredentialsURLForTesting.withValue(true) {
            try operation()
        }
    }

    static func withEnvironmentCredentialsURLForTesting<T>(
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskUseEnvironmentCredentialsURLForTesting.withValue(true) {
            try await operation()
        }
    }

    static func withPendingCacheClearStoreOverrideForTesting<T>(
        _ store: ClaudeOAuthPendingCacheClearStore?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskPendingCacheClearStoreOverride.withValue(store) {
            try operation()
        }
    }

    static func withPendingCacheClearStoreOverrideForTesting<T>(
        _ store: ClaudeOAuthPendingCacheClearStore?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskPendingCacheClearStoreOverride.withValue(store) {
            try await operation()
        }
    }

    static func withImplicitPendingCacheClearStoreOverrideForTesting<T>(
        _ store: ClaudeOAuthPendingCacheClearStore?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskImplicitPendingCacheClearStoreOverride.withValue(store) {
            try operation()
        }
    }

    static func withOAuthCacheOperationRecorderForTesting<T>(
        _ recorder: OAuthCacheOperationRecorder?,
        operation: () throws -> T) rethrows -> T
    {
        try KeychainCacheStore.withOperationRecorderForTesting(recorder, operation: operation)
    }

    static func withOAuthCacheOperationRecorderForTesting<T>(
        _ recorder: OAuthCacheOperationRecorder?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await KeychainCacheStore.withOperationRecorderForTesting(recorder, operation: operation)
    }

    static func withIsolatedCredentialsFileTrackingForTesting<T>(
        operation: () async throws -> T) async rethrows -> T
    {
        let store = CredentialsFileFingerprintStore()
        return try await self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {
            try await operation()
        }
    }

    static func withSecurityCLIReadOverrideForTesting<T>(
        _ readOverride: SecurityCLIReadOverride?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskSecurityCLIReadOverride.withValue(readOverride) {
            try operation()
        }
    }

    static func withSecurityCLIReadOverrideForTesting<T>(
        _ readOverride: SecurityCLIReadOverride?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskSecurityCLIReadOverride.withValue(readOverride) {
            try await operation()
        }
    }

    static func currentSecurityCLIReadOverrideForTesting() -> SecurityCLIReadOverride? {
        self.taskSecurityCLIReadOverride
    }

    static func withSecurityCLIReadAccountOverrideForTesting<T>(
        _ account: String?,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskSecurityCLIReadAccountOverride.withValue(account) {
            try operation()
        }
    }

    static func withSecurityCLIReadAccountOverrideForTesting<T>(
        _ account: String?,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskSecurityCLIReadAccountOverride.withValue(account) {
            try await operation()
        }
    }

    public static func withCurrentTestingOverridesForTask<T>(
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.withTestingOverridesSnapshotForTask(
            self.currentTestingOverridesSnapshotForTask,
            operation: operation)
    }

    public static func withCurrentTestingOverridesForTask<T>(
        operation: () throws -> T) rethrows -> T
    {
        try self.withTestingOverridesSnapshotForTask(
            self.currentTestingOverridesSnapshotForTask,
            operation: operation)
    }

    public static var currentTestingOverridesSnapshotForTask: TestingOverridesSnapshot {
        TestingOverridesSnapshot(
            keychainOverrideStore: self.taskClaudeKeychainOverrideStore,
            keychainData: self.taskClaudeKeychainDataOverride,
            keychainFingerprint: self.taskClaudeKeychainFingerprintOverride,
            memoryCacheStore: self.taskMemoryCacheStoreOverride,
            fingerprintStore: self.taskClaudeKeychainFingerprintStoreOverride,
            keychainAccessOverride: self.taskKeychainAccessOverride,
            credentialsFileFingerprintStore: self.taskCredentialsFileFingerprintStoreOverride,
            securityCLIReadOverride: self.taskSecurityCLIReadOverride,
            securityCLIReadAccountOverride: self.taskSecurityCLIReadAccountOverride,
            pendingCacheClearStore: self.taskPendingCacheClearStoreOverride,
            oauthCacheOperationRecorder: KeychainCacheStore.currentOperationRecorderForTesting)
    }

    public static func withTestingOverridesSnapshotForTask<T>(
        _ snapshot: TestingOverridesSnapshot,
        operation: () async throws -> T) async rethrows -> T
    {
        try await self.$taskPendingCacheClearStoreOverride.withValue(snapshot.pendingCacheClearStore) {
            try await KeychainCacheStore.withOperationRecorderForTesting(snapshot.oauthCacheOperationRecorder) {
                try await self.$taskClaudeKeychainOverrideStore.withValue(snapshot.keychainOverrideStore) {
                    try await self.$taskClaudeKeychainDataOverride.withValue(snapshot.keychainData) {
                        try await self.$taskClaudeKeychainFingerprintOverride.withValue(snapshot.keychainFingerprint) {
                            try await self.$taskMemoryCacheStoreOverride.withValue(snapshot.memoryCacheStore) {
                                try await self.$taskClaudeKeychainFingerprintStoreOverride
                                    .withValue(snapshot.fingerprintStore) {
                                        try await self.$taskKeychainAccessOverride
                                            .withValue(snapshot.keychainAccessOverride) {
                                                try await self.$taskCredentialsFileFingerprintStoreOverride.withValue(
                                                    snapshot.credentialsFileFingerprintStore)
                                                {
                                                    try await self.$taskSecurityCLIReadOverride.withValue(
                                                        snapshot.securityCLIReadOverride)
                                                    {
                                                        try await self.$taskSecurityCLIReadAccountOverride.withValue(
                                                            snapshot.securityCLIReadAccountOverride)
                                                        {
                                                            try await operation()
                                                        }
                                                    }
                                                }
                                            }
                                    }
                            }
                        }
                    }
                }
            }
        }
    }

    public static func withTestingOverridesSnapshotForTask<T>(
        _ snapshot: TestingOverridesSnapshot,
        operation: () throws -> T) rethrows -> T
    {
        try self.$taskPendingCacheClearStoreOverride.withValue(snapshot.pendingCacheClearStore) {
            try KeychainCacheStore.withOperationRecorderForTesting(snapshot.oauthCacheOperationRecorder) {
                try self.$taskClaudeKeychainOverrideStore.withValue(snapshot.keychainOverrideStore) {
                    try self.$taskClaudeKeychainDataOverride.withValue(snapshot.keychainData) {
                        try self.$taskClaudeKeychainFingerprintOverride.withValue(snapshot.keychainFingerprint) {
                            try self.$taskMemoryCacheStoreOverride.withValue(snapshot.memoryCacheStore) {
                                try self.$taskClaudeKeychainFingerprintStoreOverride
                                    .withValue(snapshot.fingerprintStore) {
                                        try self.$taskKeychainAccessOverride
                                            .withValue(snapshot.keychainAccessOverride) {
                                                try self.$taskCredentialsFileFingerprintStoreOverride.withValue(
                                                    snapshot.credentialsFileFingerprintStore)
                                                {
                                                    try self.$taskSecurityCLIReadOverride.withValue(
                                                        snapshot.securityCLIReadOverride)
                                                    {
                                                        try self.$taskSecurityCLIReadAccountOverride.withValue(
                                                            snapshot.securityCLIReadAccountOverride)
                                                        {
                                                            try operation()
                                                        }
                                                    }
                                                }
                                            }
                                    }
                            }
                        }
                    }
                }
            }
        }
    }
}
#endif
