import Foundation
import Security

actor KeychainManager {
    static let shared = KeychainManager()

    private static let serviceName = "de.hausotte.Forji"

    private init() {}

    private func key(for server: String, username: String, suffix: String = "") -> String {
        "\(server)_\(username)\(suffix)"
    }

    // MARK: - Password

    func savePassword(_ password: String, for server: String, username: String) throws {
        try saveItem(password, forKey: key(for: server, username: username))
    }

    func getPassword(for server: String, username: String) throws -> String {
        try getItem(forKey: key(for: server, username: username))
    }

    func deletePassword(for server: String, username: String) throws {
        try deleteItem(forKey: key(for: server, username: username))
    }

    // MARK: - Token

    func saveToken(_ token: String, for server: String, username: String) throws {
        try saveItem(token, forKey: key(for: server, username: username, suffix: "_token"))
    }

    func getToken(for server: String, username: String) throws -> String {
        try getItem(forKey: key(for: server, username: username, suffix: "_token"))
    }

    func deleteToken(for server: String, username: String) throws {
        try deleteItem(forKey: key(for: server, username: username, suffix: "_token"))
    }

    // MARK: - Account (both items)

    /// Removes every credential stored for an account: the password and the API token.
    /// Use when an account is removed entirely (logout, delete instance) so neither item is orphaned.
    func deleteCredentials(for server: String, username: String) throws {
        try deletePassword(for: server, username: username)
        try deleteToken(for: server, username: username)
    }

    // MARK: - Synchronous token read (no actor hop)

    nonisolated static func getTokenSync(for server: String, username: String) -> String? {
        let account = "\(server)_\(username)_token"
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: serviceName,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne,
        ]
        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        guard status == errSecSuccess,
              let data = result as? Data,
              let value = String(data: data, encoding: .utf8)
        else { return nil }
        return value
    }

    // MARK: - Migration

    func migrateAccessibility(for server: String, username: String) {
        let suffixes = ["", "_token"]
        for suffix in suffixes {
            let account = key(for: server, username: username, suffix: suffix)
            guard let value = try? getItem(forKey: account) else { continue }
            try? deleteItem(forKey: account)
            try? saveItem(value, forKey: account)
        }
    }

    // MARK: - Private helpers

    private func saveItem(_ value: String, forKey key: String) throws {
        guard let data = value.data(using: .utf8) else {
            throw KeychainError.unableToSave
        }

        let deleteQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: Self.serviceName,
            kSecAttrAccount as String: key,
        ]

        SecItemDelete(deleteQuery as CFDictionary)

        let addQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: Self.serviceName,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
        ]

        let status = SecItemAdd(addQuery as CFDictionary, nil)

        guard status == errSecSuccess else {
            throw KeychainError.unableToSave
        }
    }

    private func getItem(forKey key: String) throws -> String {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: Self.serviceName,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne,
        ]

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

        guard status == errSecSuccess,
              let data = result as? Data,
              let value = String(data: data, encoding: .utf8)
        else {
            throw KeychainError.notFound
        }

        return value
    }

    private func deleteItem(forKey key: String) throws {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: Self.serviceName,
            kSecAttrAccount as String: key,
        ]

        let status = SecItemDelete(query as CFDictionary)

        guard status == errSecSuccess || status == errSecItemNotFound else {
            throw KeychainError.unableToDelete
        }
    }
}

enum KeychainError: Error {
    case unableToSave
    case notFound
    case unableToDelete
}
