import 'dart:async';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'dart:math' as math;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:nearle/views/helpers/constants/apiconstants.dart';
import 'package:nearle/providers/Riderlog/riderlog_provider.dart';
import 'package:nearle/background/backgroundservice.dart';
import 'package:geolocator/geolocator.dart';
import 'package:nearle/utils/kalman_filter.dart';
import 'package:nearle/utils/mqtt_service.dart';
import 'package:nearle/views/helpers/constants/mqtt_constants.dart';
import 'package:battery_plus/battery_plus.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'dart:io';
import 'package:nearle/helpers/http_overrides.dart';

class _BackgroundRiderLog {
  static NearleKalmanFilter? _kf;
  static DateTime? _lastUpdateTime;

  static Future<Map<String, String>> _ensureLatLng() 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',
      'status': 'unknown',
      'accuracy': '0',
    };
    try {
      // 1. Check if location services are enabled
      final serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
        debugPrint('[BG_RIDER_LOG] Location services are disabled.');
        result['status'] = 'disabled';
        return result;
      }

      // 2. Check permissions
      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) {
        debugPrint('[BG_RIDER_LOG] Location permission denied.');
        result['status'] = 'denied';
        return result;
      }
      if (permission == LocationPermission.deniedForever) {
        debugPrint('[BG_RIDER_LOG] Location permission denied forever.');
        result['status'] = 'denied_forever';
        return result;
      }

      result['status'] = 'enabled';

      // 3. Get position (using non-deprecated LocationSettings + explicit timeout)
      final pos = await Geolocator.getCurrentPosition(
        locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.high,
        ),
      );

      // Reject mocked positions (anti-cheat)
      if (pos.isMocked) {
        debugPrint('[BG_RIDER_LOG] Mocked position detected — using cached');
        return result;
      }

      final now = DateTime.now();
      double outLat = pos.latitude;
      double outLng = pos.longitude;
      double speed = pos.speed;
      double heading = pos.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; // Default background interval
        _kf!.predict(dt);
        _kf!.update(outLat, outLng);
        outLat = _kf!.x[0];
        outLng = _kf!.x[1];
      }
      _lastUpdateTime = now;

      return {
        'lat': outLat.toStringAsFixed(6),
        'lng': outLng.toStringAsFixed(6),
        'raw_lat': pos.latitude.toStringAsFixed(6),
        'raw_lng': pos.longitude.toStringAsFixed(6),
        'speed': speed.toStringAsFixed(2),
        'heading': heading.toStringAsFixed(2),
        'velocity_lat': velocityLat.toStringAsFixed(4),
        'velocity_lng': velocityLng.toStringAsFixed(4),
        'status': 'enabled',
        'accuracy': pos.accuracy.toStringAsFixed(1),
      };
    } catch (e) {
      debugPrint('[BG_RIDER_LOG] Error getting location: $e');
      return result;
    }
  }

  static String _two(int n) => n.toString().padLeft(2, '0');
  static String _formatDateTimeFull(DateTime dt) {
    final y = dt.year.toString();
    final m = _two(dt.month);
    final d = _two(dt.day);
    final hh = _two(dt.hour);
    final mm = _two(dt.minute);
    final ss = _two(dt.second);
    return "$y-$m-$d $hh:$mm:$ss";
  }
  static String _formatTime(DateTime dt) {
    final hh = _two(dt.hour);
    final mm = _two(dt.minute);
    final ss = _two(dt.second);
    return "$hh:$mm:$ss";
  }

  /// Accumulates cumulative KMs for active deliveries using the foreground service GPS position.
  /// Only runs when LiveTrackingService (main isolate) hasn't updated in the last 10 seconds,
  /// which means the app is backgrounded/screen-off/power-saver and the main isolate is dormant.
  static Future<void> _accumulateBackgroundKms(
    SharedPreferences prefs,
    Map<String, String> loc,
  ) async {
    try {
      // Check if the main isolate's LiveTrackingService is still actively updating
      final lastLiveUpdateMs = prefs.getInt('live_tracking_last_update_ms') ?? 0;
      final nowMs = DateTime.now().millisecondsSinceEpoch;
      final secondsSinceLiveUpdate = (nowMs - lastLiveUpdateMs) / 1000.0;

      if (secondsSinceLiveUpdate < 10.0) {
        // Main isolate is active — let it handle KMs to avoid race conditions
        debugPrint(
          '[BG_KM] LiveTrackingService active (${secondsSinceLiveUpdate.toStringAsFixed(1)}s ago) — skipping background accumulation',
        );
        return;
      }

      // Skip if GPS accuracy is too poor for reliable KM tracking
      final double accuracy = double.tryParse(loc['accuracy'] ?? '9999') ?? 9999.0;
      if (accuracy > 50.0) {
        debugPrint(
          '[BG_KM] Low-accuracy position (${accuracy.toStringAsFixed(0)}m) — skipping KM accumulation',
        );
        return;
      }

      final double currentLat = double.tryParse(loc['lat'] ?? '0') ?? 0.0;
      final double currentLng = double.tryParse(loc['lng'] ?? '0') ?? 0.0;
      if (currentLat == 0.0 || currentLng == 0.0) return;

      final activeDeliveryIds =
          prefs.getStringList('active_tracking_delivery_ids') ?? [];

      for (final dId in activeDeliveryIds) {
        try {
          final lastLatStr = prefs.getString('delivery_tracking_${dId}_lastLat') ?? '';
          final lastLngStr = prefs.getString('delivery_tracking_${dId}_lastLng') ?? '';
          final currentCumKm = double.tryParse(
                prefs.getString('delivery_tracking_${dId}_cumulativeKm') ?? '0',
              ) ??
              0.0;

          if (lastLatStr.isNotEmpty && lastLngStr.isNotEmpty) {
            final lastLat = double.tryParse(lastLatStr) ?? 0.0;
            final lastLng = double.tryParse(lastLngStr) ?? 0.0;

            if (lastLat != 0.0 && lastLng != 0.0) {
              final distanceMeters = Geolocator.distanceBetween(
                lastLat,
                lastLng,
                currentLat,
                currentLng,
              );

              // Speed-based jump guard: reject if implied speed > 120 km/h (33.3 m/s).
              // Uses elapsed time since last recorded position so the threshold scales
              // correctly whether the background interval is 30s, 60s, or longer.
              final lastUpdateMs =
                  prefs.getInt('delivery_tracking_${dId}_lastUpdateMs') ?? 0;
              final nowMs = DateTime.now().millisecondsSinceEpoch;
              final elapsedSeconds = lastUpdateMs > 0
                  ? (nowMs - lastUpdateMs) / 1000.0
                  : 60.0; // conservative default
              final maxRealisticMeters = elapsedSeconds * 33.3; // 120 km/h ceiling

              if (distanceMeters > maxRealisticMeters && distanceMeters > 50.0) {
                // GPS jumped — update anchor without counting phantom distance
                debugPrint(
                  '[BG_KM] GPS jump for $dId: ${distanceMeters.toStringAsFixed(0)}m '
                  'in ${elapsedSeconds.toStringAsFixed(1)}s (max: ${maxRealisticMeters.toStringAsFixed(0)}m) — resetting anchor',
                );
                await prefs.setString('delivery_tracking_${dId}_lastLat', currentLat.toString());
                await prefs.setString('delivery_tracking_${dId}_lastLng', currentLng.toString());
                await prefs.setInt('delivery_tracking_${dId}_lastUpdateMs', nowMs);
              } else if (distanceMeters >= 5.0) {
                final newCumKm = currentCumKm + (distanceMeters / 1000.0);
                await prefs.setString(
                  'delivery_tracking_${dId}_cumulativeKm',
                  newCumKm.toStringAsFixed(4),
                );
                await prefs.setString('delivery_tracking_${dId}_lastLat', currentLat.toString());
                await prefs.setString('delivery_tracking_${dId}_lastLng', currentLng.toString());
                await prefs.setInt('delivery_tracking_${dId}_lastUpdateMs', nowMs);
                debugPrint(
                  '[BG_KM] +${(distanceMeters / 1000.0).toStringAsFixed(4)} km for $dId '
                  'in ${elapsedSeconds.toStringAsFixed(1)}s (total: ${newCumKm.toStringAsFixed(4)} km)',
                );
              }
            }
          } else {
            // No anchor yet — set initial position
            await prefs.setString('delivery_tracking_${dId}_lastLat', currentLat.toString());
            await prefs.setString('delivery_tracking_${dId}_lastLng', currentLng.toString());
            await prefs.setInt(
              'delivery_tracking_${dId}_lastUpdateMs',
              DateTime.now().millisecondsSinceEpoch,
            );
          }
        } catch (_) {}
      }
    } catch (e) {
      debugPrint('[BG_KM] Error in background KM accumulation: $e');
    }
  }

  static Future<void> createLoginNow() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      // Reload from disk so we see the latest values written by the main isolate
      await prefs.reload();

      final int onduty = prefs.getInt('onduty') ?? 0;
      if (onduty != 1) {
        return;
      }
      final int? userid = prefs.getInt('userId') ?? prefs.getInt('userid');
      final int? partnerid = prefs.getInt('partnerId') ?? prefs.getInt('partnerid');
      final int? shiftid = prefs.getInt('shiftId') ?? prefs.getInt('shiftid');
      if ((userid ?? 0) == 0) return;

      // Prefer explicit username, then fallback to stored full name or first/last
      String? username = prefs.getString('username');
      username ??= prefs.getString('user_name');
      if (username == null || username.trim().isEmpty) {
        final first = prefs.getString('firstname') ?? '';
        final last = prefs.getString('lastname') ?? '';
        final combined = ('$first $last').trim();
        if (combined.isNotEmpty) {
          username = combined;
        }
      }

      // ✅ Check if there are active deliveries to set status
      final bool hasActiveDeliveries = prefs.getBool('has_live_deliveries') ?? false;
      final String riderStatus = hasActiveDeliveries ? 'active' : 'idle';

      final now = DateTime.now();
      final iso = _formatDateTimeFull(now);
      final loginTime = _formatTime(now);
      final loc = await _ensureLatLng();

      // Accumulate KMs in background when LiveTrackingService (main isolate) is not active
      await _accumulateBackgroundKms(prefs, loc);


      final int? tenantid = prefs.getInt('tenantid');
      final int? locationid = prefs.getInt('locationid');
      final int? applocationid = prefs.getInt('applocationid');
      final String? userfcmtoken = prefs.getString('userfcmtoken');
      
      final int? logid = prefs.getInt('logId') ?? prefs.getInt('logid');
      final String orderId = prefs.getString('current_riding_order_id') ?? '';
      
      final payload = {
        "logid": logid ?? 0,
        "userid": userid,
        "partnerid": partnerid,
        "shiftid": shiftid,
        "logdate": iso,
        "login": loginTime,
        "latitude": loc['lat'] ?? '0',
        "longitude": loc['lng'] ?? '0',
        "raw_latitude": loc['raw_lat'] ?? '0',
        "raw_longitude": loc['raw_lng'] ?? '0',
        "velocity_lat": loc['velocity_lat'] ?? '0',
        "velocity_lng": loc['velocity_lng'] ?? '0',
        "speed": loc['speed'] ?? '0',
        "heading": loc['heading'] ?? '0',
        "onduty": 1,
        "status": riderStatus,
        "contactno": prefs.getString('contactno') ?? '', 
        "tenantid": tenantid ?? 0,
        "locationid": locationid ?? 0,
        "applocationid": applocationid ?? 0,
        "userfcmtoken": userfcmtoken ?? '',
        "username": (username ?? '').trim(),
        "orderid": orderId,
      };

      final firstName = prefs.getString('firstname') ?? '';
      final lastName = prefs.getString('lastname') ?? '';
      if (firstName.trim().isNotEmpty) {
        payload['firstname'] = firstName.trim();
      }
      if (lastName.trim().isNotEmpty) {
        payload['lastname'] = lastName.trim();
      }

      final base = ApiConstants.mainRoute == 'live'
          ? ApiConstants.createRiderLogLive
          : ApiConstants.createRiderLogDev;

      final provider = CreateRiderLogProvider();
      final resp = await provider.createRiderLog(base, payload);
      
      if (resp == null || resp.isEmpty) return;
      final det = (resp['details'] is Map<String, dynamic>)
          ? (resp['details'] as Map<String, dynamic>)
          : resp;
      final newLogId = int.tryParse('${det['logid'] ?? 0}') ?? (det['logid'] as int? ?? 0);
        await prefs.setInt('logid', newLogId);
        await prefs.setInt('logId', newLogId);
      
      // ✅ MQTT BACKGROUND PUBLISH ( Lane Split )
      final mqttService = NearleMqttService();
      if (!mqttService.isConnected) {
        // Use a slightly different client ID for background to avoid kicking the main one off
        await mqttService.connect(); 
      }
      
      if (mqttService.isConnected) {
        // Gather Telemetry
        final battery = Battery();
        final int batteryLevel = await battery.batteryLevel;
        final BatteryState batteryState = await battery.batteryState;
        final isCharging = batteryState == BatteryState.charging || batteryState == BatteryState.full;
        
        final connectivity = await Connectivity().checkConnectivity();
        final String connType = connectivity.isNotEmpty ? connectivity.first.toString().split('.').last : 'none';

        // 1. Direct Telemetry (Feeding the /full API)
        mqttService.publish('battery', '$batteryLevel%');
        mqttService.publish('charging', isCharging ? 'yes' : 'no');
        mqttService.publish('speed', loc['speed'] ?? '0');
        mqttService.publish('connection', connType);
        mqttService.publish('accuracy', loc['accuracy'] ?? '0');

        // 2. Alert if Location is Off
        final String locStatus = loc['status'] ?? 'unknown';
        if (locStatus != 'enabled') {
          mqttService.publish('alerts', {
            'userid': userid,
            'username': (username ?? '').trim(),
            'event': 'location_turned_off',
            'error_type': locStatus,
            'battery': '$batteryLevel%',
            'is_charging': isCharging,
            'connection': connType,
            'logdate': iso,
          });
        }

        // 3. Low Battery Alert
        if (batteryLevel < 15 && !isCharging) {
          mqttService.publish('alerts', {
            'userid': userid,
            'username': (username ?? '').trim(),
            'event': 'low_battery_warning',
            'battery': '$batteryLevel%',
            'logdate': iso,
          });
        }

        // 4. Poor GPS Accuracy Alert
        final double accuracy = double.tryParse(loc['accuracy'] ?? '0') ?? 0;
        if (accuracy > 30) {
           mqttService.publish('alerts', {
            'userid': userid,
            'username': (username ?? '').trim(),
            'event': 'poor_gps_signal',
            'accuracy': '${accuracy.toStringAsFixed(1)}m',
            'logdate': iso,
          });
        }

        // 5. Lane: Status
        mqttService.updateStatus(riderStatus == 'active' ? 'Active' : MqttConstants.statusOnline);

        // 6. Lane: Periodic Log (Comprehensive Snapshot)
        mqttService.publishLog('rider_periodic_log', {
          'userid': userid,
          'username': username,
          'logdate': iso,
          'latitude': loc['lat'] ?? '0',
          'longitude': loc['lng'] ?? '0',
          'speed': loc['speed'] ?? '0',
          'heading': loc['heading'] ?? '0',
          'accuracy': loc['accuracy'] ?? '0',
          'status': riderStatus,
          'orderid': orderId,
          'battery': '$batteryLevel%',
          'is_charging': isCharging,
          'connection': connType,
          'location_service': locStatus,
          'is_background': true,
        });
      }
    } catch (e) {
      // ignore background errors
    }
  }
}

class RiderLogTaskHandler extends TaskHandler {
  Timer? _timer; // not used; plugin provides repeat callback, but keep safety

  @override
  Future<void> onStart(DateTime timestamp, SendPort? sendPort) async {
    // No-op
  }

  @override
  Future<void> onRepeatEvent(DateTime timestamp, SendPort? sendPort) async {
    // 1. Rider Log (existing)
    await _BackgroundRiderLog.createLoginNow();
    
    // 2. Delivery Log (new)
    await BackgroundDeliveryLog.processActiveDeliveries();

    // 3. Auto Shift End (new)
    await BackgroundDeliveryLog.checkShiftEnd();
  }

  @override
  Future<void> onDestroy(DateTime timestamp, SendPort? sendPort) async {
    _timer?.cancel();
    _timer = null;
  }
}

@pragma('vm:entry-point')
void riderLogCallback() {
  HttpOverrides.global = MyHttpOverrides();
  FlutterForegroundTask.setTaskHandler(RiderLogTaskHandler());
}

