package info.knacki.pass.io.pgp;

import android.content.Context;
import android.support.annotation.NonNull;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.spec.SecretKeySpec;

import info.knacki.pass.io.FileUtils;
import info.knacki.pass.io.PathUtils;

public class UberGPGStorage implements GPGStorageEngine.IGPGStorage {
    private static final Logger log = Logger.getLogger(UberGPGStorage.class.getName());
    private final Context fContext;

    private final static String KEY_NAME = "sbcKey";
    private final static String KEY_PASS = UberGPGStorage.class.getName()+"___PASSWORD";

    UberGPGStorage(Context ctx) {
        fContext = ctx;
    }

    private PGPSecretKeyRingCollection FindKeyring(@NonNull byte[] in) throws IOException {
        try {
            return new PGPSecretKeyRingCollection(in, new JcaKeyFingerprintCalculator());
        } catch (PGPException e) {
            throw new GPGUtil.MalformedKeyException(e);
        }
    }

    private KeyStore.SecretKeyEntry FindSecretKey(@NonNull byte[] in) throws IOException {
        Iterator<PGPSecretKeyRing> kr = FindKeyring(in).iterator();
        if (kr.hasNext())
            return new KeyStore.SecretKeyEntry(new SecretKeySpec(kr.next().getEncoded(), "AES"));
        return null;
    }

    private KeyStore GetKeystore(boolean read) {
        KeyStore ks;

        try {
            ks = KeyStore.getInstance("UBER", "BC");
            if (read) {
                File gpgKeyFile = new File(PathUtils.GetGPGKeyFile(fContext));
                if (!gpgKeyFile.exists())
                    throw new FileNotFoundException(gpgKeyFile.getName());
                ks.load(new FileInputStream(gpgKeyFile), KEY_PASS.toCharArray());
            } else {
                ks.load(null, null);
            }
        } catch (KeyStoreException | NoSuchProviderException | IOException | CertificateException | NoSuchAlgorithmException e) {
            log.log(Level.SEVERE, "Cannot retrieve KeyStore", e);
            ks = null;
        }
        return ks;
    }

    private void SaveKeystore(KeyStore ks) {
        try {
            File f = new File(PathUtils.GetGPGKeyFile(fContext));
            if (!f.exists() && !f.createNewFile())
                throw new FileNotFoundException(f.getName());
            ks.store(new FileOutputStream(f), KEY_PASS.toCharArray());
        }
        catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
            log.log(Level.SEVERE, "Cannot save keystore", e);
        }
    }

    @Override
    public boolean HasGPGKey() {
        try {
            KeyStore ks = GetKeystore(true);
            return ks != null && ks.containsAlias(KEY_NAME);
        } catch (KeyStoreException e) {
            log.log(Level.SEVERE, "KeyStore Exception: " +e.getMessage(), e);
            return false;
        }
    }

    @Override
    public boolean SetGPGKeyContent(InputStream content) {
        KeyStore ks = GetKeystore(false);
        if (ks == null)
            return false;
        try {
            KeyStore.SecretKeyEntry keyring = FindSecretKey(FileUtils.ReadAllStream(content));
            ks.setEntry(KEY_NAME, keyring, new KeyStore.PasswordProtection(null));
            SaveKeystore(ks);
        } catch (KeyStoreException | IOException e) {
            log.log(Level.SEVERE, "Cannot set GPG key content", e);
            return false;
        }
        return true;
    }

    private @NonNull KeyStore.SecretKeyEntry GetGPGKeyContent() throws FileNotFoundException {
        if (!HasGPGKey())
            throw new FileNotFoundException("GPG key");
        KeyStore ks = GetKeystore(true);
        if (ks == null)
            throw new FileNotFoundException("GPG key store");
        try {
            return (KeyStore.SecretKeyEntry) ks.getEntry(KEY_NAME, null);
        } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
            log.log(Level.SEVERE, "KeyStore Exception: " +e.getMessage(), e);
            throw new FileNotFoundException("GPG key store");
        }
    }

    @Override
    public void ExportGPGKeyContent(OutputStream out) throws IOException {
        try {
            new PGPSecretKeyRingCollection(Collections.singleton(GetGPGKeyRing())).encode(out);
        } catch (PGPException e) {
            e.printStackTrace();
        }
    }

    @Override
    public @NonNull PGPSecretKeyRing GetGPGKeyRing() throws IOException {
        KeyStore.SecretKeyEntry secretKey = GetGPGKeyContent();

        try {
            Iterator<PGPSecretKeyRing> keyringCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new ByteArrayInputStream(secretKey.getSecretKey().getEncoded())), new JcaKeyFingerprintCalculator()).iterator();
            if (keyringCollection.hasNext())
                return keyringCollection.next();
        } catch (PGPException e) {
            log.log(Level.WARNING, "Cannot load key: " + e.getMessage(), e);
            throw new GPGUtil.MalformedKeyException(e);
        }
        throw new NoSuchElementException("GPG key not found");
    }

    @Override
    public @NonNull PGPSecretKey GetGPGSecretKey() throws IOException {
        PGPSecretKey key = GetGPGKeyRing().getSecretKey();
        if (key == null)
            throw new FileNotFoundException("Key");
        return key;
    }

    @Override
    public @NonNull PGPSecretKey GetGPGSecretKey(long keyId) throws IOException {
        PGPSecretKey key = GetGPGKeyRing().getSecretKey(keyId);
        if (key == null)
            throw new FileNotFoundException(String.format("Key %X", keyId));
        return key;
    }

    @Override
    public void RemoveGpgKey() {
        File f = new File(PathUtils.GetGPGKeyFile(fContext));
        if (f.exists() && !f.delete()) {
            log.severe("Cannot remove GPG file");
        }
    }
}
