package com.grigowashere.aismap.controllers;

import android.content.Context;
import android.util.Log;
import com.grigowashere.aismap.models.Vessel;
import com.grigowashere.aismap.models.AISVessel;
import com.grigowashere.aismap.maps.MapInterface;
import com.grigowashere.aismap.data.Repository;
import com.grigowashere.aismap.data.mapper.AISVesselMapper;
import com.grigowashere.aismap.services.NotificationService;
import com.grigowashere.aismap.utils.SettingsManager;
import com.grigowashere.aismap.ui.UIDataChangeNotifier;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Главный контроллер приложения
 * Координирует работу всех компонентов
 * Использует гибридный подход: координаты через Location API, остальное через NMEA
 */
public class AppController implements 
    NMEAParser.NMEAParserListener,
    UDPListener.UDPListenerCallback,
    AndroidNMEAListener.NMEAMessageCallback,
    GPSLocationListener.LocationCallback,
    MapInterface.MarkerClickListener {
    
    private static final String TAG = "AppController";
    
    private Context context;
    private NMEAParser nmeaParser;
    private UDPListener udpListener;
    private AndroidNMEAListener androidNmeaListener;
    private GPSLocationListener gpsLocationListener;
    private MapInterface mapInterface;
    
    private Vessel ownVessel;
    private List<AISVessel> aisVessels;
    private ExecutorService executor;
    private com.grigowashere.aismap.data.Repository repository;
    private NotificationService notificationService;
    private SettingsManager settingsManager;
    private VesselPathController pathController;
    // VesselPathController для каждого AIS судна (ключ: MMSI)
    private final Map<String, VesselPathController> aisPathControllers = new HashMap<>();
    
    private boolean isUDPEnabled;
    private boolean isAndroidNMEAEnabled;
    private boolean isUDPNMEAEnabled;
    private boolean isGPSLocationEnabled;
    private int udpPort;
    private String dataMode;
    
    // Время последнего получения сообщений ($ GPS) и (! AIS) в elapsedRealtime
    private long lastGPSMessageRealtimeMs;
    private long lastAISMessageRealtimeMs;
    
    // Периодическая очистка БД от устаревших AIS целей
    private android.os.Handler dbCleanupHandler;
    private Runnable dbCleanupRunnable;
    private static final long DB_CLEANUP_INTERVAL = 60000; // 1 минута
    
    // Единый Handler для всех UI операций (предотвращение утечек Handler'ов)
    private android.os.Handler uiHandler;
    
    // Индикаторы UI данных для централизованного throttling
    private UIDataChangeNotifier uiDataNotifier;
    
    // Callback для обновления UI (legacy для MainActivity)
    private UIUpdateCallback uiUpdateCallback;
    
    // Диагностика сервисов
    private long lastServiceLogTime = 0;
    
    public interface UIUpdateCallback {
        void onVesselPositionUpdated(Vessel vessel);
        void onGPSQualityUpdated(Vessel vessel);
    }
    
    /**
     * Расширенный интерфейс для дополнительных UI событий
     */
    public interface ExtendedUIUpdateCallback extends UIUpdateCallback {
        void onShowOwnVesselBottomSheet();
        void onShowAISVesselInfo(AISVessel vessel);
        void onUpdateCompass(float azimuth, List<AISVessel> nearbyVessels);
    }
    
    public AppController(Context context) {
        this.context = context;
        this.ownVessel = new Vessel();
        this.aisVessels = new ArrayList<>();
        this.executor = Executors.newCachedThreadPool();
        this.repository = new com.grigowashere.aismap.data.Repository(context);
        this.notificationService = new NotificationService(context);
        this.settingsManager = new SettingsManager(context);
        this.pathController = new VesselPathController(context, settingsManager);
        
        // Инициализируем Handler для периодической очистки БД
        this.dbCleanupHandler = new android.os.Handler(android.os.Looper.getMainLooper());
        this.dbCleanupRunnable = this::performDatabaseCleanup;
        
        // Инициализируем единый UI Handler
        this.uiHandler = new android.os.Handler(android.os.Looper.getMainLooper());
        
        initializeControllers();
    }
    
    /**
     * Инициализирует все контроллеры
     */
    private void initializeControllers() {
        // Инициализация парсера NMEA
        nmeaParser = new NMEAParser();
        nmeaParser.setListener(this);
        
        // Инициализация GPS Location Listener (для координат)
        gpsLocationListener = new GPSLocationListener(context);
        gpsLocationListener.setCallback(this);
        
        // Связываем NMEA парсер с GPS Location Listener для гибридного режима
        nmeaParser.setGPSLocationListener(gpsLocationListener);
        nmeaParser.setHybridMode(true);
        
        // Инициализация UDP слушателя (порт 10110 - стандартный для AIS)
        udpPort = 10110;
        udpListener = new UDPListener(udpPort);
        udpListener.setCallback(this);
        
        // Инициализация Android NMEA слушателя (для курса, скорости, DOP)
        androidNmeaListener = new AndroidNMEAListener(context);
        androidNmeaListener.setCallback(this);

        // Восстанавливаем данные из БД при старте АСИНХРОННО
        Log.i(TAG, "🔄 Запускаем асинхронное восстановление данных из БД...");
        executor.execute(() -> {
            try {
                Log.d(TAG, "📊 Загружаем данные судна из БД...");
                com.grigowashere.aismap.data.entity.VesselEntity latest = repository.getLatestOwnVesselSync();
                if (latest != null) {
                    ownVessel.setLatitude(latest.latitude);
                    ownVessel.setLongitude(latest.longitude);
                    ownVessel.setAccuracy(latest.accuracy);
                    ownVessel.setFixTime(latest.fixTime);
                    Log.d(TAG, "✅ Данные судна восстановлены: " + latest.latitude + "," + latest.longitude);
                } else {
                    Log.d(TAG, "ℹ️ Нет данных судна в БД");
                }
                
                Log.d(TAG, "🚢 Загружаем AIS суда из БД...");
                java.util.List<com.grigowashere.aismap.data.entity.AISVesselEntity> list = repository.getAllAISSync();
                if (list != null && !list.isEmpty()) {
                    synchronized (aisVessels) {
                        aisVessels.clear(); // Очищаем перед восстановлением
                        for (com.grigowashere.aismap.data.entity.AISVesselEntity entity : list) {
                            // Используем маппер для полного восстановления всех полей
                            AISVessel vessel = AISVesselMapper.toModel(entity);
                            aisVessels.add(vessel);
                            Log.d(TAG, "AIS судно восстановлено из БД с полными данными: " + vessel.getMmsi());
                        }
                    }
                    Log.i(TAG, "✅ Восстановлено " + list.size() + " AIS судов из БД с полными данными");
                } else {
                    Log.d(TAG, "ℹ️ Нет AIS судов в БД");
                }
                
                // Уведомляем UI о восстановлении данных (если mapInterface уже установлен)
                uiHandler.post(() -> {
                    if (mapInterface != null) {
                        Log.i(TAG, "🔄 Уведомляем UI о восстановленных данных...");
                        // Восстановление маркеров будет выполнено через setMapInterface()
                        // когда он будет вызван из MainActivity
                    } else {
                        Log.d(TAG, "⏳ mapInterface еще не установлен, восстановление отложено");
                    }
                });
                
            } catch (Exception e) {
                Log.e(TAG, "❌ Ошибка восстановления данных из БД: " + e.getMessage(), e);
            }
        });
    }
    
    /**
     * Устанавливает интерфейс карты
     */
    public void setMapInterface(MapInterface mapInterface) {
        Log.i(TAG, "setMapInterface вызван: " + (mapInterface != null ? "mapInterface установлен" : "mapInterface == null"));
        this.mapInterface = mapInterface;
        if (mapInterface != null) {
            Log.i(TAG, "Устанавливаем MarkerClickListener в MapInterface");
            mapInterface.setMarkerClickListener(this);
            Log.i(TAG, "MarkerClickListener установлен, теперь можно создавать маркеры");

            // Уведомляем UI Coordinator о восстановлении данных
            if (uiDataNotifier != null) {
                Log.i(TAG, "🔄 Восстановление данных через UI Coordinator");
                
                // Восстанавливаем позицию собственного судна
                if (ownVessel != null && ownVessel.getLatitude() != 0 && ownVessel.getLongitude() != 0) {
                    Log.i(TAG, "📍 Восстанавливаем позицию судна: " + ownVessel.getLatitude() + "," + ownVessel.getLongitude());
                    uiDataNotifier.onVesselPositionChanged(ownVessel);
                } else {
                    Log.w(TAG, "⚠️ Судно не имеет валидных координат для восстановления");
                }
                
                // Восстанавливаем AIS суда
                if (aisVessels != null && !aisVessels.isEmpty()) {
                    Log.i(TAG, "🚢 Восстанавливаем " + aisVessels.size() + " AIS судов");
                    synchronized (aisVessels) {
                        for (AISVessel v : aisVessels) {
                            Log.d(TAG, "  - AIS судно: " + v.getMmsi() + " на " + v.getLatitude() + "," + v.getLongitude());
                            uiDataNotifier.onAISVesselChanged(v);
                        }
                    }
                    Log.i(TAG, "✅ " + aisVessels.size() + " AIS судов отправлено в UI Coordinator");
                } else {
                    Log.i(TAG, "ℹ️ Нет AIS судов для восстановления");
                }
            } else {
                Log.w(TAG, "❌ uiDataNotifier не установлен при восстановлении данных - маркеры НЕ будут восстановлены!");
            }
        }
    }
    
    /**
     * Устанавливает индикатор изменений данных для централизованного UI throttling
     */
    public void setUIDataChangeNotifier(UIDataChangeNotifier notifier) {
        this.uiDataNotifier = notifier;
        Log.i(TAG, "UIDataChangeNotifier установлен: " + (notifier != null ? "success" : "null"));
    }
    
    /**
     * Устанавливает callback для обновления UI (legacy для MainActivity)
     */
    public void setUIUpdateCallback(UIUpdateCallback callback) {
        this.uiUpdateCallback = callback;
    }
    
    /**
     * Запускает все слушатели
     */
    public void startAllListeners() {
        // GPS Location Listener запускается в главном потоке
        if (isGPSLocationEnabled) {
            gpsLocationListener.startListening();
        }
        
        // Android NMEA слушатель должен запускаться в главном потоке
        if (isAndroidNMEAEnabled) {
            androidNmeaListener.startListening();
        }
        
        // UDP слушатель запускается в фоновом потоке
        if (isUDPEnabled && executor != null && !executor.isShutdown()) {
            try {
                executor.execute(() -> {
                    udpListener.start();
                });
            } catch (java.util.concurrent.RejectedExecutionException e) {
                Log.w(TAG, "Thread pool is shutting down, cannot start UDP listener: " + e.getMessage());
            }
        }
        
        // Запускаем периодическую очистку БД от устаревших AIS целей
        startDatabaseCleanup();
    }
    

    
    /**
     * Останавливает все слушатели
     */
    public void stopAllListeners() {
        // Останавливаем периодическую очистку БД
        stopDatabaseCleanup();
        
        executor.execute(() -> {
            udpListener.stop();
            androidNmeaListener.stopListening();
            gpsLocationListener.stopListening();
        });
    }
    
    /**
     * Включает/выключает UDP слушатель
     */
    public void setUDPEnabled(boolean enabled) {
        this.isUDPEnabled = enabled;
        if (enabled && !udpListener.isRunning()) {
            udpListener.start();
        } else if (!enabled && udpListener.isRunning()) {
            udpListener.stop();
        }
    }
    
    /**
     * Включает/выключает Android NMEA слушатель
     */
    public void setAndroidNMEAEnabled(boolean enabled) {
        Log.i(TAG, "🔄 setAndroidNMEAEnabled: " + enabled);
        this.isAndroidNMEAEnabled = enabled;
        
        // Android NMEA слушатель управляется в главном потоке
        if (enabled && !androidNmeaListener.isListening()) {
            Log.i(TAG, "🚀 Запускаем Android NMEA слушатель...");
            boolean success = androidNmeaListener.startListening();
            if (success) {
                Log.i(TAG, "✅ Android NMEA слушатель успешно запущен");
            } else {
                Log.e(TAG, "❌ Не удалось запустить Android NMEA слушатель");
            }
        } else if (!enabled && androidNmeaListener.isListening()) {
            Log.i(TAG, "⏹️ Останавливаем Android NMEA слушатель...");
            androidNmeaListener.stopListening();
        }
    }
    
    /**
     * Включает/выключает GPS Location слушатель
     */
    public void setGPSLocationEnabled(boolean enabled) {
        Log.i(TAG, "🔄 setGPSLocationEnabled: " + enabled);
        this.isGPSLocationEnabled = enabled;
        
        if (enabled && !gpsLocationListener.isListening()) {
            Log.i(TAG, "🚀 Запускаем GPS Location слушатель...");
            boolean success = gpsLocationListener.startListening();
            if (success) {
                Log.i(TAG, "✅ GPS Location слушатель успешно запущен");
            } else {
                Log.e(TAG, "❌ Не удалось запустить GPS Location слушатель");
            }
        } else if (!enabled && gpsLocationListener.isListening()) {
            Log.i(TAG, "⏹️ Останавливаем GPS Location слушатель...");
            gpsLocationListener.stopListening();
        }
    }
    

    
    /**
     * Отправляет данные по UDP
     */
    public void sendUDPData(String data, String address, int port) {
        udpListener.sendData(data, address, port);
    }
    
    /**
     * Проверяет, включен ли UDP слушатель
     */
    public boolean isUDPEnabled() {
        return isUDPEnabled;
    }
    
    /**
     * Проверяет, включен ли Android NMEA слушатель
     */
    public boolean isAndroidNMEAEnabled() {
        return isAndroidNMEAEnabled;
    }
    
    /**
     * Проверяет, включен ли GPS Location слушатель
     */
    public boolean isGPSLocationEnabled() {
        return isGPSLocationEnabled;
    }
    
    /**
     * Обновляет данные нашего судна при клике по маркеру
     */
    private void updateOwnVesselData(Vessel vessel) {
        if (vessel != null) {
            // Обновляем только те данные, которые могут быть актуальными
            // Координаты и основная информация уже обновляются через GPS
            if (vessel.getCourse() > 0) {
                ownVessel.setCourse(vessel.getCourse());
                updateCompass(); // Обновляем компас при изменении курса
            }
            if (vessel.getSpeed() > 0) {
                ownVessel.setSpeed(vessel.getSpeed());
            }
            if (vessel.getSatellites() > 0) {
                ownVessel.setSatellites(vessel.getSatellites());
            }
            if (vessel.getAltitude() != 0) {
                ownVessel.setAltitude(vessel.getAltitude());
            }
            if (vessel.getPdop() > 0) {
                ownVessel.setPdop(vessel.getPdop());
                ownVessel.setHdop(vessel.getHdop());
                ownVessel.setVdop(vessel.getVdop());
            }
        }
    }
    
    // Реализация LocationCallback (GPS Location Listener)
    
    @Override
    public void onLocationUpdated(Vessel vessel) {
        Log.i(TAG, "📍 GPS Location обновлен: lat=" + vessel.getLatitude() + 
              ", lon=" + vessel.getLongitude() + 
              ", accuracy=" + vessel.getAccuracy() + "м");
        
        // Обновляем координаты нашего судна
        ownVessel.setLatitude(vessel.getLatitude());
        ownVessel.setLongitude(vessel.getLongitude());
        ownVessel.setAccuracy(vessel.getAccuracy());
        ownVessel.setFixTime(vessel.getFixTime());
        ownVessel.setFixQuality(vessel.getFixQuality());
        
        // Добавляем точку в путь судна
        if (pathController != null) {
            boolean pointAdded = pathController.addPathPoint(
                vessel.getLongitude(), 
                vessel.getLatitude(), 
                (float) ownVessel.getSpeed()
            );
                if (pointAdded) {
                    Log.d(TAG, "Точка пути добавлена из GPS: " + pathController.getPathPointsCount() + " точек");
                }
            }
        
        // Сохраняем позицию в локальную БД
        try {
            com.grigowashere.aismap.data.entity.VesselEntity ve = new com.grigowashere.aismap.data.entity.VesselEntity();
            ve.latitude = ownVessel.getLatitude();
            ve.longitude = ownVessel.getLongitude();
            ve.accuracy = ownVessel.getAccuracy();
            ve.fixTime = ownVessel.getFixTime();
            repository.upsertOwnVessel(ve);
        } catch (Exception e) {
            Log.e(TAG, "Ошибка сохранения позиции в БД: " + e.getMessage(), e);
        }

        // Обновляем UI через callback
        if (uiUpdateCallback != null) {
            uiUpdateCallback.onVesselPositionUpdated(ownVessel);
        }
        
        // Уведомляем UI Coordinator об изменении позиции судна (централизованный throttling)
        if (uiDataNotifier != null) {
            Log.d(TAG, "Уведомляем UI Coordinator об изменении позиции судна");
            uiDataNotifier.onVesselPositionChanged(ownVessel);
        } else {
            Log.w(TAG, "uiDataNotifier не установлен, пропускаем UI обновление");
        }
    }
    
    @Override
    public void onGPSStatusChanged(int status) {
        Log.i(TAG, "GPS статус изменился: " + status);
    }
    
    // Реализация NMEAParserListener
    
    @Override
    public void onVesselUpdated(Vessel vessel) {
        // Сокращаем шум логов: подробности обновления судна убраны
        
        // Обновляем координаты, если они есть (для режима "только NMEA")
        if (vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
            ownVessel.setLatitude(vessel.getLatitude());
            ownVessel.setLongitude(vessel.getLongitude());
            // Сокращаем шум логов: координаты обновлены (без детализации)
            
            // Добавляем точку в путь судна
            if (pathController != null) {
                boolean pointAdded = pathController.addPathPoint(
                    vessel.getLongitude(), 
                    vessel.getLatitude(), 
                    (float) vessel.getSpeed()
                );
                // Убираем лог о добавлении каждой точки пути
            }
        }
        
        // Обновляем дополнительные данные
        if (vessel.getCourse() > 0) {
            ownVessel.setCourse(vessel.getCourse());
            updateCompass(); // Обновляем компас при изменении курса
        }
        if (vessel.getSpeed() > 0) {
            ownVessel.setSpeed(vessel.getSpeed());
        }
        if (vessel.getSatellites() > 0) {
            ownVessel.setSatellites(vessel.getSatellites());
        }
        if (vessel.getAltitude() != 0) {
            ownVessel.setAltitude(vessel.getAltitude());
        }
        
        // Сокращаем шум логов: сводка NMEA обновлений убрана
        
        // Обновляем карту в главном потоке
        if (mapInterface != null) {
            // Сокращаем шум логов: убираем информационные логи карты
            uiHandler.post(() -> {
                try {
                    mapInterface.updateOwnVesselPosition(ownVessel);
                } catch (Exception e) {
                    Log.e(TAG, "Ошибка обновления позиции на карте из NMEA: " + e.getMessage(), e);
                }
            });
        }
        
        // Обновляем UI
        if (uiUpdateCallback != null) {
            uiUpdateCallback.onVesselPositionUpdated(ownVessel);
        }
    }
    
    @Override
    public void onDOPUpdated(double pdop, double hdop, double vdop) {
        // Убираем шумный лог DOP обновлений
        
        // Обновляем DOP значения
        ownVessel.setPdop(pdop);
        ownVessel.setHdop(hdop);
        ownVessel.setVdop(vdop);
        
        // Обновляем UI
        if (uiUpdateCallback != null) {
            uiUpdateCallback.onGPSQualityUpdated(ownVessel);
        }
    }
    
    @Override
    public void onAISVesselUpdated(AISVessel vessel) {
        // Проверяем, есть ли уже такое судно
        AISVessel existingVessel = findAISVesselByMMSI(vessel.getMmsi());
        
        if (existingVessel != null) {
            // Если пришло новое safety-сообщение (тип 14), уведомим пользователя
            if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
                String prev = existingVessel.getLastSafetyMessage();
                String curr = vessel.getLastSafetyMessage();
                if (prev == null || !prev.equals(curr)) {
                    if (notificationService != null && notificationService.areNotificationsEnabled()) {
                        notificationService.notifySafetyMessage(vessel.getMmsi(), curr);
                    }
                }
                existingVessel.setLastSafetyMessage(curr);
            }
            // Обновляем существующее судно
            existingVessel.updatePosition(
                vessel.getLatitude(), 
                vessel.getLongitude(), 
                vessel.getCourse(), 
                vessel.getSpeed()
            );
            try {
                // Используем маппер для полной конвертации всех полей
                com.grigowashere.aismap.data.entity.AISVesselEntity entity = AISVesselMapper.toEntity(existingVessel);
                repository.upsertAIS(entity);
                Log.d(TAG, "AIS судно сохранено в БД с полными данными: " + existingVessel.getMmsi());
            } catch (Exception e) {
                Log.e(TAG, "Ошибка апсерта AIS в БД: " + e.getMessage(), e);
            }
            
            // Добавляем точку в путь AIS судна
            addAISVesselPathPoint(existingVessel);
            
            // Уведомляем UI Coordinator об обновлении AIS судна
            if (uiDataNotifier != null) {
                Log.d(TAG, "Уведомляем UI Coordinator об обновлении AIS судна: " + existingVessel.getMmsi());
                uiDataNotifier.onAISVesselChanged(existingVessel);
            } else {
                Log.w(TAG, "uiDataNotifier не установлен, пропускаем AIS обновление");
            }
        } else {
            // Добавляем новое судно
            synchronized (aisVessels) {
                aisVessels.add(vessel);
            }

            // Если это новое судно сразу пришло с safety-сообщением — уведомим
            if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
                if (notificationService != null && notificationService.areNotificationsEnabled()) {
                    notificationService.notifySafetyMessage(vessel.getMmsi(), vessel.getLastSafetyMessage());
                }
            }
            
            // Воспроизводим уведомление о новой цели
            if (notificationService != null && notificationService.areNotificationsEnabled()) {
                notificationService.notifyNewAISTarget();
                Log.i(TAG, "🔔 Уведомление о новой AIS цели: " + vessel.getMmsi());
            }
            
            try {
                // Используем маппер для полной конвертации всех полей
                com.grigowashere.aismap.data.entity.AISVesselEntity entity = AISVesselMapper.toEntity(vessel);
                repository.upsertAIS(entity);
                Log.d(TAG, "Новое AIS судно сохранено в БД с полными данными: " + vessel.getMmsi());
            } catch (Exception e) {
                Log.e(TAG, "Ошибка апсерта AIS в БД: " + e.getMessage(), e);
            }
            
            // Добавляем точку в путь нового AIS судна
            addAISVesselPathPoint(vessel);
            
            // Уведомляем UI Coordinator о новом AIS судне
            if (uiDataNotifier != null) {
                Log.d(TAG, "Уведомляем UI Coordinator о новом AIS судне: " + vessel.getMmsi());
                uiDataNotifier.onAISVesselChanged(vessel);
            } else {
                Log.w(TAG, "uiDataNotifier не установлен, пропускаем добавление AIS судна");
            }
        }
        
        // Обновляем компас с ближайшими судами
        updateCompass();
        
        Log.i(TAG, "AIS судно обновлено: " + vessel);
    }
    
    @Override
    public void onParseError(String error) {
        Log.e(TAG, "Ошибка парсинга NMEA: " + error);
    }
    
    /**
     * Обновляет компас с текущим азимутом и ближайшими судами
     */
    private void updateCompass() {
        if (uiUpdateCallback instanceof ExtendedUIUpdateCallback) {
            float azimuth = (float) ownVessel.getCourse();
            List<AISVessel> nearbyVessels = getNearbyVessels();
            
            // Используем существующий uiHandler вместо создания нового
            uiHandler.post(() -> {
                ((ExtendedUIUpdateCallback) uiUpdateCallback).onUpdateCompass(azimuth, nearbyVessels);
            });
        }
    }
    
    /**
     * Получает список ближайших судов (в пределах 10 км)
     */
    private List<AISVessel> getNearbyVessels() {
        List<AISVessel> nearby = new ArrayList<>();
        double maxDistance = 10000; // 10 км в метрах
        
        for (AISVessel vessel : aisVessels) {
            double distance = com.grigowashere.aismap.utils.GeoUtils.calculateDistance(ownVessel, vessel);
            if (distance <= maxDistance) {
                nearby.add(vessel);
            }
        }
        
        return nearby;
    }
    
    // Реализация UDPListenerCallback
    
    @Override
    public void onDataReceived(String data, String sourceAddress, int sourcePort) {
        // Диагностика: логируем каждые 10 секунд
        long now = System.currentTimeMillis();
        if (now - lastServiceLogTime > 10000) {
            Log.d(TAG, "📡 AppController: UDP данные получены от " + sourceAddress + ":" + sourcePort);
            lastServiceLogTime = now;
        }
        
        // Парсим полученные данные как NMEA В ФОНОВОМ ПОТОКЕ
        if (executor != null && !executor.isShutdown()) {
            try {
                executor.execute(() -> {
                    try {
                        nmeaParser.parseNMEA(data);
                        // Диагностика: логируем каждые 10 секунд
                        long now2 = System.currentTimeMillis();
                        if (now2 - lastServiceLogTime > 10000) {
                            Log.d(TAG, "✅ AppController: UDP NMEA обработано в фоновом потоке");
                            lastServiceLogTime = now2;
                        }
                    } catch (Exception e) {
                        Log.e(TAG, "❌ Ошибка парсинга UDP NMEA в фоновом потоке: " + e.getMessage(), e);
                    }
                });
            } catch (java.util.concurrent.RejectedExecutionException e) {
                Log.w(TAG, "Thread pool is shutting down, skipping UDP data processing: " + e.getMessage());
            }
        } else {
            Log.w(TAG, "Thread pool is not available, skipping UDP data processing");
        }
        
        // Обновляем метки времени по префиксу в UI потоке (быстрая операция)
        updateLastMessageAgesFromRaw(data);
    }
    
    @Override
    public void onUDPError(String error) {
        Log.e(TAG, "UDP ошибка: " + error);
    }
    
    @Override
    public void onError(String error) {
        Log.e(TAG, "GPS Location ошибка: " + error);
    }
    
    // Реализация NMEAMessageCallback
    
    @Override
    public void onNMEAMessage(String message, long timestamp) {
        // Диагностика: логируем каждые 10 секунд
        long now = System.currentTimeMillis();
        if (now - lastServiceLogTime > 10000) {
            Log.d(TAG, "📱 AppController: Android NMEA сообщение получено");
            lastServiceLogTime = now;
        }
        
        // Парсим полученные данные как NMEA В ФОНОВОМ ПОТОКЕ
        if (executor != null && !executor.isShutdown()) {
            try {
                executor.execute(() -> {
                    try {
                        nmeaParser.parseNMEA(message);
                        // Диагностика: логируем каждые 10 секунд
                        long now2 = System.currentTimeMillis();
                        if (now2 - lastServiceLogTime > 10000) {
                            Log.d(TAG, "✅ AppController: NMEA обработано в фоновом потоке");
                            lastServiceLogTime = now2;
                        }
                    } catch (Exception e) {
                        Log.e(TAG, "❌ Ошибка парсинга NMEA в фоновом потоке: " + e.getMessage(), e);
                    }
                });
            } catch (java.util.concurrent.RejectedExecutionException e) {
                Log.w(TAG, "Thread pool is shutting down, skipping NMEA processing: " + e.getMessage());
            }
        } else {
            Log.w(TAG, "Thread pool is not available, skipping NMEA processing");
        }
        
        // Обновляем метки времени в UI потоке (быстрая операция)
        if (message != null) {
            String trimmed = message.trim();
            if (!trimmed.isEmpty()) {
                char c = trimmed.charAt(0);
                long now3 = android.os.SystemClock.elapsedRealtime();
                if (c == '$') {
                    lastGPSMessageRealtimeMs = now3;
                } else if (c == '!') {
                    lastAISMessageRealtimeMs = now3;
                }
            }
        }
    }
    
    // Реализация MarkerClickListener
    
    @Override
    public void onOwnVesselClick(Vessel vessel) {
        Log.i(TAG, "Клик по нашему судну: " + vessel);
        // Уведомляем UI о необходимости показать BottomSheet
        if (uiUpdateCallback != null) {
            Log.i(TAG, "uiUpdateCallback найден, обновляем данные судна");
            // Обновляем данные судна перед показом
            updateOwnVesselData(vessel);
            // Вызываем специальный callback для показа BottomSheet
            if (uiUpdateCallback instanceof ExtendedUIUpdateCallback) {
                Log.i(TAG, "Вызываем onShowOwnVesselBottomSheet");
                ((ExtendedUIUpdateCallback) uiUpdateCallback).onShowOwnVesselBottomSheet();
            } else {
                Log.w(TAG, "uiUpdateCallback не является ExtendedUIUpdateCallback");
            }
        } else {
            Log.e(TAG, "uiUpdateCallback == null!");
        }
    }
    
    @Override
    public void onAISVesselClick(AISVessel vessel) {
        Log.i(TAG, "Клик по AIS судну: " + vessel);
        // Уведомляем UI о необходимости показать информацию об AIS судне
        if (uiUpdateCallback != null && uiUpdateCallback instanceof ExtendedUIUpdateCallback) {
            ((ExtendedUIUpdateCallback) uiUpdateCallback).onShowAISVesselInfo(vessel);
        }
    }
    
    /**
     * Находит AIS судно по MMSI
     */
    private AISVessel findAISVesselByMMSI(String mmsi) {
        synchronized (aisVessels) {
            for (AISVessel vessel : aisVessels) {
                if (mmsi.equals(vessel.getMmsi())) {
                    return vessel;
                }
            }
        }
        return null;
    }
    
    /**
     * Получает наше судно
     */
    public Vessel getOwnVessel() {
        return ownVessel;
    }
    
    /**
     * Получает список AIS судов
     */
    public List<AISVessel> getAISVessels() {
        synchronized (aisVessels) {
            return new ArrayList<>(aisVessels);
        }
    }
    
    /**
     * Очищает все AIS суда
     */
    public void clearAISVessels() {
        Log.i(TAG, "Очищаем AIS суда из контроллера");
        
        // Очищаем локальные данные
        synchronized (aisVessels) {
            aisVessels.clear();
        }
        
        // Уведомляем UI Coordinator о необходимости очистки карты
        if (uiDataNotifier != null) {
            Log.d(TAG, "Уведомляем UI Coordinator об очистке AIS судов");
            // TODO: Добавить метод очистки всех AIS судов в UIDataChangeNotifier
            // Пока что очищаем через individual removals
            Log.i(TAG, "Individual AIS removal через uiDataNotifier еще не реализован");
        } else {
            Log.w(TAG, "uiDataNotifier не установлен, очистка AIS судов пропущена");
        }
        
        // Очищаем AIS path controllers
        aisPathControllers.clear();
    }
    
    /**
     * Центрирует карту на позиции нашего судна
     */
    public void centerOnOwnVessel() {
        if (ownVessel != null) {
            Log.d(TAG, "Запрос центрирования карты на судне: " + ownVessel.getLatitude() + "," + ownVessel.getLongitude());
            
            // Уведомляем UI Coordinator о необходимости центрирования карты
            if (uiDataNotifier != null) {
                uiDataNotifier.onRequestCenterMap(ownVessel.getLatitude(), ownVessel.getLongitude());
            } else {
                Log.w(TAG, "uiDataNotifier не установлен, центрирование карты пропущено");
            }
        }
    }
    
    /**
     * Запускает периодическую очистку БД от устаревших AIS целей
     */
    public void startDatabaseCleanup() {
        if (dbCleanupHandler != null && dbCleanupRunnable != null) {
            dbCleanupHandler.postDelayed(dbCleanupRunnable, DB_CLEANUP_INTERVAL);
            Log.i(TAG, "Запущена периодическая очистка БД от устаревших AIS целей");
        }
    }
    
    /**
     * Останавливает периодическую очистку БД
     */
    public void stopDatabaseCleanup() {
        if (dbCleanupHandler != null && dbCleanupRunnable != null) {
            dbCleanupHandler.removeCallbacks(dbCleanupRunnable);
            Log.i(TAG, "Остановлена периодическая очистка БД от устаревших AIS целей");
        }
    }
    
    /**
     * Выполняет очистку БД от устаревших AIS целей
     */
    private void performDatabaseCleanup() {
        try {
            com.grigowashere.aismap.utils.SettingsManager settingsManager = 
                new com.grigowashere.aismap.utils.SettingsManager(context);
            
            int staleRemoveMinutes = settingsManager.getDataStaleRemoveMinutes();
            long thresholdEpochMs = System.currentTimeMillis() - (staleRemoveMinutes * 60 * 1000L);
            
            repository.deleteStaleAIS(thresholdEpochMs);
            
            Log.i(TAG, "Выполнена очистка БД от AIS целей старше " + staleRemoveMinutes + " минут");
            
            // Планируем следующую очистку
            if (dbCleanupHandler != null && dbCleanupRunnable != null) {
                dbCleanupHandler.postDelayed(dbCleanupRunnable, DB_CLEANUP_INTERVAL);
            }
        } catch (Exception e) {
            Log.e(TAG, "Ошибка при очистке БД от устаревших AIS целей: " + e.getMessage(), e);
        }
    }
    
    /**
     * Освобождает ресурсы
     */
    public void cleanup() {
        stopAllListeners();
        stopDatabaseCleanup();
        
        // Очищаем Handler'ы для предотвращения утечек памяти
        if (uiHandler != null) {
            uiHandler.removeCallbacksAndMessages(null);
        }
        
        if (udpListener != null) {
            udpListener.cleanup();
        }
        
        if (androidNmeaListener != null) {
            androidNmeaListener.cleanup();
        }
        
        if (gpsLocationListener != null) {
            gpsLocationListener.cleanup();
        }
        
        if (notificationService != null) {
            notificationService.cleanup();
        }
        
        if (executor != null && !executor.isShutdown()) {
            executor.shutdown();
            try {
                // Ждем завершения всех задач максимум 2 секунды
                if (!executor.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS)) {
                    Log.w(TAG, "Thread pool did not terminate gracefully, forcing shutdown");
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                Log.w(TAG, "Thread pool shutdown interrupted: " + e.getMessage());
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }
    
    // ===== Метки времени последних сообщений ($ и !) =====
    private void updateLastMessageAgesFromRaw(String raw) {
        if (raw == null) return;
        long now = android.os.SystemClock.elapsedRealtime();
        String[] lines = raw.split("\r?\n");
        for (String line : lines) {
            if (line == null) continue;
            String t = line.trim();
            if (t.isEmpty()) continue;
            char c = t.charAt(0);
            if (c == '$') {
                lastGPSMessageRealtimeMs = now;
                break;
            } else if (c == '!') {
                lastAISMessageRealtimeMs = now;
                break;
            }
        }
    }

    /** Возвращает секунды с последнего GPS ($) сообщения; -1 если не было */
    public int getSecondsSinceLastGPSMessage() {
        if (lastGPSMessageRealtimeMs <= 0) return -1;
        long diff = android.os.SystemClock.elapsedRealtime() - lastGPSMessageRealtimeMs;
        if (diff < 0) return 0;
        return (int)(diff / 1000L);
    }

    /** Возвращает секунды с последнего AIS (!) сообщения; -1 если не было */
    public int getSecondsSinceLastAISMessage() {
        if (lastAISMessageRealtimeMs <= 0) return -1;
        long diff = android.os.SystemClock.elapsedRealtime() - lastAISMessageRealtimeMs;
        if (diff < 0) return 0;
        return (int)(diff / 1000L);
    }
    
    // Методы для управления настройками
    
    /**
     * Устанавливает UDP порт
     */
    public void setUDPPort(int port) {
        if (port < 1 || port > 65535) {
            Log.w(TAG, "Некорректный UDP порт: " + port);
            return;
        }
        
        this.udpPort = port;
        Log.i(TAG, "UDP порт установлен: " + port);
        
        // Если UDP слушатель уже создан, нужно будет его пересоздать
        if (udpListener != null && udpListener.getPort() != port) {
            Log.i(TAG, "UDP порт изменен, потребуется перезапуск UDP слушателя");
        }
    }
    
    /**
     * Получает текущий UDP порт
     */
    public int getUDPPort() {
        return udpPort;
    }
    
    /**
     * Включает/выключает UDP NMEA
     */
    public void setUDPNMEAEnabled(boolean enabled) {
        this.isUDPNMEAEnabled = enabled;
        Log.i(TAG, "UDP NMEA: " + (enabled ? "включен" : "выключен"));
    }
    
    /**
     * Проверяет, включен ли UDP NMEA
     */
    public boolean isUDPNMEAEnabled() {
        return isUDPNMEAEnabled;
    }
    
    /**
     * Устанавливает режим работы с данными
     */
    public void setDataMode(String mode) {
        this.dataMode = mode;
        Log.i(TAG, "🔄 Режим данных установлен: " + mode);
        
        // Применяем режим к NMEA парсеру
        if (nmeaParser != null) {
            boolean hybridMode = "hybrid".equals(mode);
            nmeaParser.setHybridMode(hybridMode);
            Log.i(TAG, "📍 Гибридный режим NMEA парсера: " + hybridMode);
            Log.i(TAG, "📍 В режиме '" + mode + "' координаты будут " + 
                  (hybridMode ? "браться из Android GPS API" : "браться из NMEA сообщений"));
        }
    }
    
    /**
     * Получает текущий режим работы с данными
     */
    public String getDataMode() {
        return dataMode;
    }
    
    /**
     * Перезапускает UDP слушатель с новым портом
     */
    public void restartUDPListener() {
        if (udpListener != null) {
            Log.i(TAG, "Перезапускаем UDP слушатель с портом: " + udpPort);
            
            // Останавливаем текущий слушатель
            udpListener.stop();
            udpListener.cleanup();
            
            // Создаем новый слушатель с новым портом
            udpListener = new UDPListener(udpPort);
            udpListener.setCallback(this);
            
            // Запускаем, если UDP включен
            if (isUDPEnabled) {
                udpListener.start();
                Log.i(TAG, "UDP слушатель перезапущен на порту: " + udpPort);
            }
        }
    }
    
    /**
     * Получает статус всех настроек
     */
    public String getSettingsStatus() {
        return String.format(
            "UDP: порт=%d, включен=%s, NMEA=%s\n" +
            "Android NMEA: %s\n" +
            "GPS Location: %s\n" +
            "Режим данных: %s",
            udpPort,
            isUDPEnabled ? "да" : "нет",
            isUDPNMEAEnabled ? "включен" : "выключен",
            isAndroidNMEAEnabled ? "включен" : "выключен",
            isGPSLocationEnabled ? "включен" : "выключен",
            dataMode != null ? dataMode : "не установлен"
        );
    }
    
    // ===== Методы для работы с путем судна =====
    
    /**
     * Получает контроллер пути судна
     */
    public VesselPathController getPathController() {
        return pathController;
    }
    
    /**
     * Получает информацию о пути судна
     */
    public String getVesselPathInfo() {
        if (pathController != null) {
            return pathController.getPathInfo();
        }
        return "Контроллер пути не инициализирован";
    }
    
    /**
     * Очищает путь судна
     */
    public void clearVesselPath() {
        if (pathController != null) {
            pathController.clearPath();
            Log.i(TAG, "Путь судна очищен");
        }
    }
    
    /**
     * Сохраняет путь судна
     */
    public void saveVesselPath() {
        if (pathController != null) {
            Log.d(TAG, "Сохранение пути судна: " + pathController.getPathInfo());
        }
    }
    
    /**
     * Добавляет точку в путь AIS судна
     */
    private void addAISVesselPathPoint(AISVessel vessel) {
        if (vessel == null || vessel.getMmsi() == null) {
            return;
        }
        
        // Проверяем валидность координат
        if (!isValidCoordinates(vessel.getLatitude(), vessel.getLongitude())) {
            Log.d(TAG, "addAISVesselPathPoint: AIS vessel " + vessel.getMmsi() + 
                  " has invalid coordinates " + vessel.getLatitude() + "," + vessel.getLongitude() + 
                  " - skipping path point");
            return;
        }
        
        String mmsi = vessel.getMmsi();
        
        // Получаем или создаем VesselPathController для этого AIS судна
        VesselPathController aisPathController = aisPathControllers.get(mmsi);
        if (aisPathController == null) {
            // Ограничиваем количество трекеров для производительности
            if (aisPathControllers.size() >= 20) {
                Log.w(TAG, "Достигнуто максимальное количество AIS трекеров (20), пропускаем создание для " + mmsi);
                return;
            }
            
            aisPathController = new VesselPathController(context, settingsManager, mmsi);
            aisPathControllers.put(mmsi, aisPathController);
            Log.d(TAG, "Создан VesselPathController для AIS судна " + mmsi + " (всего трекеров: " + aisPathControllers.size() + ")");
        }
        
        // Добавляем точку в путь
        boolean pointAdded = aisPathController.addPathPoint(
            vessel.getLongitude(), 
            vessel.getLatitude(), 
            (float) vessel.getSpeed()
        );
        
        if (pointAdded) {
            Log.d(TAG, "Точка пути добавлена для AIS " + mmsi + ": " + aisPathController.getPathPointsCount() + " точек");
        }
    }
    
    /**
     * Проверяет валидность координат
     * Игнорирует координаты 0,0 и 181,91 (невалидные значения AIS)
     */
    private boolean isValidCoordinates(double latitude, double longitude) {
        // Проверяем на нулевые координаты
        if (latitude == 0.0 && longitude == 0.0) {
            return false;
        }
        
        // Проверяем на невалидные координаты AIS (181, 91)
        if (latitude == 91.0 && longitude == 181.0) {
            return false;
        }
        
        // Проверяем на стандартные границы координат
        if (latitude < -90.0 || latitude > 90.0) {
            return false;
        }
        
        if (longitude < -180.0 || longitude > 180.0) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Получает VesselPathController для AIS судна
     */
    public VesselPathController getAISVesselPathController(String mmsi) {
        return aisPathControllers.get(mmsi);
    }
    
    /**
     * Очищает путь AIS судна
     */
    public void clearAISVesselPath(String mmsi) {
        VesselPathController aisPathController = aisPathControllers.get(mmsi);
        if (aisPathController != null) {
            aisPathController.clearPath();
            Log.d(TAG, "Путь AIS судна " + mmsi + " очищен");
        }
    }
    
    /**
     * Очищает все пути AIS судов
     */
    public void clearAllAISVesselPaths() {
        for (VesselPathController controller : aisPathControllers.values()) {
            controller.clearPath();
        }
        aisPathControllers.clear();
        Log.d(TAG, "Все пути AIS судов очищены");
    }
}
