import 'package:workouttest_util/model/cache.dart';
import 'package:workouttest_util/model/customer_training_plan.dart';
import 'package:workouttest_util/model/customer_training_plan_details.dart';
import 'package:workouttest_util/model/exercise.dart';
import 'package:workouttest_util/model/exercise_plan_detail.dart';
import 'package:workouttest_util/model/exercise_tree.dart';
import 'package:workouttest_util/model/fitness_state.dart';
import 'package:workouttest_util/model/training_plan.dart';
import 'package:workouttest_util/model/training_plan_detail.dart';
import 'package:workouttest_util/repository/exercise_type_repository.dart';
import 'package:workouttest_util/repository/training_plan_day_repository.dart';
import 'package:workouttest_util/util/app_language.dart';
import 'package:workouttest_util/util/common.dart';
import 'package:workouttest_util/util/logging.dart';

class TrainingPlanRepository with Common, Logging {
  ExerciseTree? parentTree;
  List<TrainingPlan> getPlansByParent(String parent) {
    final List<TrainingPlan> resultList = [];
    final List<ExerciseTree>? exerciseTree = Cache().getExerciseTree();
    int? parentId;
    if (exerciseTree != null) {
      for (var element in exerciseTree) {
        if (element.internalName == parent) {
          parentId = element.treeId;
          parentTree = element;
        }
      }
    }

    final List<TrainingPlan>? plans = Cache().getTrainingPlans();
    if (plans != null && parentId != null) {
      for (var element in plans) {
        if (element.treeId == parentId) {
          resultList.add(element);
        }
      }
    }
    return resultList;
  }

  /// 1. deactivate old training plans - update all

  /// 2. calculate customer_training_plan_details weights / repleats
  /// 3. create new customer_training_plan

  CustomerTrainingPlan? activateTrainingPlan(int trainingPlanId) {
    log(" **** Activate Plan: $trainingPlanId");
    // 1. deactivate
    if (Cache().getCustomerTrainingPlans() != null) {
      for (var plan in Cache().getCustomerTrainingPlans()!) {
        plan.active = false;
        if (plan.customerTrainingPlanId != null) {
          //TrainingPlanApi().updateCustomerTrainingPlan(plan, plan.customerTrainingPlanId!);
        }
      }
    }

    CustomerTrainingPlan plan = CustomerTrainingPlan();
    plan.customerId = Cache().userLoggedIn!.customerId;
    plan.trainingPlanId = trainingPlanId;
    plan.active = true;
    plan.status = "open";
    plan.dateAdd = DateTime.now();
    TrainingPlan? trainingPlan = getTrainingPlanById(trainingPlanId);
    if (trainingPlan == null || trainingPlan.details == null) {
      log("trainingPlan null");
      return null;
    }
    plan.name = trainingPlan.nameTranslations[AppLanguage().appLocal.toString()];

    // 3 calculate weights
    int index = 0;
    int exerciseTypeIdOrig = 0;
    for (var elem in trainingPlan.details!) {
      List<CustomerTrainingPlanDetails> list = createDetail(plan, elem, exerciseTypeIdOrig, index);
      exerciseTypeIdOrig = elem.exerciseTypeId;
      for (var element in list) {
        plan.details.add(element);
        index++;
      }
    }

    Cache().myTrainingPlan = plan;

    return plan;
  }

  CustomerTrainingPlanDetails? getDetailById(CustomerTrainingPlan? plan, int customerTrainingPlanDetailsId) {
    CustomerTrainingPlanDetails? foundDetail;

    if (plan == null || plan.details.isEmpty || customerTrainingPlanDetailsId == 0) {
      return foundDetail;
    }

    for (var detail in plan.details) {
      if (detail.customerTrainingPlanDetailsId == customerTrainingPlanDetailsId) {
        foundDetail = detail;
        break;
      }
    }

    return foundDetail;
  }

  CustomerTrainingPlanDetails createAlternativeDetail(
      CustomerTrainingPlan plan, CustomerTrainingPlanDetails detail, TrainingPlanDetail elem, int exerciseTypeId) {
    CustomerTrainingPlanDetails alternativeDetail = CustomerTrainingPlanDetails();
    alternativeDetail.copy(detail);
    alternativeDetail.exerciseTypeId = exerciseTypeId;
    alternativeDetail.exerciseType = Cache().getExerciseTypeById(exerciseTypeId);

    if (elem.weight == -1) {
      if (alternativeDetail.exerciseType!.unitQuantityUnit != null) {
        alternativeDetail = getCalculatedWeightRepeats(elem.exerciseTypeId, alternativeDetail);
      } else {
        alternativeDetail.weight = 0;
      }
    } else if (elem.weight == -2) {
      final CustomerTrainingPlanDetails calculated = isWeightCalculatedByExerciseType(elem.exerciseTypeId, alternativeDetail, plan);
      if (calculated.weight != -1) {
        alternativeDetail.weight = calculated.weight;
      } else {
        alternativeDetail.weight = -2;
      }
    } else {
      alternativeDetail.weight = elem.weight;
    }
    //print("Detail $alternativeDetail exerciseType: ${alternativeDetail.exerciseType!.exerciseTypeId}");

    return alternativeDetail;
  }

  List<CustomerTrainingPlanDetails> createDetail(CustomerTrainingPlan plan, TrainingPlanDetail elem, int exerciseTypeIdOrig, int index,
      {bool changeExerciseType = false}) {
    List<CustomerTrainingPlanDetails> list = [];
    CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails();
    detail.customerTrainingPlanDetailsId = ++index;
    detail.trainingPlanDetailsId = elem.trainingPlanDetailId;
    detail.exerciseTypeId = changeExerciseType ? exerciseTypeIdOrig : elem.exerciseTypeId;
    detail.repeats = elem.repeats;
    detail.set = elem.set;
    detail.dayId = elem.dayId;
    TrainingPlanDayRepository trainingPlanDayRepository = const TrainingPlanDayRepository();
    detail.day = trainingPlanDayRepository.getNameById(elem.dayId);
    detail.parallel = elem.parallel;
    detail.restingTime = elem.restingTime;
    detail.exerciseType = Cache().getExerciseTypeById(detail.exerciseTypeId!);
    detail.alternatives = ExerciseTypeRepository.getExerciseTypeAlternatives(detail.exerciseTypeId);
    if (elem.weight == -1) {
      if (detail.exerciseType!.unitQuantityUnit != null) {
        detail = getCalculatedWeightRepeats(elem.exerciseTypeId, detail);
      } else {
        detail.weight = 0;
      }
    } else if (elem.weight == -2) {
      final CustomerTrainingPlanDetails calculated = isWeightCalculatedByExerciseType(elem.exerciseTypeId, detail, plan);
      if (calculated.weight != -1) {
        detail.weight = calculated.weight;
      } else {
        detail.weight = -2;
      }
    } else {
      detail.weight = elem.weight;
    }
    print("Detail $detail exerciseType: ${detail.exerciseType!.exerciseTypeId}");

    detail.state = ExercisePlanDetailState.start;
    if (detail.weight != null && detail.weight! > 0) {
      detail.baseOneRepMax = calculate1RM(detail.weight!, detail.repeats!.toDouble());
    }

    // first repeat: 50% more
    if (detail.weight != null && detail.weight! > 0 && exerciseTypeIdOrig != detail.exerciseTypeId && detail.repeats! > 0) {
      CustomerTrainingPlanDetails firstDetail = CustomerTrainingPlanDetails();
      firstDetail.copy(detail);
      firstDetail.repeats = (detail.repeats! * 1.5).round();
      firstDetail.baseOneRepMax = calculate1RM(firstDetail.weight!, firstDetail.repeats!.toDouble());
      firstDetail.set = 1;
      detail.set = detail.set! - 1;
      if (detail.set! > 0) {
        index++;
      }
      detail.customerTrainingPlanDetailsId = index;
      list.add(firstDetail);
    }

    if (detail.set! > 0) {
      list.add(detail);
    }
    return list;
  }

  CustomerTrainingPlanDetails isWeightCalculatedByExerciseType(int exerciseTypeId, CustomerTrainingPlanDetails detail, CustomerTrainingPlan plan) {
    CustomerTrainingPlanDetails calculated = detail;
    for (var element in plan.details) {
      if (element.exerciseTypeId == exerciseTypeId) {
        calculated = element;
        break;
      }
    }

    return calculated;
  }

  TrainingPlan? getTrainingPlanById(int trainingPlanId) {
    TrainingPlan? plan;
    if (Cache().getTrainingPlans() == null) {
      return plan;
    }

    for (var trainingPlan in Cache().getTrainingPlans()!) {
      if (trainingPlan.trainingPlanId == trainingPlanId) {
        plan = trainingPlan;
        break;
      }
    }

    return plan;
  }

  int? getTrainingPlanByInternalName(String internalName) {
    int? id;
    if (Cache().getTrainingPlans() == null) {
      return id;
    }

    for (var trainingPlan in Cache().getTrainingPlans()!) {
      //print("internal ${trainingPlan.internalName}");
      if (trainingPlan.internalName == internalName) {
        id = trainingPlan.trainingPlanId;
        break;
      }
    }

    return id;
  }

  CustomerTrainingPlanDetails getCalculatedWeightRepeats(int exerciseTypeId, CustomerTrainingPlanDetails detail) {
    double weight = -1;
    if (Cache().getExercises() == null) {
      detail.weight = weight;
      detail.isTest = true;
      return detail;
    }

    Exercise? lastExercise1RM;
    DateTime dt = DateTime.now().subtract(const Duration(days: 30));
    List<Exercise> exercises = Cache().getExercises()!;
    exercises.sort((a, b) {
      // reverse
      return a.dateAdd!.compareTo(b.dateAdd!);
    });
    for (var exercise in exercises) {
      if (exercise.exerciseTypeId == exerciseTypeId && exercise.dateAdd!.compareTo(dt) >= 0) {
        detail.weight = weight;
        lastExercise1RM = exercise;
        //print("last exercise: $exercise");
      }
    }

    if (lastExercise1RM == null || lastExercise1RM.unitQuantity == null) {
      detail.weight = weight;
      detail.isTest = true;
      return detail;
    }

    double oneRepMax = calculateMax1RMSameDay(lastExercise1RM);
    // Common.calculate1RM(lastExercise1RM!.unitQuantity!, lastExercise1RM!.quantity!);
    //print("Exercise $exerciseTypeId - 1RM : $oneRepMax");
    weight = oneRepMax * Common.get1RMPercent(detail.repeats!);
    //print("Exercise $exerciseTypeId - weight : $weight");
    //weight = Common.roundWeight(weight);
    //detail.weight = Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), lastExercise1RM!.quantity!);
    //weight = lastExercise1RM!.unitQuantity! * detail.repeats! / lastExercise1RM!.quantity!;
    weight = Common.roundWeight(weight);
    //print("Recaluclated weight ${detail.weight} - repeat: ${detail.repeats}");

    //detail.repeats = Common.calculateQuantityByChangedWeight(oneRepMax, weight, detail.repeats!.toDouble());

    detail.weight = weight;
    return detail;
  }

  double calculateMax1RMSameDay(Exercise actual) {
    List<Exercise> exercises = Cache().getExercises()!;
    double max1RM = 0.0;

    for (var exercise in exercises) {
      if (actual.exerciseTypeId == exercise.exerciseTypeId &&
          actual.dateAdd!.year == exercise.dateAdd!.year &&
          actual.dateAdd!.month == exercise.dateAdd!.month &&
          actual.dateAdd!.day == exercise.dateAdd!.day) {
        double oneRepMax = calculate1RM(exercise.unitQuantity!, exercise.quantity!);
        if (max1RM < oneRepMax) {
          max1RM = oneRepMax;
        }
      }
    }

    return max1RM;
  }

  int getOriginalRepeats(int trainingPlanId, CustomerTrainingPlanDetails detail) {
    TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
    if (plan == null) {
      return 0;
    }
    int originalRepeats = 0;
    for (var element in plan.details!) {
      if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
        originalRepeats = element.repeats ?? 0;
      }
    }
    return originalRepeats;
  }

  double getOriginalWeight(int trainingPlanId, CustomerTrainingPlanDetails detail) {
    TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
    if (plan == null) {
      return 0;
    }
    double originalWeight = 0;
    for (var element in plan.details!) {
      if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
        originalWeight = element.weight ?? 0;
      }
    }
    return originalWeight;
  }

  CustomerTrainingPlanDetails recalculateDetailFixRepeats(int trainingPlanId, CustomerTrainingPlanDetails detail) {
    TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
    if (plan == null) {
      return detail;
    }
    int originalRepeats = getOriginalRepeats(trainingPlanId, detail);

    detail.weight = Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), originalRepeats.toDouble());
    detail.weight = Common.roundWeight(detail.weight!);
    print("Recalculated weight: ${detail.weight}");
    detail.repeats = originalRepeats;
    return detail;
  }

  CustomerTrainingPlanDetails recalculateDetailFixRepeatsSet1(
      int trainingPlanId, CustomerTrainingPlanDetails detail, CustomerTrainingPlanDetails detailWithData) {
    TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
    if (plan == null) {
      return detail;
    }
    int originalRepeats = getOriginalRepeats(trainingPlanId, detail);

    detail.weight = Common.calculateWeigthByChangedQuantity(detailWithData.weight!, detailWithData.repeats!.toDouble(), originalRepeats.toDouble());
    detail.weight = Common.roundWeight(detail.weight!);
    log("Recalculated weight: ${detail.weight}");
    detail.repeats = originalRepeats;
    return detail;
  }

  CustomerTrainingPlanDetails recalculateDetail(int trainingPlanId, CustomerTrainingPlanDetails detail, CustomerTrainingPlanDetails nextDetail) {
    CustomerTrainingPlanDetails recalculatedDetail = nextDetail;

    // 1. get original repeats

    // 1a get original plan
    TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
    if (plan == null) {
      return recalculatedDetail;
    }

    // 1.b get the original detail's repeat
    int originalRepeats = detail.repeats!;
    for (var element in plan.details!) {
      if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
        originalRepeats = element.repeats ?? 0;
      }
    }

    // 2 get recalculated repeats
    recalculatedDetail.weight = Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), originalRepeats.toDouble());
    recalculatedDetail.weight = Common.roundWeight(recalculatedDetail.weight!);
    log("recalculated repeats for $originalRepeats: ${recalculatedDetail.weight}");
    //recalculatedDetail.repeats = originalRepeats;

    return recalculatedDetail;
  }

  void generateTrainingPlan() {
    int? trainingPlanId;
    if (Cache().userLoggedIn == null) {
      return;
    }

    bool isWoman = Cache().userLoggedIn!.sex == "w";

    if (Cache().userLoggedIn!.goal == "shape_forming") {
      if (Cache().userLoggedIn!.fitnessLevel == FitnessState.beginner) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("women_shape_L1") : getTrainingPlanByInternalName("man_routine1");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.intermediate) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("women_shape_L2") : getTrainingPlanByInternalName("man_routine3");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.advanced) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("women_shape_L3") : getTrainingPlanByInternalName("man_routine4");
      } else {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("women_shape_L4") : getTrainingPlanByInternalName("man_routine2");
      }
    } else if (Cache().userLoggedIn!.goal == "muscle_endurance") {
      if (Cache().userLoggedIn!.fitnessLevel == FitnessState.beginner) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_se_l1") : getTrainingPlanByInternalName("man_se_l1");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.intermediate) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_se_l2") : getTrainingPlanByInternalName("man_se_l2");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.advanced) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_se_l3") : getTrainingPlanByInternalName("man_se_l3");
      } else {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_se_l4") : getTrainingPlanByInternalName("man_se_l4");
      }
    } else if (Cache().userLoggedIn!.goal == "gain_strength") {
      if (Cache().userLoggedIn!.fitnessLevel == FitnessState.beginner) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_power_l1") : getTrainingPlanByInternalName("man_power_l1");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.intermediate) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_power_l2") : getTrainingPlanByInternalName("man_power_l2");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.advanced) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_power_l3") : getTrainingPlanByInternalName("man_power_l3");
      } else {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_power_l4") : getTrainingPlanByInternalName("man_power_l4");
      }
    } else if (Cache().userLoggedIn!.goal == "gain_muscle") {
      if (Cache().userLoggedIn!.fitnessLevel == FitnessState.beginner) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("woman_beginner") : getTrainingPlanByInternalName("beginner_man");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.intermediate) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("woman_beginner_split") : getTrainingPlanByInternalName("man_foundation");
      } else if (Cache().userLoggedIn!.fitnessLevel == FitnessState.advanced) {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("woman_advanced") : getTrainingPlanByInternalName("basic_mass_building");
      } else {
        trainingPlanId = isWoman ? getTrainingPlanByInternalName("man_routine2") : getTrainingPlanByInternalName("mass_building");
      }
    }

    log("Generated plan $trainingPlanId fitness ${Cache().userLoggedIn!.fitnessLevel} - ${FitnessState.beginner}");

    if (trainingPlanId != null) {
      CustomerTrainingPlan? customerTrainingPlan = activateTrainingPlan(trainingPlanId);
      if (customerTrainingPlan != null) {
        Cache().myTrainingPlan = customerTrainingPlan;
        Cache().saveMyTrainingPlan();
      }
    }
  }
}