import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:geocoding/geocoding.dart' as geocoding;
import 'package:project_manager/consts.dart';
import 'package:project_manager/utils/api/deliveries.dart';
import 'package:project_manager/utils/dataObjects/delivery.dart';
import 'package:project_manager/utils/api/documents.dart';

class MapPage extends StatefulWidget {
  const MapPage({super.key});

  @override
  State<MapPage> createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {
  final Location _locationController = Location();

  final Completer<GoogleMapController> _mapController =
      Completer<GoogleMapController>();

  Map<PolylineId, Polyline> polylines = {};
  Map<MarkerId, Marker> markers = {};

  List<LatLng> waypoints = [];

  LatLng? _currentPosition;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getLocationUpdates().then(
      (_) => {
        getPolylinePoints(waypoints).then((coordinates) => {
              generatePolylineFromPoints(coordinates),
            }),
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  _launchMapsUrl(_currentPosition!, waypoints.last,
                      waypoints.sublist(0, waypoints.length - 1));
                },
                icon: const Icon(Icons.map),
                label: const Text("Open in Google Maps"),
              ),
              const SizedBox(width: 8), // A little spacing between buttons
              ElevatedButton.icon(
                onPressed: _pickDocument,
                icon: const Icon(Icons.import_export),
                label: const Text("Import Route"),
              ),
            ],
          ),
        ],
      ),
      body: _currentPosition == null
          ? const Center(
              child: Text("Loading..."),
            )
          : GoogleMap(
              onMapCreated: ((GoogleMapController controller) =>
                  _mapController.complete(controller)),
              initialCameraPosition: CameraPosition(
                target: _currentPosition!,
                zoom: 10,
              ),
              markers: Set<Marker>.of(markers.values),
              polylines: Set<Polyline>.of(polylines.values),
            ),
    );
  }

  _launchMapsUrl(
      LatLng source, LatLng destination, List<LatLng> waypoints) async {
    String waypointStr = waypoints
        .map((waypoint) => '${waypoint.latitude},${waypoint.longitude}')
        .join('|');

    final String googleMapsUrl =
        "https://www.google.com/maps/dir/?api=1&origin=${source.latitude},${source.longitude}&destination=${destination.latitude},${destination.longitude}&waypoints=$waypointStr&travelmode=driving";

    final Uri googleMapsUri = Uri.parse(googleMapsUrl);

    if (await canLaunchUrl(googleMapsUri)) {
      await launchUrl(googleMapsUri);
    } else {
      throw 'Could not open Google Maps';
    }
  }

  Future<void> _cameraToPosition(LatLng position) async {
    final GoogleMapController controller = await _mapController.future;
    CameraPosition newCameraPosition =
        CameraPosition(target: (position), zoom: 13);
    await controller
        .animateCamera(CameraUpdate.newCameraPosition(newCameraPosition));
  }

  Future<void> getLocationUpdates() async {
    bool serviceEnabled;
    PermissionStatus permissionGranted;

    serviceEnabled = await _locationController.serviceEnabled();
    if (serviceEnabled) {
      serviceEnabled = await _locationController.requestService();
    } else {
      return;
    }

    permissionGranted = await _locationController.hasPermission();
    if (permissionGranted == PermissionStatus.denied) {
      permissionGranted = await _locationController.requestPermission();
      if (permissionGranted != PermissionStatus.granted) {
        return;
      }
    }

    _locationController.onLocationChanged
        .listen((LocationData currentLocation) {
      if (_currentPosition == null) {
        setState(() {
          _currentPosition =
              LatLng(currentLocation.latitude!, currentLocation.longitude!);
          _cameraToPosition(_currentPosition!);
        });
      }
    });
  }

  Future<List<LatLng>> getPolylinePoints(List<LatLng> coordinates) async {
    List<LatLng> polylineCoordinates = [];
    PolylinePoints polylinePoints = PolylinePoints();

    for (int i = 0; i < coordinates.length - 1; i++) {
      PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
        GOOGLE_MAPS_API_KEY,
        PointLatLng(coordinates[i].latitude, coordinates[i].longitude),
        PointLatLng(coordinates[i + 1].latitude, coordinates[i + 1].longitude),
        travelMode: TravelMode.driving,
      );

      if (result.points.isNotEmpty) {
        for (var point in result.points) {
          polylineCoordinates.add(LatLng(point.latitude, point.longitude));
        }
      } else {
        print(result.errorMessage);
      }
    }
    return polylineCoordinates;
  }

  void generatePolylineFromPoints(List<LatLng> polylineCoordinates) async {
    // generate a unique id for the polyline
    PolylineId id = const PolylineId("poly");
    Polyline polyline = Polyline(
      polylineId: id,
      color: Colors.blueAccent,
      points: polylineCoordinates,
      width: 8,
    );
    setState(() {
      polylines[id] = polyline;
    });
  }

  Future<void> _pickDocument() async {
    // Upload the document and wait for the process to complete
    var uploadResult =
        await Documents.uploadDocument(await Documents.pickDocument());
    if (uploadResult) {
      // Proceed to fetch and display routes only if the upload was successful
      await _importAndDisplayRoutes();
    } else {
      print("Upload failed");
    }
  }

  Future<void> _importAndDisplayRoutes() async {
    try {
      List<Delivery> deliveries = await fetchDeliveries();
      await _getLatLngListFromDeliveries(deliveries);
      print("Waypoints: $waypoints");

      // If you need to clear existing waypoints, do it here
      polylines.clear();

      // Clear existing markers if needed
      setState(() {
        markers.clear();
      });

      // Add new markers
      int i = 0;
      for (var waypoint in waypoints) {
        _addMarkerForDelivery(waypoint, deliveries[i]);
        i++;
      }

      // Generate and display new polylines
      getPolylinePoints(waypoints).then((coordinates) {
        generatePolylineFromPoints(coordinates);
      });
    } catch (e) {
      // Handle the error, possibly show an alert to the user
      print('Error fetching or processing deliveries: $e');
    }
  }

  Future<void> _getLatLngListFromDeliveries(List<Delivery> deliveries) async {
    List<LatLng> totalWaypoints = [];
    List<String> addresses = [];
    int i = 0;
    String lastAddress = "";
    for (var delivery in deliveries) {
      String address = "";
      if (delivery.addressLine2 != '0') {
        String? addressLine2 = delivery.addressLine2;
        address =
            '${delivery.addressLine1} $addressLine2,${delivery.recipientCity} ${delivery.recipientState} ${delivery.recipientZipCode}';
      } else {
        address =
            '${delivery.addressLine1},${delivery.recipientCity} ${delivery.recipientState} ${delivery.recipientZipCode}';
      }
      if (address == lastAddress) {
        continue;
      } else {
        lastAddress = address;
      }
      print("Address $i = $address");
      addresses.add(address);
      i++;
      totalWaypoints.add(await geocodeAddress(address));
    }
    setState(() {
      waypoints = totalWaypoints;
    });
  }

  Future<LatLng> geocodeAddress(String address) async {
    List<geocoding.Location> locations =
        await geocoding.locationFromAddress(address);
    return LatLng(locations[0].latitude, locations[0].longitude);
  }

  void _addMarkerForDelivery(LatLng point, Delivery delivery) {
    final MarkerId markerId = MarkerId(delivery.deliveryId.toString());
    final Marker marker = Marker(
      markerId: markerId,
      position: LatLng(
        point.latitude,
        point.longitude,
      ),
      infoWindow: InfoWindow(
        title: delivery.recipientName,
        snippet: delivery.addressLine1,
      ),
    );

    setState(() {
      markers[markerId] = marker;
    });
  }
}
