//
//  SecureStorePlugin.swift
//  Astro
//
//  Created by Justin Vaillancourt on 2015-09-24.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

// A secure store plugin that is currently best suited for storing cryptographic key items (`kSecClassKey`).
// It is also worth noting that the items are only available when the device is unlocked.

public class SecureStorePlugin: Plugin {

    private var dataSource: DataSource = KeychainDataSource()

    public required init(address: MessageAddress, messageBus: MessageBus, pluginResolver: PluginResolver, options: JSONObject?) {
        super.init(address: address, messageBus: messageBus, pluginResolver: pluginResolver, options: options)

        self.addAsyncRpcMethodShim("set") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let key: String = MethodShimUtils.getArg(params, key: "key", respond: respond),
                let value: String = MethodShimUtils.getArg(params, key: "value", respond: respond) {
                self.set(key, value: value, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addAsyncRpcMethodShim("get") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let key: String = MethodShimUtils.getArg(params, key: "key", respond: respond) {
                self.get(key, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addAsyncRpcMethodShim("delete") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let key: String = MethodShimUtils.getArg(params, key: "key", respond: respond) {
                self.delete(key, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addAsyncRpcMethodShim("clear") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.clear(respond)
            /////////////////////////////////////////////////////////////
        }
    }

    // @AsyncRpcMethod
    func set(_ key: String, value: String, respond: @escaping RPCMethodCallback) {
        DispatchQueue.main.async {
            if key.isEmpty {
                respond(.error("Cannot store an empty key"))
                return
            }
            do {
                try self.dataSource.setKey(key, value: value)
                respond(.result(NSNull()))
            } catch DataStoreError.error(let message) {
                respond(.error(message))
            } catch {
                respond(.error("Unknown error"))
            }

        }
    }

    // @AsyncRpcMethod
    func get(_ key: String, respond: @escaping RPCMethodCallback) {
        DispatchQueue.main.async {
            if key.isEmpty {
                respond(.error("Empty keys are not valid"))
                return
            }

            do {
                guard let stringResult = try self.dataSource.getString(key) else {
                    respond(.result(NSNull()))
                    return
                }
                respond(.result(stringResult))
            } catch DataStoreError.error(let message) {
                respond(.error(message))
            } catch {
                respond(.error("Unknown error"))
            }
        }
    }

    // @AsyncRpcMethod
    func delete(_ key: String, respond: @escaping RPCMethodCallback) {
        DispatchQueue.main.async {
            if key.isEmpty {
                respond(.error("Empty keys are not valid"))
                return
            }

            let query: [String: Any] = [
                String(kSecClass): kSecClassKey,
                String(kSecAttrApplicationLabel): key
            ]

            let status = SecItemDelete(query as CFDictionary)
            if ![noErr, errSecItemNotFound].contains(status) {
                respond(.error("Could not delete item with key \"\(key)\" from keychain, got status code \(status)."))
            } else {
                respond(.result(NSNull()))
            }
        }
    }

    // @AsyncRpcMethod
    func clear(_ respond: @escaping RPCMethodCallback) {
        DispatchQueue.main.async(execute: {
            let query = [
                String(kSecClass): kSecClassKey
            ]
            let status = SecItemDelete(query as CFDictionary)
            if ![noErr, errSecItemNotFound].contains(status) {
                respond(.error("Could not clear items from keychain, got status code \(status)."))
            } else {
                respond(.result(NSNull()))
            }
        })
    }
}
