
import 'dart:convert';
import 'dart:io';

import 'package:app_hit_a_wod/src/models/array_response.model.dart';
import 'package:app_hit_a_wod/src/models/bi_exercises.model.dart';
import 'package:app_hit_a_wod/src/models/box_order.model.dart';
import 'package:app_hit_a_wod/src/models/company.model.dart';
import 'package:app_hit_a_wod/src/models/exercise.model.dart';
import 'package:app_hit_a_wod/src/models/exercise_benchmark.model.dart';
import 'package:app_hit_a_wod/src/models/login_response.model.dart';
import 'package:app_hit_a_wod/src/models/new.model.dart';
import 'package:app_hit_a_wod/src/models/pricing.model.dart';
import 'package:app_hit_a_wod/src/models/stripe_user.model.dart';
import 'package:app_hit_a_wod/src/models/training_session.model.dart';
import 'package:app_hit_a_wod/src/models/bi_training_session.model.dart';
import 'package:app_hit_a_wod/src/models/training_workout.model.dart';
import 'package:app_hit_a_wod/src/models/user.model.dart';
import 'package:app_hit_a_wod/src/models/user_assistence.model.dart';
import 'package:app_hit_a_wod/src/models/user_subscription.model.dart';
import 'package:app_hit_a_wod/src/models/user_benchmark.model.dart';
import 'package:app_hit_a_wod/src/providers/snackbar.provider.dart';
import 'package:dio/dio.dart';
import 'package:intl/intl.dart';

class ApiProvider {

  String _token;

  static BaseOptions options = new BaseOptions(
    connectTimeout: 5000,
    receiveTimeout: 5000,
  );
  Dio http;
  SnackbarProvider _snack;

  static ApiProvider _instancia;
  factory ApiProvider(SnackbarProvider snack, { String url }) {
    if(_instancia==null) {
      _instancia = ApiProvider._internal(snack, url: url);
    }
    return _instancia;
  }
  ApiProvider._internal(SnackbarProvider this._snack, { String url }) {
    http = Dio(options)..interceptors.add(_tokenInterceptor())..interceptors.add(_snackbarInterceptor());
 
    if(url!=null) this.http.options.baseUrl = url;
    if(this.http.options.baseUrl==null) throw new Exception("Please provide a base url for the api provider");
  }

  /* ***************************************************
  ** ****************** INTERCEPTOR ********************
  ** ***************************************************/
  setToken(String token) {
    this._token = token;
  }

  bool _esUrlTractableSnackbar(Uri uri) {
    return uri.toString().contains("api") && !uri.toString().contains("auth");
  }

  bool _esUrlTractableToken(Uri uri) {
    return uri.toString().contains("api");
  }

  _tokenInterceptor() {
    return InterceptorsWrapper(
      onRequest: (RequestOptions options) async {
        if(this._esUrlTractableToken(options.uri)) {
          if(this._token!=null) {
            options.headers["Authorization"] = "Bearer ${this._token}";
          }
        }
        return options;
      }
    );
  }

  _snackbarInterceptor() {
    return InterceptorsWrapper(
      onResponse:(Response response) async {   
        //mostrem missatge snack
        /*if(this._esUrlTractableSnackbar(response.request.uri)) {
          switch(response.request.method) {
            case "POST":
              _snack.success("Se ha creado correctamente");
              break;
            case "PUT":
              _snack.success("Se ha actualizado correctamente");
              break;
            case "DELETE":
              _snack.success("Se ha borrado correctamente");
              break;
          }
        }*/

        return response; // continue
      },
      onError: (DioError e) async {
        if(this._esUrlTractableSnackbar(e.request.uri)) {
          //mostrem missatge snack
          if(e.type == DioErrorType.CONNECT_TIMEOUT) {
            _snack.error("No se ha podido conectar al servidor");
          }
          else if(e.response!=null) {
            if(e.response.data is String) {
              switch(e.request.method) {
                case "GET":
                  _snack.error("Error obteniendo datos: ${e.response.data}");
                  break;
                case "POST":
                  _snack.error("Error creando: ${e.response.data}");
                  break;
                case "PUT":
                  _snack.error("Error actualizando: ${e.response.data}");
                  break;
                case "DELETE":
                  _snack.error("Error borrando: ${e.response.data}");
                  break;
              }
            }
            else {
              switch(e.request.method) {
                case "GET":
                  _snack.error("Error obteniendo datos (" + e.response.statusCode.toString() + "): " + (e.response.data.containsKey("error") ? e.response.data["error"] : 'No especificado'));
                  break;
                case "POST":
                  _snack.error("Error creando (" + e.response.statusCode.toString() + "): " + (e.response.data.containsKey("error") ? e.response.data["error"] : 'No especificado'));
                  break;
                case "PUT":
                  _snack.error("Error actualizando (" + e.response.statusCode.toString() + "): " + (e.response.data.containsKey("error") ? e.response.data["error"] : 'No especificado'));
                  break;
                case "DELETE":
                  _snack.error("Error borrando (" + e.response.statusCode.toString() + "): " + (e.response.data.containsKey("error") ? e.response.data["error"] : 'No especificado'));
                  break;
              }
            }
          }
          else {
            _snack.error("Error");
          }
        }

        return  e;//continue
      }
    );
  }

  /* ***************************************************
  ** ****************** API METHODS ********************
  ** ***************************************************/
  
  /*
   * AUTHENTICATION
   */
  Future<LoginResponse> login(String email, String password, { String custom_user_id }) async {
    var resp = await http.post("/auth/login", data: {"email": email, "password": password}, queryParameters: custom_user_id==null ? null : { "custom_user_id": custom_user_id });
    return LoginResponse.fromJson(resp.data);
  }

  Future<LoginResponse> refreshToken() async {
    var resp = await http.post("/auth/refresh");
    return LoginResponse.fromJson(resp.data);
  }

  Future<dynamic> sendPasswordResetLink(String email) async {
    var resp = await http.post("/auth/sendPasswordResetLink", data: { "email": email });
    return true;
  }

  Future<dynamic> changePassword(String token, String password) async {
    var resp = await http.post("/auth/changePassword", data: { "token": token, "password": password });
    return true;
  }

  /*
   * COMPANIES
   */
  Future<ArrayResponse<Company>> getCompanies() async {
    var resp = await http.get("/companies");
    return ArrayResponse<Company>.fromJson(resp.data);
  }

  Future<Company> getCompanyById(int id) async {
    var resp = await http.get("/companies/$id");
    return Company.fromJson(resp.data);
  }

  Future<Company> postCompany(Company c) async {
    var resp = await http.post("/companies", data: c.toJson());
    return Company.fromJson(resp.data);
  }

  Future<StripeUser> getStripeUserFromCompany(int company_id, int user_id) async {
    Response resp = await http.get("/companies/$company_id/users/$user_id/stripe-user");
    if(resp.data==null || resp.data.isEmpty) return null;
    else return StripeUser.fromJson(resp.data);
  }

  Future<dynamic> createStripeIntentByStripeUser(int company_id, int user_id) async {
    var resp = await http.post("/companies/$company_id/users/$user_id/create-setup-intent", data: {});
    return resp.data;
  }

  /*
   * BOXES
   */
  Future<ArrayResponse<UserAssistence>> getBoxUserAssistance(int box_id, int user_id, { Map<String,dynamic> params = const {} }) async {
    var resp = await http.get("/boxes/$box_id/users/$user_id/assistance", queryParameters: params);
    var res = ArrayResponse<UserAssistence>.fromJson(resp.data);
    res.data = res.data.map((ua) => UserAssistence.fromJson(ua)).toList();
    return res;
  }
  
  Future<ArrayResponse<TrainingWorkout>> getBoxWorkouts(int box_id, String name, String category, String dir) async {
    var resp = await http.get("/boxes/$box_id/workouts", queryParameters: {"name": name, "category": category, "sort_dir": dir});
    var res = ArrayResponse<TrainingWorkout>.fromJson(resp.data);
    res.data = res.data.map((t) => TrainingWorkout.fromJson(t)).toList();
    return res;
  }

  Future<ArrayResponse<Pricing>> getBoxPricings(int box_id, { Map<String,dynamic> params = const {} }) async {
    var resp = await http.get("/boxes/$box_id/pricings", queryParameters: params);
    var res = ArrayResponse<Pricing>.fromJson(resp.data);
    res.data = res.data.map((p) => Pricing.fromJson(p)).toList();
    return res;
  }

  Future<ArrayResponse<BoxOrder>> getBoxUserOrders(int box_id, int user_id) async {
    var resp = await http.get("/boxes/$box_id/users/$user_id/box-orders");
    var res = ArrayResponse<BoxOrder>.fromJson(resp.data);
    res.data = res.data.map((bo) => BoxOrder.fromJson(bo)).toList();
    return res;
  }

  Future<New> getLastBoxNew(int box_id) async {
    var resp = await http.get("/boxes/$box_id/last-new");
    return New.fromJson(resp.data);
  }

  Future<ArrayResponse<New>> getBoxNews(int box_id) async {
    var resp = await http.get("/boxes/$box_id/news");
    var res = ArrayResponse<New>.fromJson(resp.data);
    res.data = res.data.map((n) => New.fromJson(n)).toList();
    return res;
  }

  Future<Map<String, dynamic>> getLastTrainingBenchmarkFromBox(int box_id, int user_id, { int days }) async {
    Response<Map<String, dynamic>> resp = await http.get("/boxes/$box_id/users/$user_id/last-session-benchmark", queryParameters: { "days": days });
    var res = resp.data;
    res["ts"] = res["ts"]==null ? null : TrainingSession.fromJson(res["ts"]);
    res["benchmark"] = res["benchmark"]==null ? null : UserBenchmark.fromJson(res["benchmark"]);
    return res;
  } 

  Future<TrainingSession> getNextTrainingSessionInBox(int box_id, int user_id, { int days }) async {
    var resp = await http.get("/boxes/$box_id/users/$user_id/last-session-benchmark", queryParameters: { "days": days });
    return TrainingSession.fromJson(resp.data);
  }

  Future<bool> getBoxAthleteShouldTrainMore(int box_id, int user_id) async {
    var resp = await http.get("/boxes/$box_id/users/$user_id/should-train-more");
    return resp.data["show"];
  }

  Future<List<User>> getBoxUserBirthToday(int box_id) async {
    var resp = await http.get("/boxes/$box_id/users/birthdays");
    List<User> res = [];
    resp.data.forEach((k, u) {
      res.add(User.fromJson(u));
    });
    return res;
  }

  /*
   * TRAINING SESSIONS
   */
  Future<ArrayResponse<TrainingSession>> getTrainingSessions(int box_id, DateTime date) async {
    var resp = await http.get("/boxes/$box_id/training-sessions");
    var res = ArrayResponse<TrainingSession>.fromJson(resp.data);
    res.data = res.data.map((t) => TrainingSession.fromJson(t)).toList();
    return res;
  }

  Future<BITrainingSession> joinSession(int user_id, int training_session_id) async {
    var resp = await http.post("/training-sessions/$training_session_id/users/$user_id");
    return BITrainingSession.fromJson(resp.data);
  }

  Future<BITrainingSession> leaveSession(int user_id, int training_session_id) async {
    var resp = await http.post("/training-sessions/$training_session_id/users/$user_id/leave");
    return BITrainingSession.fromJson(resp.data);
  }

  /*
   * USER
   */
  Future<User> updateUser(User user) async {
    var resp = await http.put("/users/${user.id}", data: user);
    return User.fromJson(resp.data);
  }

  Future<User> updateOneSignalPlayerId(int user_id, String playerId) async {
    var resp = await http.put("/users/${user_id}/one-signal-player-id", queryParameters: { "one_signal_player_id": playerId });
    return User.fromJson(resp.data);
  }

  Future<User> updateUserProfileImage(int id, File file) async {
    String fileName = file.path.split('/').last;
    FormData formData = FormData.fromMap({"photo": await MultipartFile.fromFile(file.path, filename:fileName) });
    var resp = await http.post("/users/$id/update-profile-image", data: formData);
    return User.fromJson(resp.data);
  }

  Future<User> updateUserPassword(int id, String password) async {
    var resp = await http.put("/users/$id", data: {"password": password});
    return User.fromJson(resp.data);
  }

  Future<ArrayResponse<UserSubscription>> getUserSubscriptions(int user_id) async {
    var resp = await http.get("/users/$user_id/user-subscriptions");
    var res = ArrayResponse<UserSubscription>.fromJson(resp.data);
    res.data = res.data.map((us) => UserSubscription.fromJson(us)).toList();
    return res;
  }

  Future<User> updateUserFavoriteWorkouts(int id, int id_workout) async {
    var resp = await http.put("/users/$id/favorite-workout/$id_workout");
    return User.fromJson(resp.data);
  }

  Future<ArrayResponse<UserBenchmark>> userBenchmarksWorkout(int id, int id_workout) async {
    var resp = await http.get("/users/$id/benchmarks/$id_workout");
    var res = ArrayResponse<UserBenchmark>.fromJson(resp.data);
    res.data = res.data.map((t) => UserBenchmark.fromJson(t)).toList();
    return res;
  }

  /*
   * USER SUBSCRIPTIONS
   */
  Future<dynamic> renewUserSubscription(int id, Map<String, dynamic> params) async {
    var resp = await http.put("/user-subscriptions/$id/renew", data: {}, queryParameters: params);
    return resp.data;
  }

  Future<dynamic> payUserSubscription(int id, Map<String, dynamic> params) async {
    var resp = await http.put("/user-subscriptions/$id/pay", data: {}, queryParameters: params);
    return resp.data;
  }

  Future<dynamic> cancelUserSubscription(int id) async {
    var resp = await http.delete("/user-subscriptions/$id/cancel");
    return resp.data;
  }

  Future<dynamic> getUserSubscriptionStatus(int id) async {
    var resp = await http.get("/user-subscriptions/$id/status");
    return resp.data;
  }

  /*
   * BUSINESS INTELLIGENCE
   */
  Future<ArrayResponse<BITrainingSession>> getTrainingSessionsBI( int box_id, String data) async {
    var resp = await http.get("/business-intelligence/training-sessions", queryParameters: {"box_id": box_id,"data": data});
    var res = ArrayResponse<BITrainingSession>.fromJson(resp.data);
    res.data = res.data.map((t) => BITrainingSession.fromJson(t)).toList();
    return res;
  }

  Future<ArrayResponse<BITrainingSession>> getTrainingSessionsUserBI(int user_id, int box_id, DateTime start_date, DateTime end_date, String dir) async {
    final queryParameters = {
      "box_id": box_id,
      "sort_dir": dir,
      "sort_col": "date_start"
    };

    if(start_date!=null) queryParameters["data_ini"] = DateFormat('yyyy-MM-dd').format(start_date);
    if(end_date!=null) queryParameters["data_fi"] = DateFormat('yyyy-MM-dd').format(end_date);

    
    var resp = await http.get("/business-intelligence/training-sessions/user/$user_id", queryParameters: queryParameters);
    var res = ArrayResponse<BITrainingSession>.fromJson(resp.data);
    res.data = res.data.map((t) => BITrainingSession.fromJson(t)).toList();
    return res;
  }

  Future<ArrayResponse<BIExercise>> getUserBenchmarksExercisesBI(int user_id, int box_id, DateTime start_date, DateTime end_date, String name, String dir) async {

    final queryParameters = {
      "box_id": box_id,
      "data_ini": DateFormat('yyyy-MM-dd').format(start_date),
      "data_fi": DateFormat('yyyy-MM-dd').format(end_date),
      "name": name,
      "sort_dir": dir
    };

    var resp = await http.get("/business-intelligence/exercises/user/$user_id", queryParameters: queryParameters);
    var res = ArrayResponse<BIExercise>.fromJson(resp.data);
    res.data = res.data.map((t) => BIExercise.fromJson(t)).toList();
    return res;
  }

  Future<List<String>> getCategories() async {
    var resp = await http.get("/business-intelligence/categories");
    var res = ArrayResponse<String>.fromJson(resp.data);
    // res.data = json.decode(resp.data);
    return res.data.cast<String>().toList();
  }

  /*
   * BENCHMARKS
   */
  Future<UserBenchmark> postUserBenchmark(UserBenchmark ub) async {
    var resp = await http.post("/user-benchmarks", data: ub);
    return UserBenchmark.fromJson(resp.data);
  }

  Future<Map<String,dynamic>> getMobileAppHome(int box_id, int user_id, int user_subscription_id) async {
    Response<Map<String,dynamic>> resp = await http.get("/business-intelligence/mobile-app-home", queryParameters: { "box_id": box_id, "user_id": user_id, "user_subscription_id": user_subscription_id });
    Map<String,dynamic> res = resp.data;
    res["status"] = res["status"] as int;
    res["last_new"] = res["last_new"]==null ? null : New.fromJson(res["last_new"]);
    res["last_session_benchmark"]["ts"] = res["last_session_benchmark"]["ts"]==null ? null : BITrainingSession.fromJson(res["last_session_benchmark"]["ts"]);
    res["next_training_session"] = res["next_training_session"]==null ? null : BITrainingSession.fromJson(res["next_training_session"]);
    res["should_train_more"] = res["should_train_more"] as bool;
    List<User> birthdays = [];
    if(res["birthdays"].length > 0) {
      res["birthdays"].forEach((k, u) {
        birthdays.add(User.fromJson(u));
      });
    }
    res["birthdays"] = birthdays;
    
    return res;
  }

  Future<ExerciseBenchmark> getExerciseBenchmarks(int exercise_id, int user_id) async {
    var resp = await http.get("/exercises/$exercise_id/user/$user_id");
    return ExerciseBenchmark.fromJson(resp.data);
  }

  Future<String> getUrlYoutube(String url) async {
    var resp = await http.get('http://youlink.epizy.com/?url='+url);
    var r = resp.data?.map((e) =>
            e == null ? null : Map<String, dynamic>.from(e as Map<String, dynamic>))
        ?.toList();
    return resp.data.url;
  }

  /*
   * TRAINING WORKOUTS
   */
  Future<ArrayResponse<TrainingWorkout>> getAvailableWorkouts(int user_id, String name, String dir, List<String> equipment, List<String> sections, int difficulty, int seconds, String category, List<String> exercise_types, int favorites) async {
    final queryParameters = {
      "name": name,
      "sort_dir": dir,
      "favorites": favorites
    };

    if(equipment!=null && equipment.length>0) queryParameters["equipment"] = equipment.join('/');
    if(sections!=null && sections.length>0) queryParameters["section_types"] = sections.join('/');
    if(exercise_types!=null && exercise_types.length>0) queryParameters["exercise_types"] = exercise_types.join('/');
    if(difficulty!=null) queryParameters["difficulty"] = difficulty.toString();
    if(seconds!=null) queryParameters["seconds"] = seconds.toString();
    if(category!=null) queryParameters["category"] = category;

    var resp = await http.get("/workouts/user/$user_id", queryParameters: queryParameters);
    var res = ArrayResponse<TrainingWorkout>.fromJson(resp.data);
    res.data = res.data.map((t) => TrainingWorkout.fromJson(t)).toList();
    return res;
  }

  /*
   * TRAINING WORKOUTS
   */
  Future<List<String>> getSectionTypes() async {
    var resp = await http.get("workouts/section/types");
    var res = ArrayResponse<String>.fromJson(resp.data);
    // res.data = json.decode(resp.data);
    return res.data.cast<String>().toList();
  }

  /*
   * EXERCISES
   */
  Future<ArrayResponse<Exercise>> getAllExercises(int user_id, int box_id, DateTime start_date, DateTime end_date, String name, String dir, List<String> equipment, int difficulty, List<String> modality, List<String> exercise_types) async {

    final queryParameters = {
      "box_id": box_id,
      "data_ini": DateFormat('yyyy-MM-dd').format(start_date),
      "data_fi": DateFormat('yyyy-MM-dd').format(end_date),
      "name": name,
      "sort_dir": dir
    };
    if(equipment!=null && equipment.length>0) queryParameters["equipment"] = equipment.join('/');
    if(difficulty!=null) queryParameters["difficulty"] = difficulty;
    if(exercise_types!=null && exercise_types.length>0) queryParameters["exercise_types"] = exercise_types.join('/');
    if(modality!=null && modality.length>0) queryParameters["modality"] = modality.join('/');


    var resp = await http.get("/exercises", queryParameters: queryParameters);
    var res = ArrayResponse<Exercise>.fromJson(resp.data);
    res.data = res.data.map((t) => Exercise.fromJson(t)).toList();
    return res;
  }

  /*
   * Legal
   */
  Future<String> getPolitica() async {
    var resp = await http.get("legal/politica-de-privacidad");
    var res = Map<String, dynamic>.from(resp.data);
    return res.values.toList()[0];
  }

  Future<String> getTerminos() async {
    var resp = await http.get("legal/terminos-y-condiciones-de-uso");
    var res = Map<String, dynamic>.from(resp.data);
    return res.values.toList()[0];
  }

  Future<String> getAviso() async {
    var resp = await http.get("legal/aviso-legal/completo");
    var res = Map<String, dynamic>.from(resp.data);
    return res.values.toList()[0];
  }

  Future<String> getAvisoAtleta(String company_id) async {
    final queryParameters = {
      "company_id": company_id
    };
    var resp = await http.get("legal/aviso-legal/completo-atleta", queryParameters: queryParameters);
    var res = Map<String, dynamic>.from(resp.data);
    return res.values.toList()[0];
  }

}