package chat.vojo.app;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;

import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KeyTemplates;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.aead.AeadConfig;
import com.google.crypto.tink.integration.android.AndroidKeysetManager;

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

/**
 * The ONLY store for the user's proxy credentials (proxy_support.md §15 #4).
 *
 * The host/port/username/password blob is encrypted with a Tink AEAD primitive
 * (AES-256-GCM) whose key-encryption key lives in the AndroidKeyStore — so the
 * plaintext never touches disk, localStorage, settingsAtom, or any JS state.
 * We deliberately do NOT use the deprecated
 * androidx.security EncryptedSharedPreferences.
 *
 * allowBackup=false (AndroidManifest) already excludes this prefs file from
 * Auto Backup / adb backup. Nothing here is ever logged.
 *
 * On logout the blob is wiped ({@link #wipe()}).
 */
final class ProxyConfigStore {

    private static final String TAG = "ProxyConfigStore";

    // Dedicated prefs file — NOT the vojo_poll_state used by the push/polling
    // path (proxy_android_impl.md §3: do not reuse the plaintext pattern).
    static final String PREFS = "vojo_proxy_store";
    private static final String KEYSET_NAME = "vojo_proxy_keyset";
    private static final String MASTER_KEY_URI = "android-keystore://vojo_proxy_kek";
    private static final byte[] AAD = "vojo-proxy-v1".getBytes();

    private static final String KEY_BLOB = "creds_blob";
    private static final String KEY_ENABLED = "enabled";
    // Epoch-ms of the most recent background native send that was refused
    // because the relay couldn't start (fail-closed). Non-secret, persisted so
    // it survives the process death that's typical for a Worker/receiver wake —
    // the UI reads it to warn that background traffic was blocked (F17).
    private static final String KEY_NATIVE_BLOCKED_AT = "native_blocked_at";

    /** Plain value object for the decrypted config. Never logged. */
    static final class ProxyCreds {
        final String type; // "http" | "socks5"
        final String host;
        final int port;
        final String username; // nullable / empty = no auth
        final String password; // nullable / empty

        ProxyCreds(String type, String host, int port, String username, String password) {
            this.type = type;
            this.host = host;
            this.port = port;
            this.username = username;
            this.password = password;
        }

        boolean hasAuth() {
            return username != null && !username.isEmpty();
        }
    }

    private final Context appContext;
    private volatile Aead aead;

    ProxyConfigStore(Context context) {
        this.appContext = context.getApplicationContext();
    }

    private SharedPreferences prefs() {
        return appContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
    }

    // Lazily build the AEAD primitive (Keystore I/O). Synchronized so a
    // concurrent FCM/Worker/plugin first-touch can't race the keyset creation
    // (a documented Tink corruption hazard on some devices).
    private synchronized Aead aead() {
        if (aead != null) return aead;
        try {
            AeadConfig.register();
            KeysetHandle handle = new AndroidKeysetManager.Builder()
                .withSharedPref(appContext, KEYSET_NAME, PREFS)
                .withKeyTemplate(KeyTemplates.get("AES256_GCM"))
                .withMasterKeyUri(MASTER_KEY_URI)
                .build()
                .getKeysetHandle();
            aead = handle.getPrimitive(Aead.class);
        } catch (Throwable t) {
            // Don't log the cause's message verbatim — be conservative around
            // anything crypto. The store degrades to "unconfigured".
            Log.w(TAG, "aead init failed");
            aead = null;
        }
        return aead;
    }

    boolean isConfigured() {
        return prefs().contains(KEY_BLOB);
    }

    boolean isEnabled() {
        return prefs().getBoolean(KEY_ENABLED, false);
    }

    void setEnabled(boolean enabled) {
        prefs().edit().putBoolean(KEY_ENABLED, enabled).apply();
    }

    long getNativeBlockedAt() {
        return prefs().getLong(KEY_NATIVE_BLOCKED_AT, 0);
    }

    void setNativeBlockedAt(long epochMs) {
        prefs().edit().putLong(KEY_NATIVE_BLOCKED_AT, epochMs).apply();
    }

    /** Encrypt + persist the credentials. Returns false on any crypto failure. */
    boolean save(ProxyCreds creds) {
        Aead a = aead();
        if (a == null) return false;
        try {
            JSONObject o = new JSONObject();
            o.put("type", creds.type);
            o.put("host", creds.host);
            o.put("port", creds.port);
            if (creds.username != null) o.put("username", creds.username);
            if (creds.password != null) o.put("password", creds.password);
            byte[] cipher = a.encrypt(o.toString().getBytes("UTF-8"), AAD);
            prefs().edit()
                .putString(KEY_BLOB, Base64.encodeToString(cipher, Base64.NO_WRAP))
                .apply();
            return true;
        } catch (Throwable t) {
            Log.w(TAG, "save failed");
            return false;
        }
    }

    /** Decrypt the stored credentials, or null if absent / undecryptable. */
    ProxyCreds load() {
        String b64 = prefs().getString(KEY_BLOB, null);
        if (b64 == null) return null;
        Aead a = aead();
        if (a == null) return null;
        try {
            byte[] plain = a.decrypt(Base64.decode(b64, Base64.NO_WRAP), AAD);
            JSONObject o = new JSONObject(new String(plain, "UTF-8"));
            return new ProxyCreds(
                o.optString("type", "socks5"),
                o.optString("host", ""),
                o.optInt("port", 0),
                o.has("username") ? o.optString("username", null) : null,
                o.has("password") ? o.optString("password", null) : null
            );
        } catch (JSONException | RuntimeException e) {
            Log.w(TAG, "load parse failed");
            return null;
        } catch (Throwable t) {
            Log.w(TAG, "load decrypt failed");
            return null;
        }
    }

    /** Wipe the credentials + enabled flag (logout). */
    void wipe() {
        prefs().edit().remove(KEY_BLOB).remove(KEY_ENABLED).remove(KEY_NATIVE_BLOCKED_AT).apply();
    }
}
