package com.okithaki.autoapp;

import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;

import org.json.JSONArray;
import org.json.JSONObject;

import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.UUID;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

/** Local-only encrypted store. V249529 adds persistent semantic de-duplication. */
public final class ExternalMessageStoreV249477 {
    private static final String PREFS = "ithaki_external_local_v249477";
    private static final String DATA = "encrypted_items";
    private static final String KEY_ALIAS = "ok_ithaki_external_messages_v249477";
    private static final Object LOCK = new Object();
    private ExternalMessageStoreV249477() {}

    private static SecretKey key() throws Exception {
        KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null);
        if (ks.containsAlias(KEY_ALIAS)) return ((KeyStore.SecretKeyEntry) ks.getEntry(KEY_ALIAS, null)).getSecretKey();
        KeyGenerator kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
        kg.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .setRandomizedEncryptionRequired(true).build());
        return kg.generateKey();
    }
    private static String encrypt(String plain) throws Exception {
        Cipher c=Cipher.getInstance("AES/GCM/NoPadding"); c.init(Cipher.ENCRYPT_MODE,key());
        JSONObject box=new JSONObject(); box.put("v",1);box.put("iv",Base64.encodeToString(c.getIV(),Base64.NO_WRAP));
        box.put("ct",Base64.encodeToString(c.doFinal(plain.getBytes(StandardCharsets.UTF_8)),Base64.NO_WRAP));return box.toString();
    }
    private static String decrypt(String boxText) throws Exception {
        if(boxText==null||boxText.trim().isEmpty())return "[]";JSONObject box=new JSONObject(boxText);
        Cipher c=Cipher.getInstance("AES/GCM/NoPadding");c.init(Cipher.DECRYPT_MODE,key(),new GCMParameterSpec(128,Base64.decode(box.optString("iv"),Base64.NO_WRAP)));
        return new String(c.doFinal(Base64.decode(box.optString("ct"),Base64.NO_WRAP)),StandardCharsets.UTF_8);
    }
    private static JSONArray rawList(Context context){try{return new JSONArray(decrypt(context.getSharedPreferences(PREFS,Context.MODE_PRIVATE).getString(DATA,"")));}catch(Exception e){return new JSONArray();}}
    private static void write(Context context,JSONArray arr)throws Exception{context.getSharedPreferences(PREFS,Context.MODE_PRIVATE).edit().putString(DATA,encrypt(arr.toString())).commit();}
    private static String norm(String v){return (v==null?"":v).trim().replaceAll("\\s+"," ").toLowerCase(Locale.ROOT);}
    private static long time(JSONObject x){return x.optLong("created_at",x.optLong("posted_at",x.optLong("updated_at",0L)));}
    private static String semanticKey(JSONObject x){
        String nk=x.optString("notification_key","").trim();if(!nk.isEmpty())return "key|"+nk;
        String lp=x.optString("local_path","").trim();if(!lp.isEmpty())return "file|"+lp;
        String id=x.optString("id","").trim();if("shared".equals(x.optString("source"))&&!id.isEmpty())return "shared|"+id;
        long minute=Math.max(0L,time(x)/60000L);
        return "legacy|"+norm(x.optString("package_name",x.optString("source","other")))+"|"+norm(x.optString("title",""))+"|"+norm(x.optString("body",""))+"|"+minute;
    }
    private static void preserve(JSONObject incoming,JSONObject old,String key)throws Exception{if((!incoming.has(key)||incoming.isNull(key))&&old.has(key)&&!old.isNull(key))incoming.put(key,old.get(key));}
    private static JSONObject merge(JSONObject older,JSONObject newer)throws Exception{
        JSONObject out=new JSONObject(older==null?"{}":older.toString());if(newer!=null){java.util.Iterator<String> it=newer.keys();while(it.hasNext()){String k=it.next();out.put(k,newer.get(k));}}
        if(older!=null){for(String k:new String[]{"important","archived","note","action_note","action_status","due_at","opened_at","created_at"})preserve(out,older,k);if(!newer.has("unread"))preserve(out,older,"unread");}
        return out;
    }
    private static JSONArray dedupeArray(JSONArray src){
        try{
            LinkedHashMap<String,JSONObject> map=new LinkedHashMap<>();
            for(int i=0;i<src.length();i++){JSONObject x=src.optJSONObject(i);if(x==null)continue;String k=semanticKey(x),id=x.optString("id","").trim();if(k.isEmpty())k="id|"+id;
                JSONObject old=map.get(k);if(old==null){map.put(k,new JSONObject(x.toString()));continue;}
                JSONObject newer=time(x)>=time(old)?x:old,older=newer==x?old:x;map.put(k,merge(older,newer));
            }
            ArrayList<JSONObject> list=new ArrayList<>(map.values());Collections.sort(list,new Comparator<JSONObject>(){public int compare(JSONObject a,JSONObject b){return Long.compare(time(b),time(a));}});
            JSONArray out=new JSONArray();for(JSONObject x:list)out.put(x);return out;
        }catch(Exception e){return src;}
    }
    public static JSONArray list(Context context){synchronized(LOCK){try{JSONArray raw=rawList(context),clean=dedupeArray(raw);if(clean.length()!=raw.length())write(context,clean);return clean;}catch(Exception e){return new JSONArray();}}}
    public static int dedupeNow(Context context){synchronized(LOCK){try{JSONArray raw=rawList(context),clean=dedupeArray(raw);write(context,clean);return Math.max(0,raw.length()-clean.length());}catch(Exception e){return 0;}}}
    public static JSONObject find(Context context,String id){synchronized(LOCK){JSONArray arr=list(context);for(int i=0;i<arr.length();i++){JSONObject x=arr.optJSONObject(i);if(x!=null&&id.equals(x.optString("id")))try{return new JSONObject(x.toString());}catch(Exception ignored){}}return new JSONObject();}}
    public static JSONObject put(Context context,JSONObject rawIncoming){synchronized(LOCK){try{
        JSONObject incoming=new JSONObject(rawIncoming==null?"{}":rawIncoming.toString());JSONArray arr=dedupeArray(rawList(context));String id=incoming.optString("id","").trim();if(id.isEmpty()){id="local477_"+UUID.randomUUID();incoming.put("id",id);}incoming.put("local_only",true);incoming.put("updated_at",System.currentTimeMillis());
        String key=semanticKey(incoming);boolean found=false;for(int i=0;i<arr.length();i++){JSONObject old=arr.optJSONObject(i);if(old==null)continue;boolean sameId=id.equals(old.optString("id"));boolean sameKey=key.equals(semanticKey(old));if(sameId||sameKey){boolean sameContent=incoming.optString("title").equals(old.optString("title"))&&incoming.optString("body").equals(old.optString("body"))&&incoming.optString("package_name").equals(old.optString("package_name"));JSONObject merged=merge(old,incoming);if(sameContent)merged.put("unread",old.optBoolean("unread",incoming.optBoolean("unread",true)));arr.put(i,merged);incoming=merged;found=true;break;}}
        if(!found)arr.put(incoming);write(context,dedupeArray(arr));return incoming;
    }catch(Exception e){return new JSONObject();}}}
    public static boolean delete(Context context,String id){synchronized(LOCK){try{JSONArray src=list(context),dst=new JSONArray();for(int i=0;i<src.length();i++){JSONObject x=src.optJSONObject(i);if(x!=null&&!id.equals(x.optString("id")))dst.put(x);}write(context,dst);return true;}catch(Exception e){return false;}}}
    public static boolean clear(Context context){synchronized(LOCK){try{write(context,new JSONArray());return true;}catch(Exception e){return false;}}}
    public static String encryptedBlob(Context context){return context.getSharedPreferences(PREFS,Context.MODE_PRIVATE).getString(DATA,"");}
    public static void consent(Context context,String source,boolean enabled){context.getSharedPreferences(PREFS,Context.MODE_PRIVATE).edit().putBoolean("consent_"+source,enabled).apply();}
    public static boolean consent(Context context,String source){return context.getSharedPreferences(PREFS,Context.MODE_PRIVATE).getBoolean("consent_"+source,false);}
}
