import 'dart:async';
import 'dart:collection';

import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_ability.dart';
import 'package:aitrainer_app/model/exercise_plan.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/service/exercise_plan_service.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

part 'test_set_execute_event.dart';
part 'test_set_execute_state.dart';

class TestSetExecuteBloc extends Bloc<TestSetExecuteEvent, TestSetExecuteState> {
  // ignore: close_sinks
  late MenuBloc menuBloc;
  int? exerciseTypeId;
  String? testName;
  String testType = "";
  bool miniTestSet = false;
  bool paralellTest = false;
  double scrollOffset = 0;
  ExercisePlan? exercisePlan;
  bool isDone100 = false;

  List<ExercisePlanDetail>? exercisePlanDetails;

  TestSetExecuteBloc() : super(TestSetExecuteInitial());

  void setExerciseTypeId(int id) => exerciseTypeId = id;

  void initExercisePlan() {
    exercisePlan = Cache().activeExercisePlan;
    if (exercisePlan != null) {
      print("Ability ${exercisePlan!.type!}");
      testName = exercisePlan!.name;
      this.miniTestSet = exercisePlan!.type != null && ExerciseAbility.mini_test_set.equalsStringTo(exercisePlan!.type!);
      this.paralellTest = exercisePlan!.type != null && ExerciseAbility.paralell_test.equalsStringTo(exercisePlan!.type!);

      testType = ExerciseAbility.mini_test_set.equalsStringTo(exercisePlan!.type!)
          ? ExerciseAbility.mini_test_set.description
          : ExerciseAbility.paralell_test.description;
      print("exercisePlan: $testName type: $testType");
    }
    exercisePlanDetails = Cache().activeExercisePlanDetails;
    if (exercisePlanDetails != null) {
      exercisePlanDetails!.forEach((element) {
        final ExerciseType? exerciseType = Cache().getExerciseTypeById(element.exerciseTypeId);
        if (exerciseType != null) {
          element.exerciseType = exerciseType;
        }
        this.setPlanDetailState(element);
      });
    }
  }

  @override
  Stream<TestSetExecuteState> mapEventToState(
    TestSetExecuteEvent event,
  ) async* {
    try {
      if (event is TestSetExecuteLoad) {
        yield TestSetExecuteLoading();
        initExercisePlan();
        if (exerciseTypeId != null) {
          int step = 0;
          if (exercisePlanDetails != null) {
            exercisePlanDetails!.forEach((element) {
              if (element.exerciseTypeId == this.exerciseTypeId) {
                scrollOffset = (step * 85).toDouble() + 10;
              }
              step++;
            });
          }
        }
        yield TestSetExecuteReady();
      } else if (event is TestSetExecuteDeleteActive) {
        print("Delete: ${exercisePlan!.type} paralellTest: $paralellTest");
        if (exercisePlan != null && ExerciseAbility.mini_test_set.equalsStringTo(exercisePlan!.type!)) {
          exercisePlan = null;
          if (exercisePlanDetails != null) {
            exercisePlanDetails!.removeRange(0, exercisePlanDetails!.length - 1);
          }
          await Cache().deleteActiveExercisePlan();
        }
      } else if (event is TestSetExecuteDeleteAllActive) {
        if (exercisePlan != null) {
          print("DeleteAll Test Set: ${exercisePlan!.type}");
          exercisePlan = null;
          if (exercisePlanDetails != null) {
            exercisePlanDetails!.removeRange(0, exercisePlanDetails!.length - 1);
          }
          await Cache().deleteActiveExercisePlan();
        }
      } else if (event is TestSetExecuteExerciseFinished) {
        yield TestSetExecuteLoading();
        exercisePlanDetails!.forEach((element) {
          if (element.exerciseTypeId == event.exerciseTypeId) {
            element.repeats = event.quantity.toInt();
            element.weightEquation = event.unitQuantity.toString();
            if (element.exercises == null) {
              element.exercises = [];
            }
            final Exercise exercise = Exercise();
            exercise.customerId = Cache().userLoggedIn!.customerId!;
            exercise.exerciseTypeId = event.exerciseTypeId;
            exercise.quantity = event.quantity;
            exercise.unit = element.exerciseType!.unit;
            exercise.unitQuantity = event.unitQuantity;
            exercise.dateAdd = DateTime.now();
            element.exercises!.add(exercise);
            setPlanDetailState(element);
          }
        });
        Cache().saveActiveExercisePlan(exercisePlan!, exercisePlanDetails!);
        if (this.isDone100Percent()) {
          add(TestSetExecuteFinish());
        } else {
          yield TestSetExecuteReady();
        }
      } else if (event is TestSetExecuteNewExercise) {
        yield TestSetExecuteLoading();
        if (exercisePlan == null) {
          exercisePlan = ExercisePlan(Cache().userLoggedIn!.name! + " Custom Test", Cache().userLoggedIn!.customerId!);
          exercisePlan!.private = true;
          exercisePlan!.dateAdd = DateTime.now();
          exercisePlan!.type = ExerciseAbility.paralell_test.enumToString();
          ExercisePlan savedExercisePlan = await ExercisePlanApi().saveExercisePlan(exercisePlan!);
          exercisePlan = savedExercisePlan;
          exercisePlanDetails = [];
        }

        if (!this.existsInPlanDetails(event.exerciseTypeId)) {
          ExercisePlanDetail exercisePlanDetail = ExercisePlanDetail(event.exerciseTypeId);
          if (exercisePlan!.exercisePlanId != null) {
            exercisePlanDetail.exercisePlanId = exercisePlan!.exercisePlanId!;
          }
          final ExerciseType exerciseType = Cache().getExerciseTypeById(event.exerciseTypeId)!;

          exercisePlanDetail.serie = exerciseType.unitQuantityUnit == null ? 1 : 4;
          exercisePlanDetail.exerciseType = exerciseType;
          exercisePlanDetail.exerciseTypeId = event.exerciseTypeId;
          ExercisePlanDetail savedDetail = await ExercisePlanApi().saveExercisePlanDetail(exercisePlanDetail);
          exercisePlanDetail.exercisePlanDetailId = savedDetail.exercisePlanDetailId;
          exercisePlanDetail.state = ExercisePlanDetailState.start;
          exercisePlanDetail.exercises = [];
          if (exercisePlanDetails == null) {
            exercisePlanDetails = [];
          }
          exercisePlanDetails!.add(exercisePlanDetail);
          await Cache().saveActiveExercisePlan(exercisePlan!, exercisePlanDetails!);
          paralellTest = true;
        }
        yield TestSetExecuteReady();
      } else if (event is TestSetExecuteDeleteExercise) {
        yield TestSetExecuteLoading();
        ExercisePlanDetail? deleteDetail;
        if (exercisePlanDetails != null) {
          exercisePlanDetails!.forEach((element) {
            if (element.exerciseTypeId == event.exerciseTypeId) {
              deleteDetail = element;
            }
          });
          if (deleteDetail != null) {
            exercisePlanDetails!.remove(deleteDetail);
            if (exercisePlanDetails!.isEmpty) {
              exercisePlan = null;
              exercisePlanDetails = null;
              Cache().deleteActiveExercisePlan();
            }
          }
        }
        yield TestSetExecuteReady();
      } else if (event is TestSetExecuteFinish) {
        Cache().deleteActiveExercisePlan();
        isDone100 = isDone100Percent();
        // Animation
        // Home
        yield TestSetExecuteFinished();
      }
    } on Exception catch (e) {
      yield TestSetExecuteError(message: e.toString());
    }
  }

  bool hasBegun() {
    if (exercisePlanDetails == null ||
        exercisePlanDetails!.isEmpty ||
        exercisePlanDetails![0].exercises == null ||
        exercisePlanDetails![0].exercises!.length == 0) {
      return false;
    }

    return true;
  }

  void setPlanDetailState(ExercisePlanDetail exercisePlanDetail) {
    if (exercisePlanDetail.exercises == null || exercisePlanDetail.exercises!.length == 0) {
      exercisePlanDetail.state = ExercisePlanDetailState.start;
    } else {
      int maxLength = 1;
      if (exercisePlanDetail.exerciseType!.unitQuantityUnit != null) {
        maxLength = 4;
      }
      if (exercisePlanDetail.exercises!.length >= maxLength) {
        exercisePlanDetail.state = ExercisePlanDetailState.finished;
      } else {
        exercisePlanDetail.state = ExercisePlanDetailState.inProgress;
      }
    }
  }

  ExercisePlanDetailState actualState(int exerciseTypeId) {
    ExercisePlanDetailState state = ExercisePlanDetailState.start;
    if (exercisePlanDetails == null) return state;
    exercisePlanDetails!.forEach((element) {
      if (element.exerciseTypeId == exerciseTypeId) {
        state = element.state;
      }
    });
    return state;
  }

  bool existsInPlanDetails(int exerciseTypeId) {
    bool found = false;
    if (exercisePlanDetails == null) return found;
    exercisePlanDetails!.forEach((element) {
      if (element.exerciseTypeId == exerciseTypeId) {
        found = true;
      }
    });
    return found;
  }

  ExercisePlanDetail? actualExercisePlanDetail(int exerciseTypeId) {
    ExercisePlanDetail? found;
    if (exercisePlanDetails == null) return found;

    exercisePlanDetails!.forEach((element) {
      if (element.exerciseTypeId == exerciseTypeId) {
        found = element;
      }
    });
    return found;
  }

  bool isDone100Percent() {
    bool done = true;
    if (exercisePlanDetails == null || exercisePlanDetails!.isEmpty) {
      return false;
    }
    exercisePlanDetails!.forEach((element) {
      if (!element.state.equalsTo(ExercisePlanDetailState.finished)) {
        done = false;
      }
    });
    return done;
  }

  HashMap canAddNewExercise() {
    HashMap ret = HashMap();
    if (exercisePlan != null && ExerciseAbility.mini_test_set.equalsStringTo(exercisePlan!.type!)) {
      final String message = "You have an active Test Set!";
      final String message2 = "Do you want to override it?";
      ret['message'] = message;
      ret['message2'] = message2;
      ret['canAdd'] = false;
    } else {
      ret['canAdd'] = true;
    }

    return ret;
  }

  int? getActualWorkoutTreeId(int exerciseTypeId) {
    final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId);
    if (workoutTree == null) {
      return null;
    }
    return workoutTree.id;
  }

  String getActualImageName(int exerciseTypeId) {
    if (exerciseTypeId <= 0) {
      return "";
    }
    final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId);
    if (workoutTree == null) {
      return "";
    }

    return workoutTree.imageName;
  }

  bool isFirst() {
    if (exercisePlanDetails == null) {
      return true;
    }
    if (exercisePlanDetails!.isEmpty) {
      return true;
    }
    return (exercisePlanDetails![0].exercises == null || exercisePlanDetails![0].exercises!.length == 0);
  }

  bool existsActivePlan() {
    final bool exists = exercisePlan != null && exercisePlanDetails != null && exercisePlanDetails!.isNotEmpty;
    return exists;
  }

  bool isDone(ExercisePlanDetail exercisePlanDetail) {
    return (exercisePlanDetail.state.equalsTo(ExercisePlanDetailState.finished));
  }

  ExercisePlanDetail? getNext() {
    ExercisePlanDetail? nextExercisePlanDetail;

    int minStep = 99;
    if (this.exercisePlanDetails != null) {
      for (final detail in this.exercisePlanDetails!) {
        if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) {
          if (detail.exercises == null) {
            nextExercisePlanDetail = detail;
            minStep = 1;
            break;
          } else {
            final int step = detail.exercises!.length;
            if (step < minStep) {
              nextExercisePlanDetail = detail;
              minStep = step;
            }
          }
        }
      }
    }
    return nextExercisePlanDetail;
  }

  double calculate1RM(double quantity, double unitQuantity) {
    double weight = unitQuantity;
    double repeat = 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;
  }

  String repeatTimesText(ExercisePlanDetail exercisePlanDetail) {
    String text = "maximum";
    if (!hasBegun() || exercisePlanDetail.exerciseType!.unitQuantityUnit == null) {
      return text;
    }
    int step = exercisePlanDetail.exercises!.length;
    print("repeatTimes step $step");
    if (step == 2) {
      text = "12";
    }
    return text;
  }

  String getExerciseWeight(ExercisePlanDetail exercisePlanDetail) {
    String text = "you are able to do 12-20 repeats with";
    if (!hasBegun() || exercisePlanDetail.exercises!.length < 2) {
      return text;
    }
    final double? unitQuantity = exercisePlanDetail.exercises!.last.unitQuantity!;
    final double? quantity = exercisePlanDetail.exercises!.last.quantity!;
    double oneRepMax = this.calculate1RM(quantity!, unitQuantity!);
    text = (oneRepMax * 0.75).round().toStringAsFixed(0) + " " + exercisePlanDetail.exerciseType!.unitQuantityUnit!;
    return text;
  }
}
