package com.secureline.server;

import android.content.Context;
import android.os.Environment;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Slog;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.security.KeyStore;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

public class SecureLineCommandStore {

    private static final String TAG =
            "SecureLineCommandStore";

    private static final String ALIAS =
            "secureline_command_key";

    private static final String TRANSFORMATION =
            "AES/GCM/NoPadding";

    private final File mFile;

    public SecureLineCommandStore(Context ctx) {

        File base =
                new File(
                        Environment.getDataSystemDeDirectory(0),
                        "secureline"
                );

	if (!base.exists()) {

	    if (!base.mkdirs()) {

	        throw new IllegalStateException(
	            "Failed creating command dir"
	        );
	    }

	    base.setReadable(false,false);
	    base.setWritable(true,true);
	    base.setExecutable(true,true);
	}

        mFile =
                new File(base,"command.bin");
    }

    private void ensureKey()
            throws Exception {

        KeyStore ks =
                KeyStore.getInstance("AndroidKeyStore");

        ks.load(null);

        if(ks.containsAlias(ALIAS))
            return;

        KeyGenerator kg =
                KeyGenerator.getInstance(
                        KeyProperties.KEY_ALGORITHM_AES,
                        "AndroidKeyStore"
                );

        kg.init(
                new KeyGenParameterSpec.Builder(

                        ALIAS,

                        KeyProperties.PURPOSE_ENCRYPT
                                | KeyProperties.PURPOSE_DECRYPT

                )

                        .setBlockModes(
                                KeyProperties.BLOCK_MODE_GCM
                        )

                        .setEncryptionPaddings(
                                KeyProperties.ENCRYPTION_PADDING_NONE
                        )

                        .setUnlockedDeviceRequired(false)

                        .setKeySize(256)

                        .build()
        );

        kg.generateKey();

        Slog.i(TAG,
                "Command key created");
    }

    private SecretKey getKey()
            throws Exception {

        KeyStore ks =
                KeyStore.getInstance("AndroidKeyStore");

        ks.load(null);

        return (SecretKey)
                ks.getKey(ALIAS,null);
    }

    public void store(byte[] plain)
            throws Exception {

        ensureKey();

        Cipher c =
                Cipher.getInstance(TRANSFORMATION);

        c.init(
                Cipher.ENCRYPT_MODE,
                getKey()
        );

        byte[] iv =
                c.getIV();

        byte[] enc =
                c.doFinal(plain);

        ByteBuffer buf =
                ByteBuffer.allocate(
                        4+iv.length+enc.length
                );

        buf.putInt(iv.length);
        buf.put(iv);
        buf.put(enc);

        File tmp =
                new File(
                        mFile.getAbsolutePath()+".tmp"
                );

        Files.write(
                tmp.toPath(),
                buf.array()
        );

        try(FileOutputStream fos =
                    new FileOutputStream(tmp,true)) {

            fos.getFD().sync();
        }

        if(!tmp.renameTo(mFile)) {

            throw new IllegalStateException(
                    "command rename failed"
            );
        }
    }

    public byte[] load()
            throws Exception {

        if(!mFile.exists())
            return null;

        byte[] data =
                Files.readAllBytes(
                        mFile.toPath()
                );

        ByteBuffer buf =
                ByteBuffer.wrap(data);

        int ivLen =
                buf.getInt();

        if(ivLen!=12)
            throw new SecurityException(
                    "invalid IV"
            );

        byte[] iv =
                new byte[ivLen];

        buf.get(iv);

        byte[] enc =
                new byte[buf.remaining()];

        buf.get(enc);

        Cipher c =
                Cipher.getInstance(TRANSFORMATION);

        c.init(
                Cipher.DECRYPT_MODE,
                getKey(),
                new GCMParameterSpec(128,iv)
        );

        return c.doFinal(enc);
    }

    public boolean exists() {

        boolean ok =
                mFile.exists()
                        && mFile.length() > 32;

        if (!ok) {

            Slog.w(TAG,
                    "Command store corrupted or empty");
        }

        return ok;
    }

    public void delete() {
        try {
            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
            ks.load(null);

            if (ks.containsAlias(ALIAS)) {
                ks.deleteEntry(ALIAS);
            }
        } catch (Exception e) {
            Slog.e(TAG, "Failed deleting command key", e);
        }

        try {
            if (mFile.exists() && !mFile.delete()) {
                Slog.w(TAG, "Failed deleting command store file");
            }
        } catch (Exception e) {
            Slog.e(TAG, "Failed deleting command store file", e);
        }
    }
}
