//
//  PGPAgent.swift
//  passKit
//
//  Created by Yishi Lin on 2019/7/17.
//  Copyright © 2019 Bob Sun. All rights reserved.
//

public class PGPAgent {
    public static let shared = PGPAgent()

    private let keyStore: KeyStore
    private var pgpInterface: PGPInterface?
    private var latestDecryptStatus = true

    public init(keyStore: KeyStore = AppKeychain.shared) {
        self.keyStore = keyStore
    }

    public func initKeys() throws {
        guard let publicKey: String = keyStore.get(for: PGPKey.PUBLIC.getKeychainKey()),
              let privateKey: String = keyStore.get(for: PGPKey.PRIVATE.getKeychainKey()) else {
            pgpInterface = nil
            throw AppError.keyImport
        }
        do {
            pgpInterface = try GopenPGPInterface(publicArmoredKey: publicKey, privateArmoredKey: privateKey)
        } catch {
            pgpInterface = try ObjectivePGPInterface(publicArmoredKey: publicKey, privateArmoredKey: privateKey)
        }
    }

    public func uninitKeys() {
        pgpInterface = nil
    }

    public func isInitialized() -> Bool {
        pgpInterface != nil
    }

    public func getKeyID() throws -> [String] {
        try checkAndInit()
        return pgpInterface?.keyID ?? []
    }

    public func getShortKeyID() throws -> [String] {
        try checkAndInit()
        return pgpInterface?.shortKeyID.sorted() ?? []
    }

    public func decrypt(encryptedData: Data, keyID: String, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Data? {
        // Init keys.
        try checkAndInit()
        guard let pgpInterface else {
            throw AppError.decryption
        }

        var keyID = keyID
        if !pgpInterface.containsPrivateKey(with: keyID) {
            if pgpInterface.keyID.count == 1 {
                keyID = pgpInterface.keyID.first!
            } else {
                throw AppError.pgpPrivateKeyNotFound(keyID: keyID)
            }
        }

        // Remember the previous status and set the current status
        let previousDecryptStatus = latestDecryptStatus
        latestDecryptStatus = false

        // Get the PGP key passphrase.
        var passphrase = ""
        if previousDecryptStatus == false {
            passphrase = requestPGPKeyPassphrase(keyID)
        } else {
            passphrase = keyStore.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? requestPGPKeyPassphrase(keyID)
        }
        // Decrypt.
        guard let result = try pgpInterface.decrypt(encryptedData: encryptedData, keyID: keyID, passphrase: passphrase) else {
            return nil
        }
        // The decryption step has succeed.
        latestDecryptStatus = true
        return result
    }

    public func encrypt(plainData: Data, keyID: String) throws -> Data {
        try checkAndInit()
        guard let pgpInterface else {
            throw AppError.encryption
        }
        var keyID = keyID
        if !pgpInterface.containsPublicKey(with: keyID) {
            if pgpInterface.keyID.count == 1 {
                keyID = pgpInterface.keyID.first!
            } else {
                throw AppError.pgpPublicKeyNotFound(keyID: keyID)
            }
        }
        return try pgpInterface.encrypt(plainData: plainData, keyID: keyID)
    }

    public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: (String) -> String) throws -> Data? {
        // Remember the previous status and set the current status
        let previousDecryptStatus = latestDecryptStatus
        latestDecryptStatus = false
        // Init keys.
        try checkAndInit()
        // Get the PGP key passphrase.
        var passphrase = ""
        if previousDecryptStatus == false {
            passphrase = requestPGPKeyPassphrase("")
        } else {
            passphrase = keyStore.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: "")) ?? requestPGPKeyPassphrase("")
        }
        // Decrypt.
        guard let result = try pgpInterface!.decrypt(encryptedData: encryptedData, keyID: nil, passphrase: passphrase) else {
            return nil
        }
        // The decryption step has succeed.
        latestDecryptStatus = true
        return result
    }

    public func encrypt(plainData: Data) throws -> Data {
        try checkAndInit()
        guard let pgpInterface else {
            throw AppError.encryption
        }
        return try pgpInterface.encrypt(plainData: plainData, keyID: nil)
    }

    public var isPrepared: Bool {
        keyStore.contains(key: PGPKey.PUBLIC.getKeychainKey())
            && keyStore.contains(key: PGPKey.PRIVATE.getKeychainKey())
    }

    private func checkAndInit() throws {
        if pgpInterface == nil || !keyStore.contains(key: Globals.pgpKeyPassphrase) {
            try initKeys()
        }
    }
}
