import 'dart:collection';

import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/service/exercise_service.dart';
import 'package:intl/intl.dart';

class ExerciseRepository {
  Exercise? exercise;
  Customer? customer;
  ExerciseType? exerciseType;
  List<Exercise>? exerciseList;
  List<Exercise>? exerciseLogList = [];
  List<Exercise>? actualExerciseList = [];

  double rmWendler = 0;
  double rmMcglothlin = 0;
  double rmLombardi = 0;
  double rmMayhew = 0;
  double rmOconner = 0;
  double rmWathen = 0;

  DateTime? start;
  DateTime? end;

  ExerciseRepository() {
    this.createNew();
  }

  createNew() {
    this.exercise = Exercise();
    exercise!.dateAdd = DateTime.now();
  }

  setQuantity(double quantity) {
    if (this.exercise == null) {
      this.createNew();
    }
    this.exercise!.quantity = quantity;
  }

  setUnitQuantity(double unitQuantity) {
    if (this.exercise == null) {
      this.createNew();
    }

    this.exercise!.unitQuantity = unitQuantity;
  }

  setUnit(String unit) {
    if (this.exercise == null) {
      this.createNew();
    }

    this.exercise!.unit = unit;
  }

  setDatetimeExercise(DateTime datetimeExercise) {
    if (this.exercise == null) {
      this.createNew();
    }

    this.exercise!.dateAdd = datetimeExercise;
  }

  double? get unitQuantity => this.exercise!.unitQuantity;

  double? get quantity => this.exercise!.quantity;

  Exercise? getExercise() => this.exercise;

  Future<Exercise> addExercise() async {
    if (this.customer == null) {
      throw Exception("Please log in");
    }
    final Exercise modelExercise = this.exercise!;
    modelExercise.customerId = this.customer!.customerId;
    modelExercise.exerciseTypeId = this.exerciseType!.exerciseTypeId;
    if (exerciseType!.unitQuantity != "1") {
      modelExercise.unitQuantity = null;
    }

    Exercise copy = modelExercise.copy();
    this.actualExerciseList!.add(copy);
    //final int index = this.actualExerciseList.length - 1;
    //print("$index. actual exercise " + this.actualExerciseList[index].toJson().toString());
    Exercise savedExercise = await ExerciseApi().addExercise(modelExercise);

    //this.actualExerciseList[index].exerciseId = savedExercise.exerciseId;
    if (customer!.customerId == Cache().userLoggedIn!.customerId) {
      Cache().addExercise(savedExercise);
    } else if (Cache().getTrainee() != null && customer!.customerId == Cache().getTrainee()!.customerId) {
      Cache().addExerciseTrainee(savedExercise);
    }

    return savedExercise;
  }

  void initExercise() {
    this.createNew();
    this.exerciseType = exerciseType;
    this.setUnit(exerciseType!.unit);
    exercise!.exerciseTypeId = this.exerciseType!.exerciseTypeId;
    this.setQuantity(12);
    this.setUnitQuantity(30);
    this.exercise!.exercisePlanDetailId = 0;
    exercise!.exerciseId = 0;
    this.start = DateTime.now();
  }

  Future<void> deleteExercise(Exercise exercise) async {
    await ExerciseApi().deleteExercise(exercise);
  }

  setCustomer(Customer customer) => this.customer = customer;

  setExerciseType(ExerciseType exerciseType) => this.exerciseType = exerciseType;

  Future<List<Exercise>> getExercisesByCustomer(int customerId) async {
    final results = await ExerciseApi().getExercisesByCustomer(customerId);
    this.exerciseList = results;
    if (Cache().userLoggedIn != null) {
      if (customerId == Cache().userLoggedIn!.customerId) {
        Cache().setExercises(exerciseList!);
      } else if (Cache().getTrainee() != null && customerId == Cache().getTrainee()!.customerId) {
        Cache().setExercisesTrainee(exerciseList!);
      }
    }
    return this.exerciseList!;
  }

  List<Exercise>? getExerciseList() {
    this.exerciseList = Cache().getExercises();
    return this.exerciseList;
  }

  List<Exercise>? getExerciseListTrainee() {
    this.exerciseList = Cache().getExercisesTrainee();
    return this.exerciseList;
  }

  String? nextMissingBaseExercise(SplayTreeMap sortedTree) {
    if (exerciseList == null) {
      exerciseList = Cache().getExercises();
    }

    if (exerciseList == null) {
      return null;
    }
    String? missingTreeName;
    String? foundTreeName;
    bool isBreak = false;

    sortedTree.forEach((key, list) {
      List<WorkoutMenuTree> listByMuscle = list as List<WorkoutMenuTree>;
      String treeName = key as String;
      treeName = treeName.substring(3);
      foundTreeName = null;
      listByMuscle.forEach((exercise) {
        if (missingTreeName == null) {
          missingTreeName = treeName;
        }
        if (exercise.base) {
          if (exerciseList != null) {
            exerciseList!.forEach((element) {
              if (element.exerciseTypeId == exercise.exerciseTypeId) {
                foundTreeName = treeName;
                //print("Found " + foundTreeName + " Missing actual: " + missingTreeName);
                isBreak = true;
              }
            });
          }
        }
      });
      if (foundTreeName == null && !isBreak) {
        missingTreeName = treeName;
        isBreak = true;
      }
    });

    return missingTreeName;
  }

  void getBaseExerciseFinishedPercent() {
    List<int> checkedExerciseTypeId = [];
    List<int> baseTreeItem = [];
    List<int> checkedBaseTreeItem = [];
    int count1RMExercises = 0;
    LinkedHashMap<String, WorkoutMenuTree> tree = Cache().getWorkoutMenuTree();

    if (tree.isEmpty) {
      return;
    }

    tree.forEach((key, value) {
      WorkoutMenuTree treeItem = value;
      if (treeItem.exerciseType != null && treeItem.exerciseType!.base == true && !baseTreeItem.contains(treeItem.parent)) {
        baseTreeItem.add(treeItem.parent);
      }
    });

    if (exerciseList == null) {
      exerciseList = Cache().getExercises();
    }

    if (exerciseList == null) {
      return;
    }

    exerciseList!.forEach((element) {
      Exercise exercise = element;
      if (!checkedExerciseTypeId.contains(exercise.exerciseTypeId)) {
        checkedExerciseTypeId.add(exercise.exerciseTypeId!);
        tree.forEach((key, value) {
          WorkoutMenuTree treeItem = value;
          if (treeItem.exerciseType != null &&
              treeItem.exerciseType!.base == true &&
              exercise.exerciseTypeId == treeItem.exerciseType!.exerciseTypeId &&
              !checkedBaseTreeItem.contains(treeItem.parent)) {
            //print ("id: " + exercise.exerciseTypeId.toString());
            checkedBaseTreeItem.add(treeItem.parent);
            count1RMExercises++;
          }
        });
      }
    });

    //print ("checkedExerciseTypeid: " + checkedExerciseTypeId.toString());
    //print ("baseTreeItem: " + baseTreeItem.toString());
    //print ("count1RMExercises: " + count1RMExercises.toString());
    final double percent = count1RMExercises / baseTreeItem.length;
    Cache().setPercentExercises(percent);
  }

  void getLastExercise() {
    List<Exercise>? exercises = this.getExerciseList();
    Exercise? lastExercise = exercises == null ? null : exercises[0];
    if (exercises != null) {
      exercises.forEach((element) {
        Exercise actualExercise = element;
        if (actualExercise.dateAdd!.compareTo(lastExercise!.dateAdd!) > 0) {
          lastExercise = actualExercise;
        }
      });
    }
    this.exercise = lastExercise;
    this.customer = Cache().userLoggedIn!;
    this.exerciseType = getExerciseTypeById(exercise!.exerciseTypeId!);
    return;
  }

  ExerciseType? getExerciseTypeById(int exerciseTypeId) {
    ExerciseType? actualExerciseType;
    List<ExerciseType>? exercises = Cache().getExerciseTypes();
    if (exercises != null) {
      exercises.forEach((element) {
        ExerciseType exerciseType = element;
        if (exerciseType.exerciseTypeId == exerciseTypeId) {
          actualExerciseType = exerciseType;
        }
      });
    }
    if (actualExerciseType == null) {
      throw Exception("Data error, no ExerciseType for exerciseTypeId $exerciseTypeId");
    }
    return actualExerciseType;
  }

  void getSameExercise(int exerciseTypeId, String day) {
    this.actualExerciseList = [];
    if (exerciseList != null) {
      int index = 0;
      for (int i = 0; i < this.exerciseList!.length; i++) {
        Exercise exercise = exerciseList![i];
        final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
        if (exerciseTypeId == exercise.exerciseTypeId && exerciseDate == day && index < 4) {
          this.actualExerciseList!.add(exercise);
          index++;
        }
      }
    }
  }

  double calculate1RM(Exercise exercise) {
    double weight = exercise.unitQuantity!;
    double repeat = exercise.quantity!;
    if (weight == 0 || repeat == 0) {
      return 0;
    }

    double rmWendler = weight * repeat * 0.0333 + weight;
    double rmOconner = weight * (1 + repeat / 40);
    double average = (rmWendler + rmOconner) / 2;

    return average;
  }

  double getBest1RMPercent(Exercise exercise) {
    double result = 0;
    if (this.exerciseList == null || this.exerciseList!.isEmpty) {
      this.exerciseList = Cache().getExercises();
    }

    final int exerciseTypeId = exercise.exerciseTypeId!;
    double toCompare = this.calculate1RM(exercise);

    final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(DateTime.now());
    List<Exercise> oldExercises = [];
    this.exerciseList!.forEach((exercise) {
      final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
      if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
        oldExercises.add(exercise);
      }
    });

    if (oldExercises.isNotEmpty) {
      oldExercises.sort((a, b) {
        double sumA = 0;
        double sumB = 0;
        if (a.unitQuantity != null && b.unitQuantity != null) {
          sumA = a.quantity! * a.unitQuantity!;
          sumB = b.quantity! * b.unitQuantity!;
        } else {
          sumA = a.quantity!;
          sumB = b.quantity!;
        }
        return sumA >= sumB ? 1 : -1;
      });

      double withCompare = this.calculate1RM(oldExercises.last);

      result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
    }
    return result;
  }

  double getLast1RMPercent(Exercise exercise) {
    double result = 0;
    if (this.exerciseList == null || this.exerciseList!.isEmpty) {
      this.exerciseList = Cache().getExercises();
    }

    final int exerciseTypeId = exercise.exerciseTypeId!;
    double toCompare = this.calculate1RM(exercise);

    final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
    List<Exercise> oldExercises = [];
    this.exerciseList!.forEach((exercise) {
      final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
      if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
        oldExercises.add(exercise);
      }
    });

    if (oldExercises.isNotEmpty) {
      double withCompare = this.calculate1RM(oldExercises.first);
      result = toCompare >= withCompare ? (toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
    }
    return result;
  }

  double getBestExercisePercent(Exercise exercise) {
    double result = 0;
    if (this.exerciseList == null || this.exerciseList!.isEmpty) {
      this.exerciseList = Cache().getExercises();
    }

    final int exerciseTypeId = exercise.exerciseTypeId!;
    double toCompare = exercise.unitQuantity != null ? exercise.quantity! * exercise.unitQuantity! : exercise.quantity!;

    final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(DateTime.now());
    List<Exercise> oldExercises = [];
    this.exerciseList!.forEach((exercise) {
      final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
      if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
        oldExercises.add(exercise);
      }
    });

    if (oldExercises.isNotEmpty) {
      oldExercises.sort((a, b) {
        double sumA = 0;
        double sumB = 0;
        if (a.unitQuantity != null && b.unitQuantity != null) {
          sumA = a.quantity! * a.unitQuantity!;
          sumB = b.quantity! * b.unitQuantity!;
        } else {
          sumA = a.quantity!;
          sumB = b.quantity!;
        }
        return sumA >= sumB ? 1 : -1;
      });

      double withCompare = oldExercises.last.unitQuantity != null
          ? oldExercises.last.quantity! * oldExercises.last.unitQuantity!
          : oldExercises.last.quantity!;

      result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
      print("Last Best: ${oldExercises.last} - result: $result");
    }
    return result;
  }

  double getLastExercisePercent(Exercise exercise) {
    double result = 0;
    if (this.exerciseList == null || this.exerciseList!.isEmpty) {
      this.exerciseList = Cache().getExercises();
    }

    final int exerciseTypeId = exercise.exerciseTypeId!;
    double toCompare = exercise.unitQuantity != null ? exercise.quantity! * exercise.unitQuantity! : exercise.quantity!;

    final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
    List<Exercise> oldExercises = [];
    this.exerciseList!.forEach((exercise) {
      final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
      if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
        oldExercises.add(exercise);
      }
    });

    if (oldExercises.isNotEmpty) {
      double withCompare = oldExercises.first.unitQuantity != null
          ? oldExercises.first.quantity! * oldExercises.first.unitQuantity!
          : oldExercises.first.quantity!;

      result = toCompare >= withCompare ? (toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
      print("Last Last: ${oldExercises.first} vs. $exercise -  - result: $result");
    }
    return result;
  }

  void sortByDate() {
    if (exerciseList == null || exerciseList!.isEmpty) {
      return;
    }

    exerciseList!.sort((a, b) {
      final String datePartA = DateFormat('yyyyMMdd', AppLanguage().appLocal.toString()).format(a.dateAdd!);
      String aId = datePartA + "_" + a.exerciseTypeId.toString();
      final String datePartB = DateFormat('yyyyMMdd', AppLanguage().appLocal.toString()).format(b.dateAdd!);
      String bId = datePartB + "_" + b.exerciseTypeId.toString();
      return bId.compareTo(aId);
    });

    this.exerciseLogList = [];
    String summary = "";

    String prevDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exerciseList![0].dateAdd!);
    int prevExerciseTypeId = exerciseList![0].exerciseTypeId!;
    Exercise prevExercise = exerciseList![0];
    int prevCount = 0;
    for (int i = 0; i < this.exerciseList!.length; i++) {
      Exercise exercise = exerciseList![i];
      int exerciseTypeId = exercise.exerciseTypeId!;
      String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
      //print(" -- $prevExerciseTypeId - '$prevDate' against $exerciseTypeId - '$exerciseDate'");
      if (exerciseTypeId != prevExerciseTypeId || prevDate != exerciseDate) {
        ExerciseType? exerciseType = Cache().getExerciseTypeById(prevExercise.exerciseTypeId!);
        String unit = exerciseType != null && exerciseType.unitQuantityUnit != null ? exerciseType.unitQuantityUnit! : prevExercise.unit!;
        prevExercise.summary = summary + " " + unit;
        exerciseLogList!.add(prevExercise);
        //print("Log add " + exercise.toJson().toString());
        summary = "";
        prevCount = 0;
      }
      String delimiter = "";
      if (prevCount > 0) delimiter = ", ";
      double quantity = exercise.quantity == null ? 0 : exercise.quantity!;
      summary += delimiter + quantity.toStringAsFixed(0);
      ExerciseType? exerciseType = Cache().getExerciseTypeById(exercise.exerciseTypeId!);
      //print("exerciseType " + (exerciseType == null ? "NULL" : exerciseType.name) + " ID " + exercise.exerciseTypeId.toString());
      if (exerciseType != null) {
        if (exerciseType.unitQuantity == "1") {
          summary += "x" + exercise.unitQuantity!.toStringAsFixed(0);
        }
        //print(" --- sum  " + exerciseType.name + " $summary");
      }

      prevExerciseTypeId = exerciseTypeId;
      prevDate = exerciseDate;
      prevExercise = exercise;
      prevCount++;
    }
    prevExercise.summary = summary;
    exerciseLogList!.add(prevExercise);
  }
}
