import 'dart:collection';
import 'dart:convert';
import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/model/exercise_plan.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
import 'package:aitrainer_app/model/exercise_plan_template.dart';
import 'package:aitrainer_app/model/exercise_tree.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/model_change.dart';
import 'package:aitrainer_app/model/product.dart' as wt_product;
import 'package:aitrainer_app/model/product_test.dart';
import 'package:aitrainer_app/model/property.dart';
import 'package:aitrainer_app/model/purchase.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/customer_repository.dart';
import 'package:aitrainer_app/service/firebase_api.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/service/package_service.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/env.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:flurry/flurry.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:package_info/package_info.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:intl/intl.dart';

import 'customer_exercise_device.dart';
import 'exercise_device.dart';

enum SharePrefsChange {
  login,
  registration,
  logout,
}
/*
  Auth flow of the app
  1. During the login screen the authentication will be executed
    - if not successful: message: Network error, try again later
    - if successful
      - get the stored shared preferences and customer id
        - if customer_id not present -> registration page
        - if present, check if the expiration_date > 10 days -> login page
        - else get the API customer by the stored customer_id
      - After registration / login store the preferences:
        - AuthToken
        - customer_id
        - last_store_date
        - is_registered
        - is_logged_in
 */

class Cache with Logging {
  static final Cache _singleton = Cache._internal();

  // Keys to store and fetch data from SharedPreferences
  static final String authTokenKey = 'auth_token';
  static final String customerIdKey = 'customer_id';
  static final String firebaseUidKey = 'firebase_uid';
  static final String lastStoreDateKey = 'last_date';
  static final String isRegisteredKey = 'is_registered';
  static final String isLoggedInKey = 'is_logged_in';
  static final String langKey = 'lang';
  static final String serverKey = 'live';
  static final String hardwareKey = 'hardware';
  static final String loginTypeKey = 'login_type';
  static final String timerDisplayKey = 'timer_display';
  static final String activeExercisePlanKey = 'active_exercise_plan';
  static final String activeExercisePlanDateKey = 'active_exercise_plan_date';
  static final String activeExercisePlanDetailsKey = 'active_exercise_details_plan';

  static String baseUrl = 'http://aitrainer.info:8888/api/';
  static final String mediaUrl = 'https://aitrainer.info:4343/media/';
  static final String username = 'bosi';
  static final String password = 'andio2009';

  String authToken = "";
  AccessToken accessTokenFacebook;
  Customer userLoggedIn;
  String firebaseUid;
  LoginType loginType;
  PackageInfo packageInfo;

  bool hasPurchased = false;

  bool firstLoad = true;

  List<ExerciseType> _exerciseTypes;
  List<ExerciseTree> _exerciseTree;

  List<Exercise> _exercises;
  ExercisePlan _myExercisePlan;
  List<Property> _properties;
  List<wt_product.Product> _products;
  List<Purchase> _purchases = List();
  List<ProductTest> _productTests;
  List<ExercisePlanTemplate> _exercisePlanTemplates = List();

  ExercisePlan activeExercisePlan;
  List<ExercisePlanDetail> activeExercisePlanDetails;

  List<ExerciseDevice> _devices;
  List<CustomerExerciseDevice> _customerDevices;

  LinkedHashMap<int, ExercisePlanDetail> _myExercisesPlanDetails = LinkedHashMap<int, ExercisePlanDetail>();

  LinkedHashMap _tree = LinkedHashMap<String, WorkoutMenuTree>();
  double _percentExercises = -1;

  Customer _trainee;
  List<Exercise> _exercisesTrainee;
  ExercisePlan _traineeExercisePlan;
  List<ExercisePlanDetail> _traineeExercisesPlanDetail;

  LinkedHashMap<String, int> _badges = LinkedHashMap();

  List deviceLanguages;
  String startPage = "home";
  String testEnvironment;
  bool liveServer = true;
  bool hasHardware = false;

  factory Cache() {
    return _singleton;
  }

  Cache._internal() {
    String testEnv = EnvironmentConfig.test_env;
    this.testEnvironment = testEnv;
    if (testEnv == "1") {
      baseUrl = 'http://aitrainer.app:8899/api/';
      liveServer = false;
    }
  }

  void setTestBaseUrl() {
    baseUrl = 'http://aitrainer.app:8899/api/';
  }

  String getAuthToken() {
    return this.authToken;
  }

  Future<void> saveActiveExercisePlan(ExercisePlan exercisePlan, List<ExercisePlanDetail> exercisePlanDetails) async {
    this.activeExercisePlan = exercisePlan;
    this.activeExercisePlanDetails = exercisePlanDetails;
    String exercisePlanJson = JsonEncoder().convert(exercisePlan.toJson());
    String detailsJson = jsonEncode(exercisePlanDetails.map((i) => i.toJsonWithExerciseList()).toList()).toString();

    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences;
    sharedPreferences = await prefs;

    final DateTime now = DateTime.now();
    sharedPreferences.setString(Cache.activeExercisePlanKey, exercisePlanJson);
    sharedPreferences.setString(Cache.activeExercisePlanDetailsKey, detailsJson);
    String savingDay = DateFormat("yyyy-MM-dd HH:mm:ss").format(now);
    sharedPreferences.setString(Cache.activeExercisePlanDateKey, savingDay);
  }

  Future<void> deleteActiveExercisePlan() async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences;
    sharedPreferences = await prefs;

    sharedPreferences.remove(Cache.activeExercisePlanDateKey);
    this.activeExercisePlan = null;
    this.activeExercisePlanDetails = null;
  }

  Future<void> getActiveExercisePlan() async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences;
    sharedPreferences = await prefs;

    final savedPlanDateString = sharedPreferences.getString(Cache.activeExercisePlanDateKey);
    if (savedPlanDateString == null) {
      return;
    }

    DateFormat format = DateFormat("yyyy-MM-dd HH:mm:ss");
    DateTime savedPlanDate;
    try {
      savedPlanDate = format.parse(savedPlanDateString);
    } on Exception catch (e) {
      return;
    }

    final DateTime now = DateTime.now();
    final DateTime added = savedPlanDate.add(Duration(days: 1));
    if (added.isBefore(now)) {
      return;
    }

    String exercisePlanJson = sharedPreferences.getString(Cache.activeExercisePlanKey);
    if (exercisePlanJson != null) {
      final Map<String, dynamic> map = JsonDecoder().convert(exercisePlanJson);
      this.activeExercisePlan = ExercisePlan.fromJson(map);
    }

    String detailsJson = sharedPreferences.getString(Cache.activeExercisePlanDetailsKey);
    if (detailsJson != null) {
      print("Details $detailsJson");
      Iterable json = jsonDecode(detailsJson);
      this.activeExercisePlanDetails = json.map((details) => ExercisePlanDetail.fromJsonWithExerciseList(details)).toList();
    }
  }

  Future<void> setServer(bool live) async {
    if (this.testEnvironment == "1") {
      liveServer = false;
      live = false;
    }
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences;
    sharedPreferences = await prefs;
    liveServer = live;
    sharedPreferences.setBool(Cache.serverKey, live);
  }

  void getHardware(SharedPreferences prefs) {
    final bool hasHardware = prefs.getBool(Cache.hardwareKey);
    this.hasHardware = hasHardware;
    if (hasHardware == null) {
      this.hasHardware = false;
    }
  }

  Future<bool> selectedHardwareBefore() async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences = await prefs;

    final bool selectedHardware = sharedPreferences.getBool(Cache.hardwareKey);
    return selectedHardware == null;
  }

  Future<void> setHardware(bool hasHardware) async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences;
    sharedPreferences = await prefs;
    sharedPreferences.setBool(Cache.hardwareKey, hasHardware);
    this.hasHardware = hasHardware;
  }

  void setServerAddress(SharedPreferences prefs) {
    if (this.testEnvironment == "1") {
      baseUrl = 'http://aitrainer.app:8899/api/';
      return;
    }
    final bool live = prefs.getBool(Cache.serverKey);
    if (live == null) {
      return;
    }
    liveServer = live;
    if (live) {
      baseUrl = 'http://aitrainer.info:8888/api/';
    } else {
      baseUrl = 'http://aitrainer.app:8899/api/';
    }
  }

  Future<void> setLoginTypeFromPrefs() async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences = await prefs;
    final String loginType = sharedPreferences.getString(Cache.loginTypeKey);
    LoginType type = LoginType.email;
    if (loginType == LoginType.apple.toString()) {
      type = LoginType.apple;
    } else if (loginType == LoginType.google.toString()) {
      type = LoginType.google;
    } else if (loginType == LoginType.fb.toString()) {
      type = LoginType.fb;
    } else if (loginType == LoginType.email.toString()) {
      type = LoginType.email;
    }
    //print("LoginType: " + loginType == null ? "NULL" : loginType);
    Cache().setLoginType(type);
  }

  static String getToken(SharedPreferences prefs) {
    return prefs.getString(authTokenKey);
  }

  static String getBaseUrl() {
    return baseUrl;
  }

  static String getMediaUrl() {
    return mediaUrl;
  }

  afterRegistration(Customer customer) async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();

    userLoggedIn = customer;
    final String uid = Cache().firebaseUid;
    SharedPreferences sharedPreferences = await prefs;
    sharedPreferences.setString(Cache.loginTypeKey, Cache().getLoginType().toString());
    await setPreferences(prefs, SharePrefsChange.registration, customer.customerId, uid);
  }

  afterLogin(Customer customer) async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();

    userLoggedIn = customer;
    SharedPreferences sharedPreferences = await prefs;
    sharedPreferences.setString(Cache.loginTypeKey, Cache().getLoginType().toString());
    await setPreferences(prefs, SharePrefsChange.login, customer.customerId, Cache().firebaseUid);
  }

  afterFirebaseLogin() async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences = await prefs;
    sharedPreferences.setString(Cache.loginTypeKey, Cache().getLoginType().toString());
    await setPreferences(prefs, SharePrefsChange.login, userLoggedIn.customerId, Cache().firebaseUid);
  }

  afterFacebookLogin() async {
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    SharedPreferences sharedPreferences = await prefs;
    sharedPreferences.setString(Cache.loginTypeKey, Cache().getLoginType().toString());
    await setPreferences(prefs, SharePrefsChange.login, userLoggedIn.customerId, Cache().firebaseUid);
  }

  logout() async {
    if (this.accessTokenFacebook != null) {
      await FirebaseApi().logOutFacebook();
    }
    userLoggedIn = null;
    firebaseUid = null;
    authToken = "";
    _trainee = null;
    _percentExercises = -1;
    _exercisesTrainee = null;
    _traineeExercisePlan = null;
    _exercises = List();
    _myExercisesPlanDetails = LinkedHashMap();
    log("Trainees is null? " + (_trainee == null).toString());
    Future<SharedPreferences> prefs = SharedPreferences.getInstance();
    await setPreferences(prefs, SharePrefsChange.logout, 0, "");
  }

  Future<void> setPreferences(Future<SharedPreferences> prefs, SharePrefsChange type, int customerId, String firebaseUid) async {
    SharedPreferences sharedPreferences;
    sharedPreferences = await prefs;

    DateTime now = DateTime.now();
    sharedPreferences.setString(Cache.lastStoreDateKey, now.toString());
    if (type == SharePrefsChange.registration) {
      sharedPreferences.setInt(Cache.customerIdKey, customerId);
      sharedPreferences.setBool(Cache.isRegisteredKey, true);
      sharedPreferences.setBool(Cache.isLoggedInKey, true);
      sharedPreferences.setString(Cache.firebaseUidKey, firebaseUid);
      await initCustomer(customerId);
    } else if (type == SharePrefsChange.login) {
      sharedPreferences.setInt(Cache.customerIdKey, customerId);
      sharedPreferences.setString(Cache.firebaseUidKey, firebaseUid);
      sharedPreferences.setBool(Cache.isLoggedInKey, true);
      await initCustomer(customerId);
    } else if (type == SharePrefsChange.logout) {
      sharedPreferences.setBool(Cache.isLoggedInKey, false);
      sharedPreferences.setInt(Cache.customerIdKey, 0);
      sharedPreferences.setString(Cache.firebaseUidKey, null);
      sharedPreferences.setString(authTokenKey, "");
    }
    await initBadges();
  }

  void setExerciseTypes(List<ExerciseType> exerciseTypes) {
    this._exerciseTypes = exerciseTypes;
  }

  void setExerciseTree(List<ExerciseTree> exerciseTree) {
    this._exerciseTree = exerciseTree;
  }

  void setExercises(List<Exercise> exercises) => this._exercises = exercises;

  void setExercisesTrainee(List<Exercise> exercises) {
    this._exercisesTrainee = exercises;
  }

  void setWorkoutMenuTree(LinkedHashMap<String, WorkoutMenuTree> tree) {
    this._tree = tree;
  }

  List<ExerciseType> getExerciseTypes() => this._exerciseTypes;

  ExerciseType getExerciseTypeById(int exerciseTypeId) {
    ExerciseType exerciseType;
    this._exerciseTypes.forEach((element) {
      if (element.exerciseTypeId == exerciseTypeId) {
        exerciseType = element;
      }
    });
    return exerciseType;
  }

  List<ExerciseTree> getExerciseTree() => this._exerciseTree;

  List<Exercise> getExercises() => this._exercises;

  List<Exercise> getExercisesTrainee() => this._exercisesTrainee;

  LinkedHashMap<String, WorkoutMenuTree> getWorkoutMenuTree() => this._tree;

  void setPercentExercises(double percent) => this._percentExercises = percent;

  double getPercentExercises() => this._percentExercises;

  void addExercise(Exercise exercise) => _exercises.add(exercise);

  void addExerciseTrainee(Exercise exercise) => _exercisesTrainee.add(exercise);

  Customer getTrainee() => this._trainee;

  void setTrainee(Customer trainee) => _trainee = trainee;

  void setTraineeExercisePlan(ExercisePlan exercisePlan) => this._traineeExercisePlan = exercisePlan;

  ExercisePlan getTraineesExercisePlan() => this._traineeExercisePlan;

  void setMyExercisePlan(ExercisePlan exercisePlan) => _myExercisePlan = exercisePlan;

  ExercisePlan getMyExercisePlan() => _myExercisePlan;

  void setMyExercisePlanDetails(LinkedHashMap<int, ExercisePlanDetail> listExercisePlanDetail) =>
      _myExercisesPlanDetails = listExercisePlanDetail;

  void addToMyExercisePlanDetails(ExercisePlanDetail detail) => _myExercisesPlanDetails[detail.exerciseTypeId] = detail;

  LinkedHashMap<int, ExercisePlanDetail> getMyExercisePlanDetails() => _myExercisesPlanDetails;

  void resetMyExercisePlanDetails() => _myExercisesPlanDetails = LinkedHashMap<int, ExercisePlanDetail>();

  void updateMyExercisePlanDetail(ExercisePlanDetail detail) {
    this.addToMyExercisePlanDetails(detail);
  }

  void deleteMyExercisePlanDetail(ExercisePlanDetail detail) => this.deleteMyExercisePlanDetailByExerciseTypeId(detail.exerciseTypeId);

  void deletedMyExercisePlanDetail(ExercisePlanDetail detail) =>
      this._myExercisesPlanDetails[detail.exerciseTypeId].change = ModelChange.deleted;

  void deleteMyExercisePlanDetailByExerciseTypeId(int exerciseTypeId) {
    this._myExercisesPlanDetails[exerciseTypeId].change = ModelChange.delete;
  }

  void setProperties(List<Property> properties) => this._properties = properties;

  List<Property> getProperties() => _properties;

  void setDevices(List<ExerciseDevice> devices) => this._devices = devices;

  List<ExerciseDevice> getDevices() => this._devices;

  void setCustomerDevices(List<CustomerExerciseDevice> devices) => this._customerDevices = devices;

  List<CustomerExerciseDevice> getCustomerDevices() => this._customerDevices;

  LinkedHashMap getBadges() => _badges;

  void setBadge(String key, bool inc) {
    if (inc) {
      if (_badges[key] != null) {
        _badges[key]++;
      } else {
        _badges[key] = 1;
      }
    } else {
      if (_badges[key] != null) {
        if (_badges[key] == 1) {
          _badges.remove(key);
        } else {
          _badges[key]--;
        }
      }
    }
  }

  void setBadgeNr(String key, int counter) {
    if (_badges[key] != null) {
      _badges[key] += counter;
    } else {
      _badges[key] = counter;
    }
  }

  Future<void> initBadges() async {
    CustomerRepository customerRepository = CustomerRepository();
    _badges = LinkedHashMap();
    customerRepository.setCustomer(userLoggedIn);
    int _ecto = customerRepository.getCustomerPropertyValue(PropertyEnum.Ectomorph.toStr()).toInt();
    int _mezo = customerRepository.getCustomerPropertyValue(PropertyEnum.Mesomorph.toStr()).toInt();
    int _endo = customerRepository.getCustomerPropertyValue(PropertyEnum.Endomorph.toStr()).toInt();
    //print("endo " + _endo.toString() + " mezo " + _mezo.toString());
    if (this.userLoggedIn != null) {
      if (this.userLoggedIn.birthYear == null || this.userLoggedIn.birthYear == 0) {
        setBadge("personalData", true);
        setBadge("account", true);
      }
      if (this._customerDevices == null || this._customerDevices.isEmpty) {
        setBadge("customerDevice", true);
        setBadge("account", true);
      }
      if (userLoggedIn.properties == null || userLoggedIn.properties.isEmpty) {
        setBadge("personalData", true);
        setBadge("bodyType", true);
        setBadge("Sizes", true);
        setBadge("BMI", true);
        setBadge("BMR", true);
        setBadgeNr("My Body", 3);
        setBadgeNr("home", 3);
      } else if (customerRepository.getWeight() == 0) {
        setBadge("BMI", true);
        setBadge("BMR", true);
        setBadge("My Body", true);
        setBadgeNr("home", 1);
      }
      if (_ecto == 0 && _mezo == 0 && _endo == 0) {
        setBadge("account", true);
        setBadge("bodyType", true);
      }
      if (this._exercises == null || this._exercises.length == 0) {
        setBadge("home", true);
        setBadge("Strength", true);
        setBadge("Cardio", true);
      }
      if (customerRepository.getHeight() == 0) {
        setBadge("BMI", true);
        setBadge("BMR", true);
        setBadge("My Body", true);
        setBadgeNr("home", 1);
      }
      if (userLoggedIn.goal == null) {
        setBadge("Goal", true);
        setBadge("account", true);
      }
      if (userLoggedIn.fitnessLevel == null) {
        setBadge("FitnessLevel", true);
        setBadge("account", true);
      }
    }
    log("Badges: " + _badges.toString());
  }

  List get products => _products;
  void setProducts(List value) => _products = value;

  List get purchases => _purchases;
  setPurchases(List value) => _purchases = value;

  List get productTests => _productTests;
  set productTests(List value) => _productTests = value;

  Future<void> initCustomer(int customerId) async {
    log(" *** initCustomer");
    await PackageApi().getCustomerPackage(customerId);
    Flurry.setUserId(customerId.toString());

    await setLoginTypeFromPrefs();
    await getActiveExercisePlan();
    Cache().startPage = "home";
    Track().track(TrackingEvent.enter);
  }

  AccessToken get getAccessTokenFacebook => accessTokenFacebook;
  set setAccessTokenFacebook(AccessToken accessTokenFacebook) => this.accessTokenFacebook = accessTokenFacebook;

  LoginType getLoginType() => loginType;
  void setLoginType(LoginType type) => this.loginType = type;

  List get exercisePlanTemplates => this._exercisePlanTemplates;
  setExercisePlanTemplates(value) => this._exercisePlanTemplates = value;
}