import Foundation
import Security

enum KeychainStore {
    static var installationID: String {
        if let existingID = value(for: .installationID) {
            return existingID
        }

        let newID = UUID().uuidString
        store(newID, for: .installationID)
        return newID
    }

    static var authToken: String? {
        get { value(for: .authToken) }
        set { set(newValue, for: .authToken) }
    }

    static var peerID: String? {
        get { value(for: .peerID) }
        set { set(newValue, for: .peerID) }
    }

    static var peerName: String? {
        get { value(for: .peerName) }
        set { set(newValue, for: .peerName) }
    }

    static func clearAll() {
        delete(.authToken)
        delete(.peerID)
        delete(.peerName)
    }

    private static let service = "de.diyaa.fchati"

    private enum Key: String {
        case installationID
        case authToken
        case peerID
        case peerName
    }

    private static func set(_ value: String?, for key: Key) {
        guard let value else {
            delete(key)
            return
        }

        store(value, for: key)
    }

    private static func value(for key: Key) -> String? {
        var query = baseQuery(for: key)
        query[kSecMatchLimit] = kSecMatchLimitOne
        query[kSecReturnData] = true

        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        guard status != errSecItemNotFound else {
            return nil
        }

        guard status == errSecSuccess, let data = result as? Data else {
            reportFailure(operation: "read", status: status)
            return nil
        }

        guard let value = String(data: data, encoding: .utf8) else {
            assertionFailure("Keychain value is not valid UTF-8.")
            return nil
        }

        return value
    }

    private static func store(_ value: String, for key: Key) {
        guard let data = value.data(using: .utf8) else {
            assertionFailure("Keychain value cannot be encoded as UTF-8.")
            return
        }

        let query = baseQuery(for: key)
        let attributes = [kSecValueData: data] as CFDictionary
        let updateStatus = SecItemUpdate(query as CFDictionary, attributes)

        if updateStatus == errSecItemNotFound {
            var addQuery = query
            addQuery[kSecValueData] = data
            addQuery[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly

            let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
            guard addStatus == errSecSuccess else {
                reportFailure(operation: "add", status: addStatus)
                return
            }
            return
        }

        guard updateStatus == errSecSuccess else {
            reportFailure(operation: "update", status: updateStatus)
            return
        }
    }

    private static func delete(_ key: Key) {
        let status = SecItemDelete(baseQuery(for: key) as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            reportFailure(operation: "delete", status: status)
            return
        }
    }

    private static func baseQuery(for key: Key) -> [CFString: Any] {
        [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: key.rawValue,
        ]
    }

    private static func reportFailure(operation: String, status: OSStatus) {
        assertionFailure("Keychain \(operation) failed: \(status).")
    }
}
