/*
 * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.certificate.mgt.core.config.CertificateConfigurationManager;
import org.wso2.carbon.certificate.mgt.core.config.CertificateKeystoreConfig;
import org.wso2.carbon.certificate.mgt.core.exception.CertificateManagementException;
import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception.VirtualFirealarmDeviceMgtPluginException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

public class VirtualFirealarmSecurityManager {
    private static final Log log = LogFactory.getLog(VirtualFirealarmSecurityManager.class);

    private static PrivateKey serverPrivateKey;
    private static final String SHA_512 = "SHA-512";
    private static final String CIPHER_PADDING = "RSA/ECB/PKCS1Padding";
    private static CertificateKeystoreConfig certificateKeystoreConfig;
    private VirtualFirealarmSecurityManager() {

    }

    private static CertificateKeystoreConfig getCertKeyStoreConfig() throws CertificateManagementException {
        if (certificateKeystoreConfig == null) {
            certificateKeystoreConfig = CertificateConfigurationManager.getInstance().getCertificateKeyStoreConfig();
        }
        return certificateKeystoreConfig;
    }

    public static void initVerificationManager() {
        serverPrivateKey = retrievePrivateKey();
    }

    public static PrivateKey retrievePrivateKey() {
        PrivateKey privateKey = null;
        InputStream inputStream = null;
        KeyStore keyStore;
        try {
            CertificateKeystoreConfig certificateKeystoreConfig = getCertKeyStoreConfig();
            keyStore = KeyStore.getInstance(certificateKeystoreConfig.getCertificateKeystoreType());
            inputStream = new FileInputStream(certificateKeystoreConfig.getCertificateKeystoreLocation());

            keyStore.load(inputStream, certificateKeystoreConfig.getCertificateKeystorePassword().toCharArray());

            privateKey = (PrivateKey) (keyStore.getKey(certificateKeystoreConfig.getCACertAlias(),
                                                       certificateKeystoreConfig.getCAPrivateKeyPassword().toCharArray()));

        } catch (KeyStoreException e) {
            String errorMsg = "Could not load KeyStore of given type in [certificate-config.xml] file." ;
            log.error(errorMsg, e);
        } catch (FileNotFoundException e) {
            String errorMsg = "KeyStore file could not be loaded from path given in [certificate-config.xml] file.";
            log.error(errorMsg, e);
        } catch (NoSuchAlgorithmException e) {
            String errorMsg = "Algorithm not found when loading KeyStore";
            log.error(errorMsg, e);
        } catch (CertificateException e) {
            String errorMsg = "CertificateException when loading KeyStore";
            log.error(errorMsg, e);
        } catch (IOException e) {
            String errorMsg = "Input output issue occurred when loading KeyStore";
            log.error(errorMsg, e);
        } catch (UnrecoverableKeyException e) {
            String errorMsg = "Key is unrecoverable when retrieving CA private key";
            log.error(errorMsg, e);
        } catch (CertificateManagementException e) {
            String errorMsg = "Failed to load the certificate";
            log.error(errorMsg, e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                log.error("Error closing KeyStore input stream", e);
            }
        }

        return privateKey;
    }

    public static PrivateKey getServerPrivateKey() {
        return serverPrivateKey;
    }

    public static String encryptMessage(String message, Key encryptionKey) throws
                                                                           VirtualFirealarmDeviceMgtPluginException {
        Cipher encrypter;
        byte[] cipherData;

        try {
            encrypter = Cipher.getInstance(CIPHER_PADDING);
            encrypter.init(Cipher.ENCRYPT_MODE, encryptionKey);
            cipherData = encrypter.doFinal(message.getBytes(StandardCharsets.UTF_8));

        } catch (NoSuchAlgorithmException e) {
            String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (NoSuchPaddingException e) {
            String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (InvalidKeyException e) {
            String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + encryptionKey + "\n]\n";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (BadPaddingException e) {
            String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (IllegalBlockSizeException e) {
            String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        }

        return Base64.encodeBase64String(cipherData);
    }

    public static String signMessage(String encryptedData, PrivateKey signatureKey) throws VirtualFirealarmDeviceMgtPluginException {

        Signature signature;
        String signedEncodedString;

        try {
            signature = Signature.getInstance(SHA_512);
            signature.initSign(signatureKey);
            signature.update(Base64.decodeBase64(encryptedData));

            byte[] signatureBytes = signature.sign();
            signedEncodedString = Base64.encodeBase64String(signatureBytes);

        } catch (NoSuchAlgorithmException e) {
            String errorMsg = "Algorithm not found exception occurred for Signature instance of [" + SHA_512 + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (SignatureException e) {
            String errorMsg = "Signature exception occurred for Signature instance of [" + SHA_512 + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (InvalidKeyException e) {
            String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + signatureKey + "\n]\n";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        }

        return signedEncodedString;
    }

    public static boolean verifySignature(String data, String signedData, PublicKey verificationKey)
            throws VirtualFirealarmDeviceMgtPluginException {

        Signature signature;
        boolean verified;

        try {
            signature = Signature.getInstance(SHA_512);
            signature.initVerify(verificationKey);
            signature.update(Base64.decodeBase64(data));

            verified = signature.verify(Base64.decodeBase64(signedData));

        } catch (NoSuchAlgorithmException e) {
            String errorMsg = "Algorithm not found exception occurred for Signature instance of [" + SHA_512 + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (SignatureException e) {
            String errorMsg = "Signature exception occurred for Signature instance of [" + SHA_512 + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (InvalidKeyException e) {
            String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + verificationKey + "\n]\n";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        }

        return verified;
    }

    public static String decryptMessage(String encryptedMessage, Key decryptKey) throws VirtualFirealarmDeviceMgtPluginException {

        Cipher decrypter;
        String decryptedMessage;

        try {

            decrypter = Cipher.getInstance(CIPHER_PADDING);
            decrypter.init(Cipher.DECRYPT_MODE, decryptKey);
            decryptedMessage = new String(decrypter.doFinal(Base64.decodeBase64(encryptedMessage)), StandardCharsets.UTF_8);

        } catch (NoSuchAlgorithmException e) {
            String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (NoSuchPaddingException e) {
            String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (InvalidKeyException e) {
            String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + decryptKey + "\n]\n";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (BadPaddingException e) {
            String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        } catch (IllegalBlockSizeException e) {
            String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]";
            log.error(errorMsg);
            throw new VirtualFirealarmDeviceMgtPluginException(errorMsg, e);
        }

        return decryptedMessage;
    }


}
