import "dart:async";
import "dart:convert";
import "dart:developer";
import "dart:io";
import "package:anyway/main.dart";
import 'package:dio/dio.dart';

import 'package:anyway/constants.dart';
import "package:anyway/utils/load_landmark_image.dart";
import "package:anyway/structs/landmark.dart";
import "package:anyway/structs/trip.dart";
import "package:anyway/structs/preferences.dart";


Dio dio = Dio(
  BaseOptions(
    baseUrl: API_URL_BASE,
    connectTimeout: const Duration(seconds: 5),
    receiveTimeout: const Duration(seconds: 120),
    // also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors
    validateStatus: (status) => status! <= 500,
    receiveDataWhenStatusError: true,
    contentType: Headers.jsonContentType,
    responseType: ResponseType.json,
  ),
);


fetchTrip(
  Trip trip,
  UserPreferences preferences,
) async {
  Map<String, dynamic> data = {
    "preferences": preferences.toJson(),
    "start": trip.landmarks.first.location,
  };
  String dataString = jsonEncode(data);
  log(dataString);

  late Response response;
  try {
     response = await dio.post(
      "/trip/new",
      data: data
    );
  } catch (e) {
    trip.updateUUID("error");

    // Format the error message to be more user friendly
    String errorDescription;
    if (e is DioException) {
        errorDescription = e.message ?? "Unknown error";
    } else if (e is SocketException) {
        errorDescription = "No internet connection";
    } else if (e is TimeoutException) {
        errorDescription = "Request timed out";
    } else {
        errorDescription = "Unknown error";
    }

    String errorMessage = """
      We're sorry, the following error was generated:

      ${errorDescription.trim()}
    """.trim();

    trip.updateError(errorMessage);
    log(e.toString());
    log(errorMessage);
    return;
  }

  // handle more specific errors
  if (response.statusCode != 200) {
    trip.updateUUID("error");
    String errorDescription;
    if (response.data.runtimeType == String) {
      errorDescription = response.data;
    } else if (response.data.runtimeType == Map<String, dynamic>) {
      errorDescription = response.data["detail"] ?? "Unknown error";
    } else {
      errorDescription = "Unknown error";
    }

    String errorMessage = """
      We're sorry, our servers generated the following error:

      ${errorDescription.trim()}
      Please try again.
    """.trim();
    trip.updateError(errorMessage);
    log(errorMessage);
    // Actualy no need to throw an exception, we can just log the error and let the user retry
    // throw Exception(errorDetail);
  } else {

    // if the response data is not json, throw an error
    if (response.data is! Map<String, dynamic>) {
      log("${response.data.runtimeType}");
      trip.updateUUID("error");
      String errorMessage = """
        We're sorry, our servers generated the following error:

        ${response.data.trim()}
        Please try again.
      """.trim();
      trip.updateError(errorMessage);
      log(errorMessage);
      return;
    }

    Map<String, dynamic> json = response.data;

    // only fill in the trip "meta" data for now
    trip.loadFromJson(json);

    // now fill the trip with landmarks
    // we are going to recreate ALL the landmarks from the information given by the api
    trip.landmarks.remove(trip.landmarks.first);
    String? nextUUID = json["first_landmark_uuid"];
    while (nextUUID != null) {
      var (landmark, newUUID) = await fetchLandmark(nextUUID);
      trip.addLandmark(landmark);
      nextUUID = newUUID;
    }

  log(response.data.toString());
  // Also save the trip for the user's convenience
  savedTrips.addTrip(trip);
  }
}


patchLandmarkImage(Landmark landmark) async {
  // patch the landmark to include an image from an external source
  if (landmark.imageURL == null) {
    String? newUrl = await getImageUrlFromName(landmark.name);
    if (newUrl != null) {
      landmark.imageURL = newUrl;
    }
  } else if (landmark.imageURL!.contains("photos.app.goo.gl")) {
    // the image is a google photos link, we should get the image behind the link
    String? newUrl = await getImageUrlFromGooglePhotos(landmark.imageURL!);
    // also set the new url if it is null
    landmark.imageURL = newUrl;
  }
}

Future<(Landmark, String?)> fetchLandmark(String uuid) async {
  final response = await dio.get(
    "/landmark/$uuid"
  );

  // handle errors
  if (response.statusCode != 200) {
    throw Exception('Failed to load landmark');
  }
  if (response.data["detail"] != null) {
    throw Exception(response.data["detail"]);
  }
  // log(response.data.toString());
  Map<String, dynamic> json = response.data;
  String? nextUUID = json["next_uuid"];
  Landmark landmark = Landmark.fromJson(json);
  patchLandmarkImage(landmark);
  return (landmark, nextUUID);
}
