import Foundation
import Security

public enum GenericPasswordKeychainStore {
    public static func loadString(service: String, account: String) -> String? {
        guard let data = self.loadData(service: service, account: account) else { return nil }
        return String(data: data, encoding: .utf8)
    }

    @discardableResult
    public static func saveString(
        _ value: String,
        service: String,
        account: String,
        accessible: CFString = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
    ) -> Bool {
        self.saveData(Data(value.utf8), service: service, account: account, accessible: accessible)
    }

    @discardableResult
    public static func delete(service: String, account: String) -> Bool {
        let query = self.baseQuery(service: service, account: account)
        let status = SecItemDelete(query as CFDictionary)
        return status == errSecSuccess || status == errSecItemNotFound
    }

    private static func loadData(service: String, account: String) -> Data? {
        var query = self.baseQuery(service: service, account: account)
        query[kSecReturnData as String] = true
        query[kSecMatchLimit as String] = kSecMatchLimitOne

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess, let data = item as? Data else { return nil }
        return data
    }

    @discardableResult
    private static func saveData(
        _ data: Data,
        service: String,
        account: String,
        accessible: CFString
    ) -> Bool {
        let query = self.baseQuery(service: service, account: account)
        let previousData = self.loadData(service: service, account: account)

        let deleteStatus = SecItemDelete(query as CFDictionary)
        guard deleteStatus == errSecSuccess || deleteStatus == errSecItemNotFound else {
            return false
        }

        var insert = query
        insert[kSecValueData as String] = data
        insert[kSecAttrAccessible as String] = accessible
        if SecItemAdd(insert as CFDictionary, nil) == errSecSuccess {
            return true
        }

        // Best-effort rollback: preserve prior value if replacement fails.
        guard let previousData else { return false }
        var rollback = query
        rollback[kSecValueData as String] = previousData
        rollback[kSecAttrAccessible as String] = accessible
        _ = SecItemDelete(query as CFDictionary)
        _ = SecItemAdd(rollback as CFDictionary, nil)
        return false
    }

    private static func baseQuery(service: String, account: String) -> [String: Any] {
        [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
        ]
    }
}
