package com.secureline.server;

import android.os.FileUtils;
import android.os.Process;
import android.util.AtomicFile;
import android.util.Slog;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;

public final class SecureLinePolicyStore {

    private static final String TAG =
            "SecureLinePolicyStore";

    /*
     * Persistent policy storage
     *
     * IMPORTANT:
     * /metadata survives factory reset / wipe.
     */

    private static final String POLICY_DIR =
            "/metadata/secureline";

    private static final String POLICY_FILE =
            POLICY_DIR + "/policy.json";

    private SecureLinePolicyStore() {}

    /*
     * Persist debugging policy
     *
     * Backend/admin panel is authoritative.
     */

    public static synchronized void saveDebuggingAllowed(
            boolean allowed
    ) {

        AtomicFile atomicFile =
                new AtomicFile(
                        new File(POLICY_FILE)
                );

        FileOutputStream fos = null;

        try {

            File dir = new File(POLICY_DIR);

            if (!dir.exists()) {

                if (!dir.mkdirs()) {

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

            /*
             * Secure directory permissions
             */

            FileUtils.setPermissions(
                    dir.getAbsolutePath(),
                    0700,
                    Process.SYSTEM_UID,
                    Process.SYSTEM_UID
            );

            JSONObject obj = loadPolicyInternal();

            obj.put(
                    "debugging_allowed",
                    allowed
            );

            fos = atomicFile.startWrite();

            byte[] data =
                    obj.toString(2)
                            .getBytes(StandardCharsets.UTF_8);

            fos.write(data);

            /*
             * Ensure durability
             */

            fos.flush();

            fos.getFD().sync();

            atomicFile.finishWrite(fos);

            fos = null;

            /*
             * Secure file permissions
             */

            FileUtils.setPermissions(
                    POLICY_FILE,
                    0600,
                    Process.SYSTEM_UID,
                    Process.SYSTEM_UID
            );

            Slog.i(
                    TAG,
                    "Persisted debugging policy="
                            + allowed
            );

        } catch (Throwable t) {

            Slog.e(
                    TAG,
                    "Failed saving policy",
                    t
            );

            if (fos != null) {

                atomicFile.failWrite(fos);
            }
        }
    }

    /*
     * Returns true only if policy exists.
     */

    public static synchronized boolean hasDebuggingPolicy() {

        try {

            JSONObject obj =
                    loadPolicyInternal();

            return obj.has(
                    "debugging_allowed"
            );

        } catch (Throwable t) {

            Slog.e(
                    TAG,
                    "Failed checking policy existence",
                    t
            );

            return false;
        }
    }

    /*
     * Load persistent debugging policy
     *
     * IMPORTANT:
     * Fail-secure model.
     *
     * If anything fails:
     * debugging becomes BLOCKED.
     */

    public static synchronized boolean loadDebuggingAllowed() {

        try {

            JSONObject obj =
                    loadPolicyInternal();

            return obj.optBoolean(
                    "debugging_allowed",
                    false
            );

        } catch (Throwable t) {

            Slog.e(
                    TAG,
                    "Failed loading policy",
                    t
            );

            /*
             * FAIL SECURE
             */

            return false;
        }
    }

    private static JSONObject loadPolicyInternal()
            throws Exception {

        File file =
                new File(POLICY_FILE);

        if (!file.exists()) {

            return new JSONObject();
        }

        FileInputStream fis =
                new FileInputStream(file);

        byte[] data;

        try {

            data = fis.readAllBytes();

        } finally {

            fis.close();
        }

        try {

            return new JSONObject(
                    new String(
                            data,
                            StandardCharsets.UTF_8
                    )
            );

        } catch (JSONException e) {

            Slog.e(
                    TAG,
                    "Corrupted policy file",
                    e
            );

            file.delete();

            return new JSONObject();
        }
    }
}
