import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'meshcore_ble_service.dart';

/// Centralized location tracking service for MeshCore SAR
///
/// Handles GPS tracking, distance thresholds, background updates,
/// and location broadcasting to the mesh network.
///
/// Features:
/// - Singleton pattern for app-wide access
/// - Configurable distance thresholds (min/max)
/// - Configurable time intervals
/// - Permission handling
/// - SharedPreferences persistence
/// - MeshCore mesh network integration
/// - Real-time position updates via callbacks
class LocationTrackingService {
  // ============================================================================
  // Singleton Pattern
  // ============================================================================

  static final LocationTrackingService _instance = LocationTrackingService._internal();

  /// Get the singleton instance
  factory LocationTrackingService() => _instance;

  LocationTrackingService._internal();

  // ============================================================================
  // SharedPreferences Keys
  // ============================================================================

  static const String _prefKeyEnabled = 'background_tracking_enabled';
  static const String _prefKeyMinDistance = 'map_gps_min_distance';
  static const String _prefKeyMaxDistance = 'map_gps_max_distance';
  static const String _prefKeyMinTimeInterval = 'map_gps_min_time_interval';
  static const String _prefKeyGpsUpdateDistance = 'map_gps_update_distance';
  static const String _prefKeyLastLat = 'background_last_lat';
  static const String _prefKeyLastLon = 'background_last_lon';

  // ============================================================================
  // Configuration Properties
  // ============================================================================

  /// Minimum distance in meters before broadcasting update
  double minDistanceMeters = 5.0;

  /// Maximum distance in meters that forces a broadcast regardless of time
  double maxDistanceMeters = 100.0;

  /// Minimum time interval in seconds between broadcasts
  int minTimeIntervalSeconds = 30;

  /// GPS update distance filter for position stream
  double gpsUpdateDistance = 10.0;

  // ============================================================================
  // State Properties
  // ============================================================================

  /// Current GPS position
  Position? currentPosition;

  /// Last position that was broadcast to mesh network
  Position? _lastBroadcastPosition;

  /// Timestamp of last broadcast
  DateTime? _lastBroadcastTime;

  /// Whether tracking is currently active
  bool isTracking = false;

  /// Whether service has been initialized with BLE service
  bool _isInitialized = false;

  // ============================================================================
  // Private Properties
  // ============================================================================

  /// Reference to MeshCore BLE service for broadcasting
  MeshCoreBleService? _bleService;

  /// Position stream subscription
  StreamSubscription<Position>? _positionSubscription;

  // ============================================================================
  // Callback Properties
  // ============================================================================

  /// Called when position is updated
  void Function(Position)? onPositionUpdate;

  /// Called when an error occurs
  void Function(String error)? onError;

  /// Called when a location broadcast is sent to mesh network
  void Function(Position)? onBroadcastSent;

  /// Called when tracking state changes
  void Function(bool isTracking)? onTrackingStateChanged;

  // ============================================================================
  // Initialization
  // ============================================================================

  /// Initialize the service with MeshCore BLE service reference
  ///
  /// Must be called before starting tracking.
  Future<bool> initialize(MeshCoreBleService bleService) async {
    _bleService = bleService;
    _isInitialized = true;

    // Load saved settings
    await loadSettings();

    debugPrint('✅ [LocationTracking] Service initialized');
    return true;
  }

  // ============================================================================
  // Permission Handling
  // ============================================================================

  /// Check if location permissions are granted
  Future<bool> checkPermissions() async {
    final permission = await Geolocator.checkPermission();
    return permission == LocationPermission.always ||
           permission == LocationPermission.whileInUse;
  }

  /// Request location permissions from user
  ///
  /// Returns true if granted, false otherwise.
  Future<bool> requestPermissions() async {
    // Check if location service is enabled
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      onError?.call('Location services are disabled');
      return false;
    }

    // Check current permission
    LocationPermission permission = await Geolocator.checkPermission();

    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        onError?.call('Location permission denied');
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      onError?.call('Location permission permanently denied. Please enable in settings.');
      return false;
    }

    debugPrint('✅ [LocationTracking] Location permissions granted');
    return true;
  }

  // ============================================================================
  // GPS Position Methods
  // ============================================================================

  /// Get current GPS position
  ///
  /// Returns null if position unavailable or permissions denied.
  Future<Position?> getCurrentPosition() async {
    try {
      final position = await Geolocator.getCurrentPosition(
        locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.best,
          timeLimit: Duration(seconds: 5),
        ),
      );

      currentPosition = position;
      return position;
    } catch (e) {
      debugPrint('❌ [LocationTracking] Error getting position: $e');
      onError?.call('Failed to get current position: $e');
      return null;
    }
  }

  /// Get position stream with configurable distance filter
  ///
  /// [distanceFilter] - Minimum distance in meters between position updates
  Stream<Position> getPositionStream({double distanceFilter = 10.0}) {
    return Geolocator.getPositionStream(
      locationSettings: LocationSettings(
        accuracy: LocationAccuracy.best,
        distanceFilter: distanceFilter.toInt(),
      ),
    );
  }

  // ============================================================================
  // Tracking Control
  // ============================================================================

  /// Start location tracking
  ///
  /// [distanceThreshold] - GPS update distance filter
  ///
  /// Returns true if successful, false otherwise.
  Future<bool> startTracking({double? distanceThreshold}) async {
    if (!_isInitialized || _bleService == null) {
      debugPrint('⚠️ [LocationTracking] Service not initialized or BLE service null');
      onError?.call('Location tracking service not initialized');
      return false;
    }

    if (!_bleService!.isConnected) {
      debugPrint('⚠️ [LocationTracking] BLE not connected');
      onError?.call('Not connected to mesh device');
      return false;
    }

    // Check permissions
    final hasPermission = await requestPermissions();
    if (!hasPermission) {
      return false;
    }

    // Use provided threshold or current setting
    final threshold = distanceThreshold ?? gpsUpdateDistance;
    gpsUpdateDistance = threshold;

    // Save settings
    await saveSettings();

    // Get initial position
    await getCurrentPosition();

    // Start position stream
    try {
      _positionSubscription = getPositionStream(distanceFilter: threshold).listen(
        _handlePositionUpdate,
        onError: (error) {
          debugPrint('❌ [LocationTracking] Position stream error: $error');
          onError?.call('Position stream error: $error');
        },
      );

      isTracking = true;
      onTrackingStateChanged?.call(true);

      debugPrint('✅ [LocationTracking] Tracking started with ${threshold}m threshold');
      return true;
    } catch (e) {
      debugPrint('❌ [LocationTracking] Failed to start tracking: $e');
      onError?.call('Failed to start tracking: $e');
      return false;
    }
  }

  /// Stop location tracking
  Future<void> stopTracking() async {
    debugPrint('🛑 [LocationTracking] Stopping tracking');

    await _positionSubscription?.cancel();
    _positionSubscription = null;

    isTracking = false;
    onTrackingStateChanged?.call(false);

    // Save disabled state
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool(_prefKeyEnabled, false);

    debugPrint('✅ [LocationTracking] Tracking stopped');
  }

  /// Update the distance threshold and restart tracking if active
  Future<void> updateDistanceThreshold(double meters) async {
    gpsUpdateDistance = meters;
    await saveSettings();

    debugPrint('📏 [LocationTracking] Distance threshold updated to ${meters}m');

    // Restart tracking if currently active
    if (isTracking) {
      await stopTracking();
      await startTracking(distanceThreshold: meters);
    }
  }

  // ============================================================================
  // Position Update Handler
  // ============================================================================

  /// Handle incoming position updates from GPS stream
  void _handlePositionUpdate(Position position) {
    debugPrint('📍 [LocationTracking] New position: ${position.latitude}, ${position.longitude}');

    // Update current position
    currentPosition = position;

    // Notify listeners
    onPositionUpdate?.call(position);

    // Check if we should broadcast to mesh network
    _checkAndBroadcast(position);
  }

  /// Check if position should be broadcast based on distance and time thresholds
  void _checkAndBroadcast(Position position) {
    // If never broadcast before, do it now
    if (_lastBroadcastPosition == null || _lastBroadcastTime == null) {
      _broadcastPosition(position);
      return;
    }

    // Calculate time since last broadcast
    final timeSinceLastBroadcast = DateTime.now().difference(_lastBroadcastTime!);
    final secondsSinceLastBroadcast = timeSinceLastBroadcast.inSeconds;

    // Calculate distance from last broadcast position
    final distanceFromLastBroadcast = Geolocator.distanceBetween(
      _lastBroadcastPosition!.latitude,
      _lastBroadcastPosition!.longitude,
      position.latitude,
      position.longitude,
    );

    debugPrint('   Distance from last broadcast: ${distanceFromLastBroadcast.toStringAsFixed(1)}m');
    debugPrint('   Time since last broadcast: ${secondsSinceLastBroadcast}s');

    // Broadcast if moved beyond max distance threshold
    if (distanceFromLastBroadcast >= maxDistanceMeters) {
      debugPrint('   📤 Triggering broadcast: exceeded max distance (${maxDistanceMeters}m)');
      _broadcastPosition(position);
      return;
    }

    // Broadcast if minimum time interval passed AND moved beyond min distance
    if (secondsSinceLastBroadcast >= minTimeIntervalSeconds &&
        distanceFromLastBroadcast >= minDistanceMeters) {
      debugPrint('   📤 Triggering broadcast: exceeded min time (${minTimeIntervalSeconds}s) and min distance (${minDistanceMeters}m)');
      _broadcastPosition(position);
      return;
    }

    debugPrint('   ⏸️ Not broadcasting: thresholds not met');
  }

  // ============================================================================
  // Mesh Network Broadcasting
  // ============================================================================

  /// Broadcast position to mesh network via BLE
  void _broadcastPosition(Position position) async {
    if (_bleService == null || !_bleService!.isConnected) {
      debugPrint('⚠️ [LocationTracking] Cannot broadcast: BLE not connected');
      return;
    }

    try {
      debugPrint('📤 [LocationTracking] Broadcasting position to mesh network');

      // Update device's advertised location
      await _bleService!.setAdvertLatLon(
        latitude: position.latitude,
        longitude: position.longitude,
      );

      // Send advertisement to mesh network
      await _bleService!.sendSelfAdvert(floodMode: true);

      // Update broadcast tracking
      _lastBroadcastPosition = position;
      _lastBroadcastTime = DateTime.now();

      // Save to preferences
      final prefs = await SharedPreferences.getInstance();
      await prefs.setDouble(_prefKeyLastLat, position.latitude);
      await prefs.setDouble(_prefKeyLastLon, position.longitude);

      debugPrint('✅ [LocationTracking] Position broadcast successful');
      onBroadcastSent?.call(position);
    } catch (e) {
      debugPrint('❌ [LocationTracking] Failed to broadcast position: $e');
      onError?.call('Failed to broadcast position: $e');
    }
  }

  /// Manually broadcast current location immediately
  ///
  /// Useful for "Send Location Now" button functionality.
  Future<bool> broadcastLocationNow() async {
    if (!_isInitialized || _bleService == null) {
      onError?.call('Location tracking service not initialized');
      return false;
    }

    if (!_bleService!.isConnected) {
      onError?.call('Not connected to mesh device');
      return false;
    }

    try {
      // Get current position
      final position = await getCurrentPosition();
      if (position == null) {
        onError?.call('Failed to get current position');
        return false;
      }

      // Broadcast regardless of thresholds
      await _bleService!.setAdvertLatLon(
        latitude: position.latitude,
        longitude: position.longitude,
      );

      await _bleService!.sendSelfAdvert(floodMode: true);

      // Update broadcast tracking
      _lastBroadcastPosition = position;
      _lastBroadcastTime = DateTime.now();

      debugPrint('✅ [LocationTracking] Manual broadcast successful');
      onBroadcastSent?.call(position);

      return true;
    } catch (e) {
      debugPrint('❌ [LocationTracking] Manual broadcast failed: $e');
      onError?.call('Failed to broadcast location: $e');
      return false;
    }
  }

  // ============================================================================
  // Settings Persistence
  // ============================================================================

  /// Load settings from SharedPreferences
  Future<void> loadSettings() async {
    final prefs = await SharedPreferences.getInstance();

    minDistanceMeters = prefs.getDouble(_prefKeyMinDistance) ?? 5.0;
    maxDistanceMeters = prefs.getDouble(_prefKeyMaxDistance) ?? 100.0;
    minTimeIntervalSeconds = prefs.getInt(_prefKeyMinTimeInterval) ?? 30;
    gpsUpdateDistance = prefs.getDouble(_prefKeyGpsUpdateDistance) ?? 10.0;

    // Load last broadcast position if available
    final lastLat = prefs.getDouble(_prefKeyLastLat);
    final lastLon = prefs.getDouble(_prefKeyLastLon);
    if (lastLat != null && lastLon != null) {
      _lastBroadcastPosition = Position(
        latitude: lastLat,
        longitude: lastLon,
        timestamp: DateTime.now(),
        accuracy: 0.0,
        altitude: 0.0,
        altitudeAccuracy: 0.0,
        heading: 0.0,
        headingAccuracy: 0.0,
        speed: 0.0,
        speedAccuracy: 0.0,
      );
    }

    debugPrint('✅ [LocationTracking] Settings loaded');
    debugPrint('    Min distance: ${minDistanceMeters}m');
    debugPrint('    Max distance: ${maxDistanceMeters}m');
    debugPrint('    Min time interval: ${minTimeIntervalSeconds}s');
    debugPrint('    GPS update distance: ${gpsUpdateDistance}m');
  }

  /// Save settings to SharedPreferences
  Future<void> saveSettings() async {
    final prefs = await SharedPreferences.getInstance();

    await prefs.setDouble(_prefKeyMinDistance, minDistanceMeters);
    await prefs.setDouble(_prefKeyMaxDistance, maxDistanceMeters);
    await prefs.setInt(_prefKeyMinTimeInterval, minTimeIntervalSeconds);
    await prefs.setDouble(_prefKeyGpsUpdateDistance, gpsUpdateDistance);
    await prefs.setBool(_prefKeyEnabled, isTracking);

    debugPrint('✅ [LocationTracking] Settings saved');
  }

  // ============================================================================
  // Cleanup
  // ============================================================================

  /// Dispose resources and cleanup
  void dispose() {
    debugPrint('🗑️ [LocationTracking] Disposing service');
    _positionSubscription?.cancel();
    _positionSubscription = null;
    _bleService = null;
    _isInitialized = false;
    isTracking = false;
  }
}
