import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gen_service/Models/GetInTouchListResponse.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geocoding/geocoding.dart' as geocoding;
import 'package:location/location.dart' as Location;
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';

import '../Services/api_calling.dart';

class MapProvider extends ChangeNotifier {
  GetInTouchListResponse? _response;
  List<GetInTouchList> _locationsList = [];
  final String _googleApikey = "AIzaSyBGzvgMMKwPBAANTwaoRsAnrCpiWCj8wVs";
  GoogleMapController? _mapController;
  CameraPosition? _cameraPosition;
  final LatLng _startLocation = const LatLng(
    17.439112226708446,
    78.43292499146135,
  );
  String _latlongs = "";
  List<Marker> _markers = [];
  List<String> _addresses = [];
  Location.LocationData? _currentLocation;
  bool _isLocationEnabled = false;
  bool _hasLocationPermission = false;
  Timer? _timer;
  bool _isLoading = true;
  Timer? _debounceTimer;
  LatLng? _mapCenter;

  GetInTouchListResponse? get response => _response;
  List<GetInTouchList> get locationsList => _locationsList;
  String get googleAPIKey => _googleApikey;
  GoogleMapController? get mapController => _mapController;
  CameraPosition? get cameraPosition => _cameraPosition;
  LatLng get startLocation => _startLocation;
  String get latlongs => _latlongs;
  List<Marker> get markers => _markers;
  List<String> get addresses => _addresses;
  Location.LocationData? get currentLocation => _currentLocation;
  bool get isLocationEnabled => _isLocationEnabled;
  bool get hasLocationPermission => _hasLocationPermission;
  bool get isLoading => _isLoading;
  Timer? get timer => _timer;

  set markers(List<Marker> value) {
    _markers = value;
    if (value.isNotEmpty) {
      _isLoading = false; // Mark screen as loaded when markers are added
    }
    notifyListeners();
  }

  set mapController(GoogleMapController? value) {
    _mapController = value;
    notifyListeners();
  }

  set mapCenter(LatLng? value) {
    _mapCenter = value;
    _latlongs = value != null ? '${value.latitude},${value.longitude}' : '';
    notifyListeners();
  }

  set isLoading(bool value) {
    _isLoading = value;
    notifyListeners();
  }

  void resetAll() {
    _markers = [];
    _addresses = [];
    _mapCenter = null;
    _isLoading = true;
    notifyListeners();
  }

  Future<void> getLocationPermission(
    BuildContext context,
    empId,
    session,
  ) async {
    _isLocationEnabled = await Geolocator.isLocationServiceEnabled();
    LocationPermission permission = await Geolocator.checkPermission();
    _hasLocationPermission =
        permission == LocationPermission.always ||
        permission == LocationPermission.whileInUse;

    final Location.Location location = Location.Location();
    bool serviceEnabled;
    Location.PermissionStatus permissionGranted;
    serviceEnabled = await location.serviceEnabled();
    if (!serviceEnabled) {
      serviceEnabled = await location.requestService();
      if (!serviceEnabled) {
        _isLoading = false;
        return;
      }
    }
    permissionGranted = await location.hasPermission();
    if (permissionGranted == Location.PermissionStatus.denied) {
      permissionGranted = await location.requestPermission();
      if (permissionGranted != Location.PermissionStatus.granted) {
        // toast(context, 'Location permission denied.');
        _isLoading = false;
        return;
      }
    }
    final Location.LocationData locData = await location.getLocation();

    _currentLocation = locData;

    if (_currentLocation != null && _mapCenter == null) {
      _mapCenter = LatLng(
        _currentLocation!.latitude!,
        _currentLocation!.longitude!,
      );
      _latlongs =
          '${_currentLocation!.latitude},${_currentLocation!.longitude}';
      _mapController?.animateCamera(CameraUpdate.newLatLng(_mapCenter!));
      await nearbyServiceLocations(context, empId, session);
    }
    notifyListeners();
  }

  void onCameraMove(
    BuildContext context,
    CameraPosition position,
    empId,
    session,
  ) {
    _timer?.cancel();
    _mapCenter = position.target;
    _latlongs = '${_mapCenter!.latitude},${_mapCenter!.longitude}';
    _timer = Timer(Duration(seconds: 1), () {
      nearbyServiceLocations(context, empId, session);
    });
    notifyListeners();
  }

  void debounce(VoidCallback callback, Duration duration) {
    _debounceTimer?.cancel();
    _debounceTimer = Timer(duration, callback);
  }

  Future<void> nearbyServiceLocations(
    BuildContext context,
    empId,
    session,
  ) async {
    try {
      final data = await ApiCalling.fetchGetInTouchListApi(empId, session);
      if (data != null) {
        if (data.error == "0") {
          _isLoading = false;
          _response = data;
          _locationsList = data.getInTouchList ?? [];

          await updateMarkersFromApiResponse(
            context,
            data.getInTouchList ?? [],
          );
          if (_locationsList.isEmpty) {
            // toast(context, 'No leads found within the selected radius.');
          }
        } else {
          // toast(context, data.message ?? 'Failed to load leads.');
          _isLoading = false;
          notifyListeners();
        }
      } else {
        // toast(context, "Something went wrong, please try again.");
        _isLoading = false;
        notifyListeners();
      }
    } on Exception catch (e, s) {
      print("nearbyServiceLocations error: $e, stack: $s");
      // toast(context, 'An error occurred while loading leads.');
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> updateMarkersFromApiResponse(
    BuildContext context,
    List<GetInTouchList> locationsList,
  ) async {
    print("markers updating");

    try {
      _markers = await createMarkersFromApiResponse(context, locationsList);
      _addresses.clear();
      await Future.forEach(locationsList, (store) async {
        String address = await _getAddressFromLatLng(store.loc);
        _addresses.add(address);
      });
    } catch (e) {}
    notifyListeners();
  }

  Future<List<Marker>> createMarkersFromApiResponse(
    BuildContext context,
    List<GetInTouchList> locationsList,
  ) async {
    List<Marker> markers = [];
    ByteData data = await rootBundle.load("assets/images/maps_ic.png");
    Uint8List bytes = data.buffer.asUint8List();

    await Future.forEach(locationsList, (leads) async {
      try {
        ui.Codec codec = await ui.instantiateImageCodec(bytes);
        ui.FrameInfo fi = await codec.getNextFrame();
        Uint8List resizedBytes =
            (await fi.image.toByteData(
              format: ui.ImageByteFormat.png,
            ))!.buffer.asUint8List();
        if (leads.loc != null) {
          if (leads.loc!.isNotEmpty) {
            markers.add(
              Marker(
                markerId: MarkerId(leads.id.toString()),
                position: parseLatLng(leads.loc),
                icon: BitmapDescriptor.fromBytes(resizedBytes),
                infoWindow: InfoWindow(
                  title: leads.branchName ?? 'Unknown Lead',
                  snippet: leads.address ?? 'No address available',

                  anchor: const Offset(0.5, 1.0),
                ),
                zIndex: 100,
                onTap: () {
                  _mapController?.showMarkerInfoWindow(
                    MarkerId(leads.id.toString()),
                  );
                  print(
                    'Marker tapped: id=${leads.id}, name=${leads.branchName}',
                  );
                },
              ),
            );
          }
        }
      } catch (e) {
        print("Exception at Markers: $e");
      }
    });
    return markers;
  }

  LatLng parseLatLng(String? location) {
    if (location != null) {
      List<String> parts = location.split(',');
      if (parts.length == 2) {
        double lat = double.tryParse(parts[0]) ?? 0.0;
        double lng = double.tryParse(parts[1]) ?? 0.0;
        if (lat != 0.0 && lng != 0.0) {
          return LatLng(lat, lng);
        }
      }
    }
    print('Invalid location string: $location');
    return const LatLng(0.0, 0.0);
  }

  Future<String> _getAddressFromLatLng(String? location) async {
    if (location != null) {
      List<String> parts = location.split(',');
      if (parts.length == 2) {
        double lat = double.tryParse(parts[0]) ?? 0.0;
        double lng = double.tryParse(parts[1]) ?? 0.0;
        if (lat != 0.0 && lng != 0.0) {
          try {
            List<geocoding.Placemark> placemarks = await geocoding
                .placemarkFromCoordinates(lat, lng);
            if (placemarks.isNotEmpty) {
              final placemark = placemarks.first;
              String address =
                  '${placemark.street ?? ''}, '
                  '${placemark.thoroughfare ?? ''} '
                  '${placemark.subLocality ?? ''}, '
                  '${placemark.locality ?? ''}, '
                  '${placemark.administrativeArea ?? ''}, '
                  '${placemark.subAdministrativeArea ?? ''} '
                  '${placemark.postalCode ?? ''}, '
                  '${placemark.country ?? ''}';
              return address.trim();
            }
          } catch (e) {
            print('Geocoding error for location $location: $e');
          }
        }
      }
    }
    return "Address not found";
  }
}
