package kz.arta.synergy.workflow.mobile.android.works.manager.completion;

import android.util.Base64;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by neox on 24.02.16.
 * Менеджер для работы с подписями
 */
public class SignManager {

    public static final int OK = 0;

    public static final int NO_PASSWORD = 1;

    public static final int INCORRECT_PASSWORD = 2;

    public static final int INCORRECT_ALG = 3;

    /**
     * ИИН
     */
    public static final String SERIALNUMBER = "SERIALNUMBER";

    /**
     * Имя
     */
    public static final String SURNAME = "CN";

    /**
     * Отчество
     */
    public static final String GIVENNAME = "GIVENNAME";

    /**
     * Организация
     */
    public static final String ORGANIZATION = "O";

    /**
     * До какого числа действителен
     */
    public static final String EXPIRED = "EXPIRED";

    /**
     * Формат даты "Просрочен"
     */
    private static final String DATE_FORMAT = "dd.MM.yyyy";

    /**
     * Путь до сертификата
     */
    private String filePath;

    /**
     * Пароль
     */
    private String password;

    /**
     * Подписываемые данные
     */
    private String rawData;

    /**
     * Метод используется для валидации пароля
     *
     * @return статус валидации
     */
    public int validPassword() {
        if (password == null) {
            return NO_PASSWORD;
        }
        try {
            File file = new File(filePath);
            KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
            keyStore.load(new FileInputStream(file.getPath()), password.toCharArray());
            String alias = keyStore.aliases().nextElement();
            X509Certificate x509Certificate = (X509Certificate) keyStore.getCertificate(alias);
            return (x509Certificate != null ? OK : INCORRECT_PASSWORD);
        } catch (IOException e) {
            if (e.getMessage().startsWith("PKCS12 key store mac invalid")) {
                return INCORRECT_PASSWORD;
            } else if (e.getMessage().startsWith("exception unwrapping private key")) {
                return INCORRECT_ALG;
            }
        } catch (Exception exc) {
            return INCORRECT_PASSWORD;
        }
        return OK;
    }

    /**
     * Метод возвращает сертификат
     *
     * @return при успешной валидации сервер возвращает объект класса X509Certificate, иначе - null
     */
    public X509Certificate getCert() {
        if (password == null) {
            return null;
        }
        X509Certificate x509Certificate;
        try {
            File file = new File(filePath);
            KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
            keyStore.load(new FileInputStream(file.getPath()), password.toCharArray());
            String alias = keyStore.aliases().nextElement();
            x509Certificate = (X509Certificate) keyStore.getCertificate(alias);
        } catch (Exception exc) {
            return null;
        }
        return x509Certificate;
    }

    /**
     * Метод подписывает документ и возвращает массив состоящий из 2-х элементов
     *
     * @return result[0] - base64 строка сертификата
     * result[1] - base64 строка подписанных данных
     */
    public String[] signRawData() {
        try {
            String[] result = new String[2];
            KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
            keyStore.load(new FileInputStream(new File(filePath).getPath()), password.toCharArray());
            Enumeration enumeration = keyStore.aliases();
            String alias = (String) enumeration.nextElement();
            X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
            certificate.checkValidity();
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
            byte[] data = Base64.decode(rawData, Base64.NO_WRAP);
            String alg = (certificate.getSigAlgName().contains("MD5") ? "MD5WITHRSA" : "SHA1withRSA");
            Signature signature = Signature.getInstance(alg);
            signature.initSign(privateKey);
            signature.update(data, 0, data.length);
            final byte[] signByte = signature.sign();
            Signature valid = Signature.getInstance(alg);
            valid.initVerify(certificate);
            valid.update(data);
            valid.verify(signByte);
            result[0] = Base64.encodeToString(certificate.getEncoded(), Base64.NO_WRAP);
            result[1] = Base64.encodeToString(signByte, Base64.NO_WRAP);
            return result;
        } catch (Exception exc) {
            return null;
        }
    }

    /**
     * Метод возвращает информацию о сертификате
     *
     * @return карта
     */
    public Map<String, String> getCertInfo() {
        Map<String, String> info = new HashMap<>();
        try {
            File file = new File(filePath);
            KeyStore p12 = KeyStore.getInstance("PKCS12", "BC");
            p12.load(new FileInputStream(file.getPath()), password.toCharArray());
            Enumeration enumeration = p12.aliases();
            String alias = (String) enumeration.nextElement();
            X509Certificate certificate = (X509Certificate) p12.getCertificate(alias);
            certificate.checkValidity();
            String[] subjectDN = certificate.getSubjectDN().getName().split(",");
            for (String str : subjectDN) {
                String[] data = str.split("=");
                if (data[0].equals(SERIALNUMBER)) {
                    info.put(SERIALNUMBER, data[1]);
                }
                if (data[0].equals(SURNAME)) {
                    info.put(SURNAME, data[1]);
                }
                if (data[0].equals(GIVENNAME)) {
                    info.put(GIVENNAME, data[1]);
                }
                if (data[0].equals(ORGANIZATION)) {
                    info.put(ORGANIZATION, data[1]);
                }
            }
            info.put(EXPIRED, new SimpleDateFormat(DATE_FORMAT).format(certificate.getNotAfter()));
        } catch (Exception ignored) {
            return info;
        }
        return info;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setRawData(String rawData) {
        this.rawData = rawData;
    }
}
