import Foundation
import Security
import os
import HostRegistry

/// In-memory keychain double behind the `SecItemShim` seam (plan §3.3):
/// unsigned `swift test` binaries get errSecMissingEntitlement (-34018) from
/// the real data-protection keychain, so store logic is tested against this.
///
/// Behavioral single-slot model (the store uses exactly one service+account):
/// - add: duplicate if a value is stored; otherwise stores kSecValueData
/// - update: itemNotFound when empty; otherwise replaces the stored value
/// - copyMatching: itemNotFound when empty; otherwise returns the stored value
/// - delete: itemNotFound when empty; otherwise clears
///
/// `force*` knobs override the next status/result (forced statuses do NOT
/// touch storage — the fake stays dumb). All calls are recorded verbatim so
/// tests can assert the §5.3 attribute dictionaries character-for-character.
final class FakeSecItemShim: SecItemShim, Sendable {
    struct UpdateCall: Sendable {
        let query: [String: any Sendable]
        let attributes: [String: any Sendable]
    }

    private struct State: Sendable {
        var storedData: Data?
        var addCalls: [[String: any Sendable]] = []
        var updateCalls: [UpdateCall] = []
        var copyCalls: [[String: any Sendable]] = []
        var deleteCalls: [[String: any Sendable]] = []
        var forcedAddStatus: OSStatus?
        var forcedUpdateStatus: OSStatus?
        var forcedCopyStatus: OSStatus?
        var forcedDeleteStatus: OSStatus?
        var forcedCopyData: Data?
    }

    private let state = OSAllocatedUnfairLock(initialState: State())

    // MARK: - SecItemShim

    func add(_ attributes: [String: any Sendable]) -> OSStatus {
        state.withLock { s in
            s.addCalls.append(attributes)
            if let forced = s.forcedAddStatus { return forced }
            guard s.storedData == nil else { return errSecDuplicateItem }
            s.storedData = attributes[kSecValueData as String] as? Data
            return errSecSuccess
        }
    }

    func update(query: [String: any Sendable],
                attributesToUpdate: [String: any Sendable]) -> OSStatus {
        state.withLock { s in
            s.updateCalls.append(UpdateCall(query: query, attributes: attributesToUpdate))
            if let forced = s.forcedUpdateStatus { return forced }
            guard s.storedData != nil else { return errSecItemNotFound }
            s.storedData = attributesToUpdate[kSecValueData as String] as? Data
            return errSecSuccess
        }
    }

    func copyMatching(_ query: [String: any Sendable]) -> (status: OSStatus, data: Data?) {
        state.withLock { s in
            s.copyCalls.append(query)
            if let forced = s.forcedCopyStatus { return (forced, nil) }
            if let forcedData = s.forcedCopyData { return (errSecSuccess, forcedData) }
            guard let data = s.storedData else { return (errSecItemNotFound, nil) }
            return (errSecSuccess, data)
        }
    }

    func delete(_ query: [String: any Sendable]) -> OSStatus {
        state.withLock { s in
            s.deleteCalls.append(query)
            if let forced = s.forcedDeleteStatus { return forced }
            guard s.storedData != nil else { return errSecItemNotFound }
            s.storedData = nil
            return errSecSuccess
        }
    }

    // MARK: - Test controls

    func forceAddStatus(_ status: OSStatus) { state.withLock { $0.forcedAddStatus = status } }
    func forceUpdateStatus(_ status: OSStatus) { state.withLock { $0.forcedUpdateStatus = status } }
    func forceCopyStatus(_ status: OSStatus) { state.withLock { $0.forcedCopyStatus = status } }
    func forceDeleteStatus(_ status: OSStatus) { state.withLock { $0.forcedDeleteStatus = status } }
    func forceCopyData(_ data: Data) { state.withLock { $0.forcedCopyData = data } }

    // MARK: - Recorded calls

    var recordedAdds: [[String: any Sendable]] { state.withLock { $0.addCalls } }
    var recordedUpdates: [UpdateCall] { state.withLock { $0.updateCalls } }
    var recordedCopies: [[String: any Sendable]] { state.withLock { $0.copyCalls } }
    var recordedDeletes: [[String: any Sendable]] { state.withLock { $0.deleteCalls } }
}
