import 'dart:async';
import 'dart:convert';
import 'dart:math' show Random, atan2, cos, pi, pow, sin, sqrt;
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';

import '../../../constant/links.dart';
import '../../../constant/api_key.dart';
import '../../../print.dart';
import '../../functions/crud.dart';
import 'map_engine_controller.dart';
import 'location_search_controller.dart';
import 'ride_lifecycle_controller.dart';
import '../../../models/model/locations.dart';
import 'car_location.dart';
import 'package:device_info_plus/device_info_plus.dart';

class NearbyDriversController extends GetxController {
  List carsLocationByPassenger = [];
  List<LatLng> driverCarsLocationToPassengerAfterApplied = [];
  List<CarLocationModel> carLocationsModels = [];
  String? currentDriverMarkerId;
  bool lowPerf = false;

  dynamic dataCarsLocationByPassenger;
  bool noCarString = false;
  final double minMovementThreshold = 2.0;
  final Map<String, Timer> _animationTimers = {};

  final List<Map<String, dynamic>> fakeCarData = [];

  Future<bool> getCarsLocationByPassengerAndReloadMarker() async {
    carsLocationByPassenger = [];
    final locSearch = Get.find<LocationSearchController>();

    if (locSearch.passengerLocation.latitude == 0 && locSearch.passengerLocation.longitude == 0) {
      return false;
    }

    var res = await CRUD().get(
      link: AppLink.getCarsLocationByPassenger,
      payload: {
        'lat': locSearch.passengerLocation.latitude.toString(),
        'lng': locSearch.passengerLocation.longitude.toString(),
        'radius': '5',
        'limit': '50',
      },
    );

    if (res == 'failure') {
      noCarString = true;
      dataCarsLocationByPassenger = 'failure';
      update();
      return false;
    }

    noCarString = false;
    var responseData = jsonDecode(res);
    dataCarsLocationByPassenger = responseData;

    List driversList = [];
    if (responseData['status'] == true && responseData['data'] != null) {
      driversList = responseData['data'];
    } else if (responseData['message'] != null) {
      driversList = responseData['message'];
    }

    final mapEngine = Get.find<MapEngineController>();

    if (driversList.isEmpty) {
      carsLocationByPassenger.clear();
      mapEngine.update();
      return false;
    }

    carsLocationByPassenger.clear();

    for (var i = 0; i < driversList.length; i++) {
      var carData = driversList[i];

      double lat = double.tryParse(carData['latitude'].toString()) ?? 0.0;
      double lng = double.tryParse(carData['longitude'].toString()) ?? 0.0;
      double heading = double.tryParse(carData['heading'].toString()) ?? 0.0;

      if (lat == 0.0 || lng == 0.0) continue;

      String driverId = (carData['driver_id'] ?? carData['id'] ?? '').toString();
      if (driverId.isEmpty || driverId == 'null') continue;

      _updateOrCreateMarker(
        driverId,
        LatLng(lat, lng),
        heading,
        _getIconForCar(carData),
      );
    }

    mapEngine.update();
    return true;
  }

  void _addFakeCarMarkers(LatLng center, int count) {
    if (fakeCarData.isEmpty) {
      Random random = Random();
      double radiusInKm = 2.5;

      for (int i = 0; i < count; i++) {
        double angle = random.nextDouble() * 2 * pi;
        double distance = sqrt(random.nextDouble()) * radiusInKm;

        double latOffset = (distance / 111.32);
        double lonOffset =
            (distance / (111.32 * cos(center.latitude * pi / 180.0)));

        double lat = center.latitude + (latOffset * cos(angle));
        double lon = center.longitude + (lonOffset * sin(angle));

        double heading = random.nextDouble() * 360;

        fakeCarData.add({
          'id': 'fake_$i',
          'latitude': lat,
          'longitude': lon,
          'heading': heading,
          'gender': 'Male',
        });
      }
    }

    for (var carData in fakeCarData) {
      _updateOrCreateMarker(
        carData['id'].toString(),
        LatLng(carData['latitude'], carData['longitude']),
        carData['heading'],
        _getIconForCar(carData),
      );
    }
  }

  void addFakeCarMarkers(LatLng center, int count) {
    _addFakeCarMarkers(center, count);
  }

  Future<CarLocation?> getNearestDriverByPassengerLocation() async {
    final rideLife = Get.find<RideLifecycleController>();
    final locSearch = Get.find<LocationSearchController>();

    if (!rideLife.rideConfirm) {
      if (dataCarsLocationByPassenger != 'failure' &&
          dataCarsLocationByPassenger != null &&
          dataCarsLocationByPassenger.containsKey('message') &&
          dataCarsLocationByPassenger['message'] != null &&
          dataCarsLocationByPassenger['message'].length > 0) {
        double nearestDistance = double.infinity;
        CarLocation? nearestCar;

        for (var i = 0;
            i < dataCarsLocationByPassenger['message'].length;
            i++) {
          var carLocation = dataCarsLocationByPassenger['message'][i];

          try {
            final distance = Geolocator.distanceBetween(
              locSearch.passengerLocation.latitude,
              locSearch.passengerLocation.longitude,
              double.parse(carLocation['latitude']),
              double.parse(carLocation['longitude']),
            );

            int durationToPassenger = (distance / 1000 / 25 * 3600).round();
            update();

            if (distance < nearestDistance) {
              nearestDistance = distance;

              nearestCar = CarLocation(
                distance: distance,
                duration: durationToPassenger.toDouble(),
                id: carLocation['driver_id'],
                latitude: double.parse(carLocation['latitude']),
                longitude: double.parse(carLocation['longitude']),
              );
              update();
            }
          } catch (e) {
            Log.print('Error calculating distance/duration: $e');
          }
        }
        return nearestCar;
      }
    }
    return null;
  }

  Future<CarLocation?> getNearestDriverByPassengerLocationAPIGOOGLE() async {
    final rideLife = Get.find<RideLifecycleController>();
    final mapEngine = Get.find<MapEngineController>();
    final locSearch = Get.find<LocationSearchController>();

    if (mapEngine.polyLines.isEmpty || rideLife.totalCostPassenger == 0) {
      return null;
    }
    if (!rideLife.rideConfirm) {
      double nearestDistance = double.infinity;
      if (dataCarsLocationByPassenger != 'failure' &&
          dataCarsLocationByPassenger != null &&
          dataCarsLocationByPassenger.containsKey('message') &&
          dataCarsLocationByPassenger['message'] != null) {
        if (dataCarsLocationByPassenger['message'].length > 0) {
          CarLocation? nearestCar;
          for (var i = 0;
              i < dataCarsLocationByPassenger['message'].length;
              i++) {
            var carLocation = dataCarsLocationByPassenger['message'][i];

            update();
            String apiUrl =
                '${AppLink.googleMapsLink}distancematrix/json?destinations=${carLocation['latitude']},${carLocation['longitude']}&origins=${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}&units=metric&key=${AK.mapAPIKEY}';
            var response = await CRUD().getGoogleApi(link: apiUrl, payload: {});
            if (response != null && response['status'] == "OK") {
              var data = response;
              int distance1 =
                  data['rows'][0]['elements'][0]['distance']['value'];
              rideLife.distanceByPassenger =
                  data['rows'][0]['elements'][0]['distance']['text'];
              rideLife.durationToPassenger =
                  data['rows'][0]['elements'][0]['duration']['value'];

              Duration durationFromDriverToPassenger =
                  Duration(seconds: rideLife.durationToPassenger.toInt());
              rideLife.stringRemainingTimeToPassenger =
                  data['rows'][0]['elements'][0]['duration']['text'];
              update();
              if (distance1 < nearestDistance) {
                nearestDistance = distance1.toDouble();

                nearestCar = CarLocation(
                  distance: distance1.toDouble(),
                  duration: rideLife.durationToPassenger.toDouble(),
                  id: carLocation['driver_id'],
                  latitude: double.parse(carLocation['latitude']),
                  longitude: double.parse(carLocation['longitude']),
                );
                update();
              }
            } else {
              Log.print('${response?['status']}: error Google distance matrix');
            }
          }
          return nearestCar;
        }
      }
    }
    return null;
  }

  Future getCarForFirstConfirm(String carType) async {
    bool foundCars = false;
    int attempt = 0;

    Timer.periodic(const Duration(seconds: 4), (Timer t) async {
      foundCars = await getCarsLocationByPassengerAndReloadMarker();
      Log.print('foundCars: $foundCars');

      if (foundCars) {
        t.cancel();
      } else if (attempt >= 4) {
        t.cancel();
        if (!foundCars) {
          noCarString = true;
          dataCarsLocationByPassenger = 'failure';
        }
        update();
      }
      attempt++;
    });
  }

  void startCarLocationSearch(String carType) {
    int searchInterval = 5;
    Log.print('searchInterval: $searchInterval');
    int boundIncreaseStep = 2500;
    Log.print('boundIncreaseStep: $boundIncreaseStep');
    int maxAttempts = 3;
    int maxBoundIncreaseStep = 6000;
    int attempt = 0;
    Log.print('initial attempt: $attempt');

    Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async {
      Log.print('Current attempt: $attempt');
      bool foundCars = false;
      final mapEngine = Get.find<MapEngineController>();
      if (attempt >= maxAttempts) {
        timer.cancel();
        if (foundCars == false) {
          noCarString = true;
          update();
        }
      } else if (mapEngine.reloadStartApp == true) {
        Log.print('reloadStartApp: ${mapEngine.reloadStartApp}');
        foundCars = await getCarsLocationByPassengerAndReloadMarker();
        Log.print('foundCars: $foundCars');

        if (foundCars) {
          timer.cancel();
        } else {
          attempt++;
          Log.print('Incrementing attempt to: $attempt');

          if (boundIncreaseStep < maxBoundIncreaseStep) {
            boundIncreaseStep += 1500;
            if (boundIncreaseStep > maxBoundIncreaseStep) {
              boundIncreaseStep = maxBoundIncreaseStep;
            }
            Log.print('New boundIncreaseStep: $boundIncreaseStep');
          }
        }
      }
    });
  }

  String _getIconForCar(Map<String, dynamic> carData) {
    final mapEngine = Get.find<MapEngineController>();
    if (carData['model'].toString().contains('دراجة')) {
      return mapEngine.motoIcon;
    } else if (carData['gender'] == 'Female') {
      return mapEngine.ladyIcon;
    } else {
      return mapEngine.carIcon;
    }
  }

  void _updateOrCreateMarker(
      String markerId, LatLng newPosition, double newHeading, String icon) {
    final mapEngine = Get.find<MapEngineController>();
    if (!mapEngine.isIconsLoaded) {
      Log.print("⚠️ Skipping drawing marker $markerId because map icons are not fully loaded yet.");
      return;
    }
    final mId = MarkerId(markerId);
    final existingMarker = mapEngine.markers.cast<Marker?>().firstWhere(
          (m) => m?.markerId == mId,
          orElse: () => null,
        );

    if (existingMarker == null) {
      mapEngine.markers = {
        ...mapEngine.markers,
        Marker(
          markerId: mId,
          position: newPosition,
          rotation: newHeading,
          icon: InlqBitmap.fromStyleImage(icon),
          anchor: const Offset(0.5, 0.5),
        ),
      };
      mapEngine.update();
    } else {
      double distance = Geolocator.distanceBetween(
          existingMarker.position.latitude,
          existingMarker.position.longitude,
          newPosition.latitude,
          newPosition.longitude);
      if (distance >= minMovementThreshold) {
        _smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
      }
    }
  }

  void _smoothlyUpdateMarker(
      Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
    final mapEngine = Get.find<MapEngineController>();
    final MarkerId markerIdKey = oldMarker.markerId;

    _animationTimers[markerIdKey.value]?.cancel();

    int ticks = 0;
    const int totalSteps = 20;
    const int stepDuration = 50;

    double latStep =
        (newPosition.latitude - oldMarker.position.latitude) / totalSteps;
    double lngStep =
        (newPosition.longitude - oldMarker.position.longitude) / totalSteps;
    double headingStep = (newHeading - oldMarker.rotation) / totalSteps;

    LatLng currentPos = oldMarker.position;
    double currentHeading = oldMarker.rotation;

    _animationTimers[markerIdKey.value] =
        Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
      ticks++;

      currentPos =
          LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
      currentHeading += headingStep;

      final updatedMarker = oldMarker.copyWith(
        position: currentPos,
        rotation: currentHeading,
        icon: InlqBitmap.fromStyleImage(icon),
      );

      mapEngine.markers = {
        ...mapEngine.markers.where((m) => m.markerId != markerIdKey),
        updatedMarker,
      };

      if (mapEngine.mapController != null) {
        mapEngine.mapController!.animateCamera(CameraUpdate.newLatLng(currentPos));
      }

      mapEngine.update();

      if (ticks >= totalSteps) {
        timer.cancel();
        _animationTimers.remove(markerIdKey.value);
      }
    });
  }

  double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
    double deltaLon = lon2 - lon1;
    double y = sin(deltaLon) * cos(lat2);
    double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLon);
    double bearing = atan2(y, x);
    return (bearing * 180 / pi + 360) % 360;
  }

  void analyzeBehavior(Position currentPosition, List<LatLng> routePoints) {
    double actualBearing = currentPosition.heading;
    double expectedBearing = calculateBearing(
      routePoints[0].latitude,
      routePoints[0].longitude,
      routePoints[1].latitude,
      routePoints[1].longitude,
    );

    double bearingDifference = (expectedBearing - actualBearing).abs();
    if (bearingDifference > 30) {
      Log.print("⚠️ السائق انحرف عن المسار!");
    }
  }

  void detectStops(Position currentPosition) {
    if (currentPosition.speed < 0.5) {
      Log.print("🚦 السائق توقف في موقع غير متوقع!");
    }
  }

  Future<void> detectPerfMode() async {
    try {
      if (GetPlatform.isAndroid) {
        final info = await DeviceInfoPlugin().androidInfo;
        final sdk = info.version.sdkInt;
        final ram = info.availableRamSize;
        lowPerf = (sdk < 28) || (ram > 0 && ram < 3 * 1024 * 1024 * 1024);
      } else {
        lowPerf = false;
      }
    } catch (_) {
      lowPerf = false;
    }
    update();
  }

  @override
  void onClose() {
    _animationTimers.forEach((key, timer) => timer.cancel());
    _animationTimers.clear();
    super.onClose();
  }
}
