//
//  KeychainDataSource.swift
//  Astro
//
//  Created by Steven Shin on 1/5/17.
//  Copyright © 2017 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

public class KeychainDataSource: DataSource {

    func setKey(_ key: String, value: String) throws {
        guard let data = value.data(using: .utf8) else {
            throw DataStoreError.error("Could not encode String as NSData.")
        }

        let query: [String: Any] = [
            String(kSecClass): kSecClassKey,
            String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
            String(kSecAttrApplicationLabel): key,
            String(kSecValueData): data
        ]

        // Delete items with the given query (not including the query's value). We have to do this
        // because adding an item with duplicate attributes results in an `errSecDuplicateItem` error.
        SecItemDelete(query as CFDictionary)

        let status = SecItemAdd(query as CFDictionary, nil)
        if status != noErr {
            throw DataStoreError.error("Could not set item with key \"\(key)\" in keychain, got status code \(status).")
        }
    }

    func getString(_ key: String) throws -> String? {
        let query: [String: Any] = [
            String(kSecClass): kSecClassKey,
            String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
            String(kSecAttrApplicationLabel): key,
            String(kSecReturnData): kCFBooleanTrue,
            String(kSecMatchLimit): kSecMatchLimitOne
        ]

        var dataTypeRef: AnyObject?
        let status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, $0) }

        guard status == noErr else {
            if status != errSecItemNotFound {
                throw DataStoreError.error("Could not get item with key \"\(key)\" from keychain, got status code \(status).")
            }
            return nil
        }
        guard let data = dataTypeRef as? Data else {
            throw DataStoreError.error("Could not convert data type reference to data")
        }
        guard let value = String(data: data, encoding: .utf8) else {
            throw DataStoreError.error("Could not decode Data to String.")
        }
        return value
    }
}
