package com.secureline.server;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.util.Base64;
import android.util.Slog;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

import javax.net.ssl.HttpsURLConnection;

public class SecureLineActivationClient {

    private static final String TAG =
            "SecureLineActivationClient";

    private static final String BASE_URL =
            "https://api-secureline.com";

    private static final int TIMEOUT_MS =
            15000;

    private static final int MAX_LOG_BODY =
            512;

    private final Context mContext;

    public SecureLineActivationClient(Context context) {
        mContext = context;
    }

    // ============================================================
    // STEP 1
    // NONCE
    // ============================================================

    public String requestNonce() throws Exception {

        final String requestId =
                UUID.randomUUID().toString();

        final URL url =
                new URL(
                        BASE_URL
                        + "/v1/device/activation/nonce"
                );

        HttpsURLConnection conn = null;

        try {

            conn =
                    openConnection(url);

            conn.setRequestMethod("POST");

            conn.setDoOutput(false);

            Slog.i(
                    TAG,
                    "nonce request start"
                            + " id=" + requestId
                            + " url=" + url
            );

            final int code =
                    conn.getResponseCode();

            final String body =
                    readResponse(conn, code);

            Slog.i(
                    TAG,
                    "nonce response"
                            + " id=" + requestId
                            + " http=" + code
                            + " body=" + safeLog(body)
            );

            if (code != 200) {

                throw new SecurityException(
                        "nonce failed"
                                + " http=" + code
                                + " body=" + safeLog(body)
                );
            }

            if (body == null || body.isEmpty()) {

                throw new SecurityException(
                        "nonce response empty"
                );
            }

            final JSONObject obj =
                    new JSONObject(body);

            final String nonce =
                    obj.getString("nonce");

            if (nonce == null || nonce.isEmpty()) {

                throw new SecurityException(
                        "nonce missing in response"
                );
            }

            return nonce;

        } finally {

            if (conn != null) {

                conn.disconnect();
            }
        }
    }

    // ============================================================
    // STEP 2
    // ACTIVATE
    // ============================================================

    public ActivationResponse activate(

            String activationCode,

            String nonce,

            byte[] attestationBlob

    ) throws Exception {

        if (activationCode == null
                || activationCode.isBlank()) {

            throw new IllegalArgumentException(
                    "activationCode empty"
            );
        }

        if (nonce == null
                || nonce.isBlank()) {

            throw new IllegalArgumentException(
                    "nonce empty"
            );
        }

        if (attestationBlob == null
                || attestationBlob.length == 0) {

            throw new IllegalArgumentException(
                    "attestationBlob empty"
            );
        }

        final String requestId =
                UUID.randomUUID().toString();

        final URL url =
                new URL(
                        BASE_URL
                        + "/v1/device/activate"
                );

        HttpsURLConnection conn = null;

        try {

            conn =
                    openConnection(url);

            conn.setRequestMethod("POST");

            conn.setRequestProperty(
                    "Content-Type",
                    "application/json; charset=UTF-8"
            );

            conn.setDoOutput(true);

            final JSONObject body =
                    new JSONObject();

            body.put(
                    "activation_code",
                    activationCode
            );

            body.put(
                    "nonce",
                    nonce
            );

            body.put(
                    "attestation",
                    Base64.encodeToString(
                            attestationBlob,
                            Base64.NO_WRAP
                    )
            );

            final byte[] json =
                    body.toString()
                            .getBytes(
                                    StandardCharsets.UTF_8
                            );

            conn.setFixedLengthStreamingMode(
                    json.length
            );

            Slog.i(
                    TAG,
                    "activate request start"
                            + " id=" + requestId
                            + " url=" + url
                            + " activationCode_len=" + activationCode.length()
                            + " attestation_bytes=" + attestationBlob.length
            );

            try (OutputStream os =
                         conn.getOutputStream()) {

                os.write(json);

                os.flush();
            }

            final int code =
                    conn.getResponseCode();

            final String response =
                    readResponse(conn, code);

            Slog.i(
                    TAG,
                    "activate response"
                            + " id=" + requestId
                            + " http=" + code
                            + " body=" + safeLog(response)
            );

            if (code != 200) {

                throw new SecurityException(
                        "activation failed"
                                + " http=" + code
                                + " body=" + safeLog(response)
                );
            }

            return parseActivationResponse(
                    response
            );

        } finally {

            if (conn != null) {

                conn.disconnect();
            }
        }
    }

    // ============================================================
    // CONNECTION
    // ============================================================

    private HttpsURLConnection openConnection(
            URL url
    ) throws Exception {

        final ConnectivityManager cm =
                (ConnectivityManager)
                        mContext.getSystemService(
                                Context.CONNECTIVITY_SERVICE
                        );

        if (cm == null) {

            throw new IllegalStateException(
                    "ConnectivityManager unavailable"
            );
        }

        final Network network =
                cm.getActiveNetwork();

        if (network == null) {

            throw new IllegalStateException(
                    "no active network"
            );
        }

        final NetworkCapabilities caps =
                cm.getNetworkCapabilities(network);

        if (caps == null) {

            throw new IllegalStateException(
                    "network capabilities null"
            );
        }

        if (!caps.hasCapability(
                NetworkCapabilities.NET_CAPABILITY_INTERNET
        )) {

            throw new IllegalStateException(
                    "network has no internet capability"
            );
        }

        Slog.i(
                TAG,
                "network capabilities"
                        + " internet="
                        + caps.hasCapability(
                        NetworkCapabilities.NET_CAPABILITY_INTERNET
                )
                        + " validated="
                        + caps.hasCapability(
                        NetworkCapabilities.NET_CAPABILITY_VALIDATED
                )
                        + " wifi="
                        + caps.hasTransport(
                        NetworkCapabilities.TRANSPORT_WIFI
                )
                        + " cellular="
                        + caps.hasTransport(
                        NetworkCapabilities.TRANSPORT_CELLULAR
                )
        );

        final HttpsURLConnection conn =
                (HttpsURLConnection)
                        network.openConnection(url);

        conn.setSSLSocketFactory(
                SecureLineTlsPinning.getPinnedFactory()
        );

        conn.setConnectTimeout(
                TIMEOUT_MS
        );

        conn.setReadTimeout(
                TIMEOUT_MS
        );

        conn.setUseCaches(false);

        conn.setDoInput(true);

        return conn;
    }

    // ============================================================
    // READ RESPONSE
    // ============================================================

    private String readResponse(

            HttpURLConnection conn,

            int code

    ) throws Exception {

        InputStream stream =
                code >= 400
                        ? conn.getErrorStream()
                        : conn.getInputStream();

        if (stream == null) {

            return "";
        }

        try (BufferedReader reader =
                     new BufferedReader(

                             new InputStreamReader(

                                     stream,

                                     StandardCharsets.UTF_8
                             )
                     )) {

            final StringBuilder sb =
                    new StringBuilder();

            String line;

            while ((line =
                    reader.readLine()) != null) {

                sb.append(line);
            }

            return sb.toString();
        }
    }

    // ============================================================
    // PARSE RESPONSE
    // ============================================================

    private ActivationResponse parseActivationResponse(

            String json

    ) throws Exception {

        if (json == null
                || json.isEmpty()) {

            throw new SecurityException(
                    "activation response empty"
            );
        }

        final JSONObject obj =
                new JSONObject(json);

        final ActivationResponse resp =
                new ActivationResponse();

        resp.deviceId =
                obj.getString("device_id");

        resp.supportId =
                obj.getString("support_id");

        resp.wipeToken =
                obj.getString("wipe_token");

        resp.plan =
                obj.getString("plan");

        resp.licensePayload =
                obj.optString(
                        "license_payload",
                        null
                );

        resp.licenseSignature =
                obj.optString(
                        "license_signature",
                        null
                );

        Slog.i(
                TAG,
                "activation parsed"
                        + " deviceId=" + resp.deviceId
                        + " supportId=" + resp.supportId
                        + " plan=" + resp.plan
                        + " hasLicensePayload="
                        + (resp.licensePayload != null)
                        + " hasLicenseSignature="
                        + (resp.licenseSignature != null)
        );

        return resp;
    }

    // ============================================================
    // SAFE LOG
    // ============================================================

    private static String safeLog(

            String value

    ) {

        if (value == null) {

            return "null";
        }

        if (value.length() <= MAX_LOG_BODY) {

            return value;
        }

        return value.substring(
                0,
                MAX_LOG_BODY
        ) + "...";
    }

    // ============================================================

    public static final class ActivationResponse {

        public String deviceId;

        public String supportId;

        public String wipeToken;

        public String plan;

        public String licensePayload;

        public String licenseSignature;
    }
}
