import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:audioplayers/audioplayers.dart';
import 'dart:math' as math;

import 'package:nearle/providers/deliverylog/deliverylog_provider.dart';
import 'package:nearle/providers/notifications/notificationservce.dart';
import 'package:nearle/views/helpers/constants/apiconstants.dart';
import 'package:nearle/utils/kalman_filter.dart';

/// Background service for managing active delivery logs
class BackgroundDeliveryLog {
  static NearleKalmanFilter? _kf;
  static DateTime? _lastUpdateTime;

  static final CreateDeliveryLogProvider _logProvider =
      CreateDeliveryLogProvider();
  static final http.Client _httpClient = http.Client();
  static final FlutterTts _tts = FlutterTts();
  static final AudioPlayer _proximityPlayer = AudioPlayer();

  static const double _idleThresholdMeters = 10; // ~5-10m tolerance
  static const double _proximityThresholdMeters = 50.0; // 50 meters for arrival alert
  static const String _activeDeliveriesKey = 'active_deliveries';
  static const String _payloadKeyPrefix = 'delivery_payload_';
  static const String _offlineLogKey = 'offline_delivery_logs';
  static const String _proximityAlertKeyPrefix = 'delivery_proximity_alerted_';

  static bool _isPosting = false;
  static bool _ttsReady = false;
  static bool _audioReady = false;

  /// Process active deliveries: fetch from API, filter active, and post logs
  static Future<void> processActiveDeliveries() async {
    if (_isPosting) {
      debugPrint('[ACTIVE_DELIVERY_LOG][BG] Already posting, skipping...');
      return;
    }
    _isPosting = true;

    try {
      // Get userid from SharedPreferences
      final prefs = await SharedPreferences.getInstance();
      final userId = prefs.getInt('userid') ?? prefs.getInt('userId') ?? 0;

      debugPrint('[ACTIVE_DELIVERY_LOG][BG] Checking for user: $userId');

      if (userId == 0) {
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] ❌ No user ID found in prefs');
        return;
      }

      // Get current date dynamically (YYYY-MM-DD format)
      final now = DateTime.now();
      final today = '${now.year}-${_pad(now.month)}-${_pad(now.day)}';

      // Build API URL using v1 endpoint (getdeliveries)
      final bool isLive = ApiConstants.mainRoute == 'live';
      final baseUrl = isLive
          ? ApiConstants.currentDeliveryLive
          : ApiConstants.currentDeliveryDev;

      final uri = Uri.parse(baseUrl).replace(
        queryParameters: {
          'userid': userId.toString(),
          'fromdate': today,
          'todate': today,
          't': DateTime.now().millisecondsSinceEpoch.toString(),
        },
      );

      debugPrint('[ACTIVE_DELIVERY_LOG][BG] Fetching deliveries: $uri');

      // Fetch deliveries from API using v1 endpoint
      final deliveries = await _fetchDeliveriesFromApi(uri);
      
      debugPrint('[ACTIVE_DELIVERY_LOG][BG] API returned ${deliveries.length} items');

      // Filter for active orders only
      final activeOrders = deliveries.whereType<Map<String, dynamic>>().where((
        order,
      ) {
        final status = (order['orderstatus']?.toString().toLowerCase() ?? '')
            .trim();
        final isActive = status == 'active';
        return isActive;
      }).toList();

      if (activeOrders.isEmpty) {
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] No active orders found');
        // ✅ Still check shift end even if no active deliveries
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] 🔍 Checking shift end time...');
        await checkShiftEnd();
        return;
      }

      debugPrint(
        '[ACTIVE_DELIVERY_LOG][BG] Found ${activeOrders.length} active deliveries',
      );

      // Post logs for each active delivery
      for (final order in activeOrders) {
        final orderId = (order['orderid'] ?? '').toString();
        if (orderId.isEmpty) continue;

        // Get current coordinates once per order to reuse for proximity + log
        final Map<String, String>? coords = await _getCoordinatesWithFallback();
        if (coords != null) {
          await _checkProximityAlert(order, coords);
        }

        // Get payload from SharedPreferences or create new one
        Map<String, dynamic>? payload = await _loadPayload(orderId);
        if (payload == null) {
          // Create new payload from order data with userId from SharedPreferences
          payload = _createPayload(order, userId);
          await _persistPayload(orderId, payload);
          await _markAsActive(orderId);
        } else {
          // Ensure userid is set correctly in existing payload
          payload['userid'] = userId;
        }

        // Post the log
        await _postDeliveryLog(orderId, payload, currentCoords: coords);
      }

      // ✅ CRITICAL: Check if shift end time has passed (backup to alarm)
      await checkShiftEnd();

      // Attempt to flush offline logs
      await _flushOfflineLogs();
    } catch (e) {
      debugPrint('[ACTIVE_DELIVERY_LOG][BG] Error processing: $e');
    } finally {
      _isPosting = false;
    }
  }

  /// Post delivery log for a specific order
  static Future<void> _postDeliveryLog(
    String orderId,
    Map<String, dynamic> payload, {
    Map<String, String>? currentCoords,
  }) async {
    Map<String, dynamic>? payloadWithCoords;
    try {
      // Get current coordinates
      final coords = currentCoords ?? await _getCoordinatesWithFallback();
      if (coords == null) {
        return;
      }

      // Distance calculation is now fully handled by LiveTrackingService in the background.
      // 03-14: Removed redundant 60-second background calculations to avoid logic conflicts.
      final deliveryId = payload['deliveryid']?.toString() ?? '';


      // Create log date timestamp
      final now = DateTime.now();
      final logDate =
          '${now.year}-${_pad(now.month)}-${_pad(now.day)} ${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';

      // Retrieve final cumulative KM to send
      double riderKmsToSend = 0.0;
      if (deliveryId.isNotEmpty && deliveryId != '0') {
        try {
           final prefs = await SharedPreferences.getInstance();
           await prefs.reload(); // Force reload so we get the latest value written by the main isolate
           riderKmsToSend = double.tryParse(prefs.getString('delivery_tracking_${deliveryId}_cumulativeKm') ?? '0') ?? 0.0;
        } catch (_) {}
      }

      // Build payload with coordinates and timestamp
      payloadWithCoords = <String, dynamic>{
        ...payload,
        'logdate': logDate,
        'latitude': coords['lat'] ?? '0',
        'longitude': coords['lng'] ?? '0',
        'raw_latitude': coords['raw_lat'] ?? '0',
        'raw_longitude': coords['raw_lng'] ?? '0',
        'velocity_lat': coords['velocity_lat'] ?? '0',
        'velocity_lng': coords['velocity_lng'] ?? '0',
        'speed': coords['speed'] ?? '0',
        'heading': coords['heading'] ?? '0',
        'riderkms': riderKmsToSend,
        'logstatus': await _resolveLogStatus(orderId, coords),
      };

      // Determine API endpoint
      final url = ApiConstants.mainRoute == 'live'
          ? ApiConstants.createDeliveryLogLive
          : ApiConstants.createDeliveryLogDev;

      // Post the log
      debugPrint('[ACTIVE_DELIVERY_LOG][BG] Posting log for $orderId');

      final result = await _logProvider
          .createDeliveryLog(url, payloadWithCoords)
          .timeout(
            const Duration(seconds: 8),
            onTimeout: () => throw TimeoutException(
              'Delivery log post timeout for $orderId',
            ),
          );

      if (result != null) {
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] Success for $orderId');
        await _persistLastLogLocation(orderId, coords);
      } else {
        debugPrint(
          '[ACTIVE_DELIVERY_LOG][BG] Warning: No response for $orderId',
        );
      }
    } catch (e) {
      debugPrint(
        '[ACTIVE_DELIVERY_LOG][BG] Failed to post log for $orderId: $e',
      );
      // Offline fallback
      if (payloadWithCoords != null) {
        await _saveToOfflineQueue(orderId, payloadWithCoords);
      }
    }
  }

  /// Create payload from delivery order data
  static Map<String, dynamic> _createPayload(
    Map<String, dynamic> delivery,
    int userId,
  ) {
    return <String, dynamic>{
      'logid': 0,
      'tenantid': delivery['tenantid'] ?? 0,
      'partnerid': delivery['partnerid'] ?? 0,
      'locationid': delivery['locationid'] ?? 0,
      'orderheaderid': delivery['orderheaderid'] ?? 0,
      'deliveryid': delivery['deliveryid'] ?? 0,
      'userid': userId,
      'orderid': (delivery['orderid'] ?? '').toString(),
      'orderstatus': 'active',
    };
  }

  /// Get coordinates with fallback (current position -> last known -> cached)
  static Future<Map<String, String>?> _getCoordinatesWithFallback() async {
    Map<String, String> result = {
      'lat': '0',
      'lng': '0',
      'raw_lat': '0',
      'raw_lng': '0',
      'speed': '0',
      'heading': '0',
      'velocity_lat': '0',
      'velocity_lng': '0',
    };

    try {
      // 1. Check if location services are enabled
      final serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] Location services disabled');
        return await _getCachedCoordinatesAsMap();
      }

      // 2. Check permissions
      final permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] Location permission denied');
        return await _getCachedCoordinatesAsMap();
      }

      Position? position;
      try {
        position = await Geolocator.getCurrentPosition(
          locationSettings: const LocationSettings(
            accuracy: LocationAccuracy.high,
          ),
        ).timeout(const Duration(seconds: 5));
      } catch (e) {
        debugPrint('[ACTIVE_DELIVERY_LOG][BG] Failed to get current pos: $e');
        position = await Geolocator.getLastKnownPosition();
      }

      if (position != null) {
        // Reject mocked GPS (anti-cheat)
        if (position.isMocked) {
          debugPrint('[ACTIVE_DELIVERY_LOG][BG] Mocked position — using cached');
          return await _getCachedCoordinatesAsMap();
        }
        // Reject very poor accuracy to avoid phantom movements in delivery logs
        if (position.accuracy > 50.0) {
          debugPrint(
            '[ACTIVE_DELIVERY_LOG][BG] Low-accuracy position (${position.accuracy.toStringAsFixed(0)}m) — using cached',
          );
          return await _getCachedCoordinatesAsMap();
        }

        final now = DateTime.now();
        double outLat = position.latitude;
        double outLng = position.longitude;
        double speed = position.speed;
        double heading = position.heading;

        // Decompose velocity for Kalman
        final double headingRadians = heading * (math.pi / 180.0);
        final double velocityLng = speed * math.sin(headingRadians);
        final double velocityLat = speed * math.cos(headingRadians);

        if (_kf == null) {
          _kf = NearleKalmanFilter(lat: outLat, lng: outLng);
        } else {
          final double dt = _lastUpdateTime != null
              ? now.difference(_lastUpdateTime!).inMilliseconds / 1000.0
              : 30.0;
          
          _kf!.predict(dt);
          _kf!.update(outLat, outLng);
          outLat = _kf!.x[0];
          outLng = _kf!.x[1];
        }
        _lastUpdateTime = now;

        result = {
          'lat': outLat.toString(),
          'lng': outLng.toString(),
          'raw_lat': position.latitude.toString(),
          'raw_lng': position.longitude.toString(),
          'speed': speed.toStringAsFixed(2),
          'heading': heading.toStringAsFixed(2),
          'velocity_lat': velocityLat.toStringAsFixed(4),
          'velocity_lng': velocityLng.toStringAsFixed(4),
        };

        await _cacheCoordinates(result['lat']!, result['lng']!);
        return result;
      }

      return await _getCachedCoordinatesAsMap();
    } catch (e) {
      debugPrint('[ACTIVE_DELIVERY_LOG][BG] Error getting coords: $e');
      return await _getCachedCoordinatesAsMap();
    }
  }

  static Future<Map<String, String>?> _getCachedCoordinatesAsMap() async {
    final coords = await _getCachedCoordinates();
    if (coords == null) return null;
    return {
      'lat': coords.$1,
      'lng': coords.$2,
      'raw_lat': coords.$1,
      'raw_lng': coords.$2,
      'speed': '0',
      'heading': '0',
      'velocity_lat': '0',
      'velocity_lng': '0',
    };
  }

  /// Cache coordinates to SharedPreferences
  static Future<void> _cacheCoordinates(String lat, String lng) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString('last_lat', lat);
      await prefs.setString('last_lng', lng);
    } catch (_) {}
  }

  /// Get cached coordinates from SharedPreferences
  static Future<(String, String)?> _getCachedCoordinates() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final lat = (prefs.getString('last_lat') ?? '').trim();
      final lng = (prefs.getString('last_lng') ?? '').trim();
      if (lat.isNotEmpty && lng.isNotEmpty) {
        return (lat, lng);
      }
    } catch (_) {}
    return null;
  }

  /// Load payload from SharedPreferences
  static Future<Map<String, dynamic>?> _loadPayload(String orderId) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final jsonStr = prefs.getString('$_payloadKeyPrefix$orderId');
      if (jsonStr == null || jsonStr.isEmpty) return null;
      final decoded = jsonDecode(jsonStr);
      if (decoded is Map<String, dynamic>) return decoded;
      if (decoded is Map) return decoded.cast<String, dynamic>();
    } catch (_) {}
    return null;
  }

  /// Persist payload to SharedPreferences
  static Future<void> _persistPayload(
    String orderId,
    Map<String, dynamic> payload,
  ) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString('$_payloadKeyPrefix$orderId', jsonEncode(payload));
    } catch (_) {}
  }

  /// Mark order as active in SharedPreferences
  static Future<void> _markAsActive(String orderId) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final list = prefs.getStringList(_activeDeliveriesKey) ?? <String>[];
      if (!list.contains(orderId)) {
        list.add(orderId);
        await prefs.setStringList(_activeDeliveriesKey, list);
      }
    } catch (_) {}
  }

  /// Fetch deliveries from API using v1 endpoint
  static Future<List<dynamic>> _fetchDeliveriesFromApi(Uri uri) async {
    try {
      final res = await _httpClient
          .get(uri)
          .timeout(const Duration(seconds: 10));

      if (res.statusCode >= 200 && res.statusCode < 300) {
        final decoded = json.decode(res.body);

        final data = decoded is Map<String, dynamic>
            ? (decoded['details'] ?? decoded['data'] ?? decoded)
            : decoded;

        if (data is List) {
          return data;
        }
        if (data is Map && data['items'] is List) {
          return data['items'] as List;
        }

        return [];
      }
      return [];
    } catch (e) {
      return [];
    }
  }

  /// Helper to pad numbers with leading zero
  static String _pad(int n) => n.toString().padLeft(2, '0');

  static Future<int> _resolveLogStatus(
    String orderId,
    Map<String, String> coords,
  ) async {
    try {
      final last = await _loadLastLogLocation(orderId);
      if (last != null) {
        final lastLat = last.$1;
        final lastLng = last.$2;
        final currentLat = double.tryParse(coords['lat'] ?? '0') ?? 0;
        final currentLng = double.tryParse(coords['lng'] ?? '0') ?? 0;
        if (currentLat != 0 &&
            currentLng != 0 &&
            lastLat != 0 &&
            lastLng != 0) {
          final distance = Geolocator.distanceBetween(
            lastLat,
            lastLng,
            currentLat,
            currentLng,
          );
          if (distance <= _idleThresholdMeters) {
            return 1; // idle
          }
        }
      }
    } catch (e) {
      debugPrint('[ACTIVE_DELIVERY_LOG][BG] logstatus error: $e');
    }
    return 0; // moving
  }

  static Future<(double, double)?> _loadLastLogLocation(String orderId) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final jsonStr = prefs.getString('last_log_loc_$orderId');
      if (jsonStr == null || jsonStr.isEmpty) return null;
      final decoded = jsonDecode(jsonStr);
      if (decoded is Map<String, dynamic>) {
        final lat = double.tryParse('${decoded['lat']}') ?? 0;
        final lng = double.tryParse('${decoded['lng']}') ?? 0;
        if (lat != 0 && lng != 0) return (lat, lng);
      }
    } catch (_) {}
    return null;
  }

  static Future<void> _persistLastLogLocation(
    String orderId,
    Map<String, String> coords,
  ) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString(
        'last_log_loc_$orderId',
        jsonEncode({'lat': coords['lat'], 'lng': coords['lng']}),
      );
    } catch (_) {}
  }

  // ---------------- Offline Queue Logic ----------------

  static Future<void> _flushOfflineLogs() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final List<String> queue = prefs.getStringList(_offlineLogKey) ?? [];
      if (queue.isEmpty) return;

      debugPrint(
        '[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Flushing ${queue.length} offline logs...',
      );

      final List<String> remaining = [];
      bool anySuccess = false;

      final url = ApiConstants.mainRoute == 'live'
          ? ApiConstants.createDeliveryLogLive
          : ApiConstants.createDeliveryLogDev;

      for (final itemStr in queue) {
        try {
          final Map<String, dynamic> item = jsonDecode(itemStr);
          final String orderId = item['orderId'] ?? '';
          final Map<String, dynamic> payload = Map<String, dynamic>.from(
            item['payload'] ?? {},
          );

          if (payload.isEmpty) continue;

          debugPrint(
            '[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Retrying for orderId: $orderId',
          );

          final result = await _logProvider
              .createDeliveryLog(url, payload)
              .timeout(const Duration(seconds: 8));

          if (result != null) {
            debugPrint(
              '[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Success for orderId: $orderId',
            );
            anySuccess = true;
          } else {
            remaining.add(itemStr);
          }
        } catch (e) {
          remaining.add(itemStr);
        }
      }

      if (anySuccess || remaining.length != queue.length) {
        await prefs.setStringList(_offlineLogKey, remaining);
      }
    } catch (e) {
      debugPrint('[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Flush error: $e');
    }
  }

  static Future<void> _saveToOfflineQueue(
    String orderId,
    Map<String, dynamic> payload,
  ) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final List<String> queue = prefs.getStringList(_offlineLogKey) ?? [];

      final item = jsonEncode({
        'orderId': orderId,
        'payload': payload,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });

      queue.add(item);
      await prefs.setStringList(_offlineLogKey, queue);
      debugPrint(
        '[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Saved to queue. Total: ${queue.length}',
      );
    } catch (e) {
      debugPrint(
        '[ACTIVE_DELIVERY_LOG][BG][OFFLINE] Error saving to queue: $e',
      );
    }
  }

  // ---------------- Auto Shift End Logic ----------------

  /// Check if shift has ended and trigger auto-break if needed
  static Future<void> checkShiftEnd() async {
    try {
      debugPrint('[AUTO_SHIFT_END] 🔍 Starting shift end check...');
      final prefs = await SharedPreferences.getInstance();
      
      // 1. Check if currently On Duty
      final int onduty = prefs.getInt('onduty') ?? 0;
      debugPrint('[AUTO_SHIFT_END] 📊 Current onduty status: $onduty');
      if (onduty != 1) {
        debugPrint('[AUTO_SHIFT_END] ⏭️ Rider not on duty, skipping check');
        return; // Already offline
      }

      // 2. Get Shift Timings
      final String startTimeStr = prefs.getString('starttime') ?? '';
      final String endTimeStr = prefs.getString('endtime') ?? '';
      debugPrint('[AUTO_SHIFT_END] ⏰ Shift times - Start: "$startTimeStr", End: "$endTimeStr"');
      
      if (endTimeStr.isEmpty) {
        debugPrint('[AUTO_SHIFT_END] ⚠️ No endtime found, cannot check shift end');
        return;
      }

      // 3. Parse Times (Assumed format HH:mm:ss)
      final now = DateTime.now();
      
      final endParts = endTimeStr.split(':');
      if (endParts.length < 2) return;
      
      final int endHour = int.tryParse(endParts[0]) ?? 0;
      final int endMinute = int.tryParse(endParts[1]) ?? 0;
      final int endSecond = endParts.length > 2 ? (int.tryParse(endParts[2]) ?? 0) : 0;
      
      // Create DateTime for end time on TODAY
      final DateTime endToday = DateTime(
        now.year,
        now.month,
        now.day,
        endHour,
        endMinute,
        endSecond,
      );

      bool isShiftOver = false;

      if (startTimeStr.isNotEmpty) {
        final startParts = startTimeStr.split(':');
        if (startParts.length >= 2) {
          final int startHour = int.tryParse(startParts[0]) ?? 0;
          final int startMinute = int.tryParse(startParts[1]) ?? 0;
          
          // Create DateTime for start time on TODAY
          final DateTime startToday = DateTime(
            now.year,
            now.month,
            now.day,
            startHour,
            startMinute,
          );
          
          // Check for overnight shift (Start > End in 24-hour format, e.g. 22:00 to 06:00)
          // This means shift crosses midnight
          final double startVal = startHour + (startMinute / 60.0);
          final double endVal = endHour + (endMinute / 60.0);
          
          if (startVal > endVal) {
            // ✅ OVERNIGHT SHIFT (crosses midnight, e.g. 22:00 to 06:00)
            // Shift is over if current time is AFTER end time today AND BEFORE start time today
            // Example: End 06:00, Start 22:00
            // - Now 23:00 -> After 22:00 (start) -> Still in shift (ACTIVE)
            // - Now 05:00 -> Before 06:00 (end) -> Still in shift from yesterday (ACTIVE)
            // - Now 10:00 -> After 06:00 (end) AND Before 22:00 (start) -> Shift OVER
            
            debugPrint('[AUTO_SHIFT_END] 🌙 Overnight shift detected (Start: ${_pad(startHour)}:${_pad(startMinute)}, End: ${_pad(endHour)}:${_pad(endMinute)})');
            debugPrint('[AUTO_SHIFT_END] 📅 Current time: ${_pad(now.hour)}:${_pad(now.minute)}');
            
            if (now.isAfter(endToday) && now.isBefore(startToday)) {
              // We're in the gap period between end and start -> shift is OVER
              isShiftOver = true;
              debugPrint('[AUTO_SHIFT_END] ✅ Overnight shift: In gap period -> SHIFT OVER');
            } else {
              // We're either before end (still in shift from yesterday) or after start (still in shift today)
              isShiftOver = false;
              debugPrint('[AUTO_SHIFT_END] ✅ Overnight shift: Still active');
            }
          } else {
            // ✅ NORMAL DAY SHIFT (e.g. 09:00 to 17:00, doesn't cross midnight)
            // Shift is over if current time is AFTER end time
            debugPrint('[AUTO_SHIFT_END] ☀️ Day shift detected (Start: ${_pad(startHour)}:${_pad(startMinute)}, End: ${_pad(endHour)}:${_pad(endMinute)})');
            debugPrint('[AUTO_SHIFT_END] 📅 Current time: ${_pad(now.hour)}:${_pad(now.minute)}, End time: ${_pad(endHour)}:${_pad(endMinute)}');
            
            if (now.isAfter(endToday) || now.isAtSameMomentAs(endToday)) {
              isShiftOver = true;
              debugPrint('[AUTO_SHIFT_END] ✅ Day shift: Shift ended');
            } else {
              isShiftOver = false;
              debugPrint('[AUTO_SHIFT_END] ✅ Day shift: Still active');
            }
          }
        } else {
          // Fallback if start time parse fails: assume day shift
          debugPrint('[AUTO_SHIFT_END] ⚠️ Could not parse start time, assuming day shift');
          if (now.isAfter(endToday) || now.isAtSameMomentAs(endToday)) {
            isShiftOver = true;
          }
        }
      } else {
        // Fallback if no start time: assume day shift
        debugPrint('[AUTO_SHIFT_END] ⚠️ No start time provided, assuming day shift');
        if (now.isAfter(endToday) || now.isAtSameMomentAs(endToday)) {
          isShiftOver = true;
        }
      }

      // 4. Trigger Auto End if Shift is Over
      debugPrint('[AUTO_SHIFT_END] 📅 Time check result - isShiftOver: $isShiftOver, Current: ${_pad(now.hour)}:${_pad(now.minute)}, End: ${_pad(endHour)}:${_pad(endMinute)}');
      if (isShiftOver) {
        debugPrint('[AUTO_SHIFT_END] ⏰ Shift ended (Start: $startTimeStr, End: $endTimeStr). Current: ${_pad(now.hour)}:${_pad(now.minute)}');
        await _autoEndShift(prefs);
      } else {
        debugPrint('[AUTO_SHIFT_END] ✅ Shift not ended yet, continuing...');
      }
    } catch (e) {
      debugPrint('[AUTO_SHIFT_END] ❌ Error checking shift end: $e');
      debugPrint('[AUTO_SHIFT_END] Stack trace: ${StackTrace.current}');
    }
  }

  static Future<void> _autoEndShift(SharedPreferences prefs) async {
    try {
      debugPrint(
        '[AUTO_SHIFT_END] 🚀 Initiating auto-break and offline sequence...',
      );

      // 1. Get Required IDs
      final int userid = prefs.getInt('userid') ?? prefs.getInt('userId') ?? 0;
      final int partnerid =
          prefs.getInt('partnerid') ?? prefs.getInt('partnerId') ?? 0;
      final int shiftid =
          prefs.getInt('shiftid') ?? prefs.getInt('shiftId') ?? 0;
      final int logid = prefs.getInt('logid') ?? prefs.getInt('logId') ?? 0;

      if (userid == 0) {
        debugPrint(
          '[AUTO_SHIFT_END] ❌ Missing userid, cannot create break log',
        );
        return;
      }

      // 2. Get Location
      final Map<String, String>? coords = await _getCoordinatesWithFallback();
      final String lat = coords?['lat'] ?? '0';
      final String lng = coords?['lng'] ?? '0';

      // 3. Prepare Break Log Payload
      final now = DateTime.now();
      final int localBreakId =
          (DateTime.now().millisecondsSinceEpoch % 900) + 100; // Random-ish ID
      final String breakdate =
          '${now.year}-${_pad(now.month)}-${_pad(now.day)} ${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';
      final String breakstart =
          '${_pad(now.hour)}:${_pad(now.minute)}:${_pad(now.second)}';

      final payload = <String, dynamic>{
        "breakid": localBreakId,
        "logid": logid,
        "breakdate": breakdate,
        "userid": userid,
        "partnerid": partnerid,
        "shiftid": shiftid,
        "breakstart": breakstart,
        "breakend": "",
        "breakhours": 0.0,
        "latitude": lat,
        "longitude": lng,
      };

      // 4. Call API to Create Break
      final url = ApiConstants.mainRoute == 'live'
          ? ApiConstants.createBreakRiderLogLive
          : ApiConstants.createBreakRiderLogDev;

      debugPrint('[AUTO_SHIFT_END] Creating break log: $url');

      // We use a separate provider instance or http call if needed,
      // but _logProvider is for delivery logs. We need a generic post or use http directly.
      // Since we don't have BreakRiderLogProvider here, we'll use http directly for simplicity and isolation.

      try {
        final response = await _httpClient
            .post(
              Uri.parse(url),
              headers: {'Content-Type': 'application/json'},
              body: jsonEncode(payload),
            )
            .timeout(const Duration(seconds: 10));

        if (response.statusCode >= 200 && response.statusCode < 300) {
          debugPrint('[AUTO_SHIFT_END] ✅ Break log created successfully');

          // Parse response to get server break ID if needed, but mainly we just need to go offline
          final body = jsonDecode(response.body);
          final det = (body['details'] is Map) ? body['details'] : body;
          final serverBreakId = det['breakid'];

          if (serverBreakId != null) {
            await prefs.setInt(
              'breakId',
              int.tryParse('$serverBreakId') ?? localBreakId,
            );
          }
          await prefs.setString('breakStart', breakstart);
          await prefs.setInt('break_start_epoch', now.millisecondsSinceEpoch);
        } else {
          debugPrint(
            '[AUTO_SHIFT_END] ⚠️ Failed to create break log: ${response.statusCode}',
          );
        }
      } catch (e) {
        debugPrint('[AUTO_SHIFT_END] ❌ API Error: $e');
        // Even if API fails, we MUST go offline locally to prevent further issues
      }

      // 5. Set Offline Locally
      await prefs.setInt('onduty', 0);
      await prefs.setBool('online', false);

      // 6. Update Rider Log (Set Duty = 0)
      // We should also update the main rider log to say onduty=0
      final updateUrl = ApiConstants.mainRoute == 'live'
          ? ApiConstants.updateRiderLogLive
          : ApiConstants.updateRiderLogDev;

      final updatePayload = {
        "userid": userid,
        "onduty": 0,
        "latitude": lat,
        "longitude": lng,
      };

      try {
        await _httpClient
            .post(
              Uri.parse(updateUrl),
              headers: {'Content-Type': 'application/json'},
              body: jsonEncode(updatePayload),
            )
            .timeout(const Duration(seconds: 5));
        debugPrint('[AUTO_SHIFT_END] ✅ Rider status updated to Offline');
      } catch (_) {}

      debugPrint('[AUTO_SHIFT_END] 🏁 Auto-shift end sequence complete.');
    } catch (e) {
      debugPrint('[AUTO_SHIFT_END] Critical error in _autoEndShift: $e');
    }
  }

  /// Trigger proximity alert once per delivery when within threshold
  static Future<void> _checkProximityAlert(
    Map<String, dynamic> order,
    Map<String, String> coords,
  ) async {
    try {
      final deliveryId =
          (order['deliveryid'] ?? order['orderid'] ?? '').toString();
      if (deliveryId.isEmpty) return;

      final dropLatRaw =
          (order['droplat'] ?? order['DropLat'] ?? order['deliverylat'] ?? '')
              .toString();
      final dropLngRaw = (order['droplon'] ??
              order['droplong'] ??
              order['DropLon'] ??
              order['DropLong'] ??
              order['deliverylong'] ??
              '')
          .toString();
      final dropLat = double.tryParse(dropLatRaw) ?? 0.0;
      final dropLng = double.tryParse(dropLngRaw) ?? 0.0;
      if (dropLat == 0 ||
          dropLng == 0 ||
          dropLat.abs() > 90 ||
          dropLng.abs() > 180) {
        return;
      }

      final riderLat = double.tryParse(coords['lat'] ?? '0') ?? 0.0;
      final riderLng = double.tryParse(coords['lng'] ?? '0') ?? 0.0;
      if (riderLat == 0 ||
          riderLng == 0 ||
          riderLat.abs() > 90 ||
          riderLng.abs() > 180) {
        return;
      }

      final distanceMeters = Geolocator.distanceBetween(
        dropLat,
        dropLng,
        riderLat,
        riderLng,
      );

      if (distanceMeters <= _proximityThresholdMeters) {
        final prefs = await SharedPreferences.getInstance();
        final alreadyAlerted = prefs.getBool(‘$_proximityAlertKeyPrefix$deliveryId’) ?? false;
        if (alreadyAlerted) {
          debugPrint(‘[PROXIMITY] Already alerted for deliveryId=$deliveryId, skipping’);
          return;
        }

        await NotificationServce.showLocalNotification(
          title: ‘Near delivery location’,
          body: ‘You\’ve reached your destination. Please update the status.’,
        );

        final played = await _playProximityAudio();
        await prefs.setBool(‘$_proximityAlertKeyPrefix$deliveryId’, true);

        debugPrint(
          ‘[PROXIMITY] Alerted deliveryId=$deliveryId at ${distanceMeters.toStringAsFixed(1)}m ‘
          ‘target=($dropLat,$dropLng) rider=($riderLat,$riderLng) played=$played’,
        );
      } else {
        debugPrint(
          ‘[PROXIMITY] Skipped alert for deliveryId=$deliveryId | distance=${distanceMeters.toStringAsFixed(1)}m’,
        );
      }
    } catch (e) {
      debugPrint('[PROXIMITY] Error sending alert: $e');
    }
  }

  static Future<bool> _playProximityAudio() async {
    try {
      if (!_audioReady) {
        await _proximityPlayer.setReleaseMode(ReleaseMode.stop);
        await _proximityPlayer.setVolume(1.0);
        _audioReady = true;
      }
      // Attempt to play bundled destination audio
      await _proximityPlayer.stop();
      await _proximityPlayer.play(AssetSource('audio/destination.mp3'));
      debugPrint('[PROXIMITY][AUDIO] Playing destination.mp3');
      return true;
    } catch (e) {
      debugPrint('[PROXIMITY][AUDIO] Error playing destination clip: $e');
    }

    // Fallback to TTS if audio fails
    try {
      // Initialize once
      if (!_ttsReady) {
        await _tts.setLanguage('en-US');
        await _tts.setSpeechRate(0.9);
        await _tts.setVolume(1.0);
        await _tts.setPitch(1.0);
        _ttsReady = true;
      }
      // Speak without awaiting completion to avoid blocking
      await _tts.speak('You have reached your destination. Please update the delivery status.');
      debugPrint('[PROXIMITY][TTS] Spoke destination prompt');
      return true;
    } catch (e) {
      debugPrint('[PROXIMITY][TTS] Error speaking prompt: $e');
    }
    return false;
  }
}
