import 'dart:async';

import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/customer_training_plan.dart';
import 'package:aitrainer_app/model/customer_training_plan_details.dart';
import 'package:aitrainer_app/model/exercise.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/repository/training_plan_repository.dart';
import 'package:aitrainer_app/service/exercise_service.dart';
import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

part 'training_plan_event.dart';
part 'training_plan_state.dart';

class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
  final TrainingPlanRepository trainingPlanRepository;
  final MenuBloc menuBloc;
  TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial());

  CustomerTrainingPlan? _myPlan;
  CustomerTrainingPlanDetails? _myDetail;

  bool started = false;
  final List<String> dayNames = [];
  bool restarting = false;
  bool celebrating = false;
  int activeDayIndex = 0;

  CustomerTrainingPlan? getMyPlan() => this._myPlan;
  setMyPlan(CustomerTrainingPlan? myPlan) => this._myPlan = myPlan;

  CustomerTrainingPlanDetails? getMyDetail() => this._myDetail;
  setMyDetail(CustomerTrainingPlanDetails? value) => this._myDetail = value;

  @override
  Stream<TrainingPlanState> mapEventToState(TrainingPlanEvent event) async* {
    try {
      if (event is TrainingPlanActivate) {
        yield TrainingPlanLoading();
        _myPlan = await trainingPlanRepository.activateTrainingPlan(event.trainingPlanId);
        _myPlan!.type = CustomerTrainingPlanType.template;

        menuBloc.menuTreeRepository.sortedTree.forEach((name, list) {
          final List<WorkoutMenuTree> menuList = list as List<WorkoutMenuTree>;
          menuList.forEach((element) {
            element.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.none;
          });
        });
        this.activateDays();
        Cache().myTrainingPlan = _myPlan;
        await Cache().saveMyTrainingPlan();
        yield TrainingPlanFinished();
      } else if (event is TrainingPlanWeightChange) {
        yield TrainingPlanLoading();
        event.detail.weight = event.weight;

        yield TrainingPlanReady();
      } else if (event is TrainingPlanRepeatsChange) {
        yield TrainingPlanLoading();

        event.detail.repeats = event.repeats;

        yield TrainingPlanReady();
      } else if (event is TrainingPlanSetChange) {
        yield TrainingPlanLoading();

        event.detail.set = event.set;

        yield TrainingPlanReady();
      } else if (event is TrainingPlanSaveExercise) {
        yield TrainingPlanLoading();

        event.detail.state = ExercisePlanDetailState.inProgress;
        final Exercise exercise = Exercise();
        exercise.customerId = Cache().userLoggedIn!.customerId!;
        exercise.exerciseTypeId = event.detail.exerciseTypeId;
        exercise.quantity = event.detail.repeats!.toDouble();
        exercise.unit = event.detail.exerciseType!.unit;
        exercise.unitQuantity = event.detail.weight;
        exercise.dateAdd = DateTime.now();
        event.detail.exercises.add(exercise);
        if (event.detail.exercises.length >= event.detail.set!) {
          event.detail.state = ExercisePlanDetailState.finished;
        } else if (event.detail.exercises.length >= 0) {
          event.detail.state = ExercisePlanDetailState.inProgress;
        }

        // recalculate the weight to the original planned repeats
        if (event.detail.isTest && event.detail.exercises.length == 1) {
          trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail);
        }

        exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;

        // save Exercise
        Exercise savedExercise = await ExerciseApi().addExercise(exercise);
        Cache().addExercise(savedExercise);

        Cache().myTrainingPlan = _myPlan;
        await Cache().saveMyTrainingPlan();

        if (isDayDone()) {
          this.add(TrainingPlanFinishDay());
        } else {
          yield TrainingPlanReady();
        }
      } else if (event is TrainingPlanSkipExercise) {
        yield TrainingPlanLoading();
        event.detail.state = ExercisePlanDetailState.skipped;
        Cache().myTrainingPlan = _myPlan;
        await Cache().saveMyTrainingPlan();
        if (isDayDone()) {
          this.add(TrainingPlanFinishDay());
        } else {
          yield TrainingPlanReady();
        }
      } else if (event is TrainingPlanFinishDay) {
        yield TrainingPlanLoading();
        celebrating = true;
        yield TrainingPlanDayFinished();
      } else if (event is TrainingPlanGoToRestart) {
        yield TrainingPlanLoading();
        restarting = true;
        yield TrainingPlanDayReadyToRestart();
      } else if (event is TrainingPlanAddExerciseType) {
        if (_myDetail == null) {
          throw Exception("Create new Detail");
        }
        yield TrainingPlanLoading();
        _myDetail!.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.added;
        _myPlan!.details.add(this._myDetail!);
        yield TrainingPlanReady();
      } else if (event is TrainingPlanDeleteExerciseType) {
        if (_myPlan == null || _myPlan!.details.isEmpty) {
          throw Exception("No MyPlan");
        }
        yield TrainingPlanLoading();
        CustomerTrainingPlanDetails? remove;
        for (var detail in _myPlan!.details) {
          if (event.exerciseType.exerciseTypeId == detail.exerciseTypeId) {
            remove = detail;
            break;
          }
        }
        if (remove != null) {
          _myPlan!.details.remove(remove);
        }
        event.exerciseType.trainingPlanState = ExerciseTypeTrainingPlanState.none;
        yield TrainingPlanReady();
      } else if (event is TrainingPlanCustomAddLoad) {
        yield TrainingPlanLoading();
        addNewPlan();

        _myDetail = CustomerTrainingPlanDetails();
        _myDetail!.exerciseType = event.exerciseType;
        _myDetail!.exerciseTypeId = event.exerciseType.exerciseTypeId;
        _myDetail!.repeats = 12;
        _myDetail!.weight = 30;
        _myDetail!.set = 3;
        _myDetail!.parallel = false;
        _myDetail!.day = "";
        _myDetail!.restingTime = 2;
        _myDetail!.state = ExercisePlanDetailState.start;

        if (_myDetail!.exerciseType!.unitQuantityUnit != null) {
          _myDetail = trainingPlanRepository.getCalculatedWeightRepeats(event.exerciseType.exerciseTypeId, _myDetail!);
        } else {
          _myDetail!.weight = 0;
        }
        yield TrainingPlanReady();
      }
    } on Exception catch (e) {
      yield TrainingPlanError(message: e.toString());
    }
  }

  void addNewPlan() {
    if (_myPlan == null) {
      _myPlan = CustomerTrainingPlan();
    } else {
      if (!_myPlan!.type.equalsTo(CustomerTrainingPlanType.custom)) {
        _myPlan!.details.clear();
      }
    }
    _myPlan!.trainingPlanId = 0;
    _myPlan!.name = "Custom";
    _myPlan!.dateAdd = DateTime.now();
    _myPlan!.customerId = Cache().userLoggedIn == null ? Cache().userLoggedIn!.customerId : 0;

    _myPlan!.type = CustomerTrainingPlanType.custom;

    restart();

    print("New custom plan: $_myPlan");
  }

  void restart() {
    if (_myPlan == null) {
      return;
    }
    for (var day in dayNames) {
      _myPlan!.days[day]!.clear();
    }
    _myPlan!.details.forEach((element) {
      element.state = ExercisePlanDetailState.start;
      element.exercises.clear();
    });
    dayNames.clear();
    Cache().myTrainingPlan = _myPlan;
    Cache().saveMyTrainingPlan();
    restarting = false;
    print("Restarting finished");
  }

  void activateDays() {
    if (_myPlan == null) {
      return;
    }

    if (isDone100Percent()) {
      this.add(TrainingPlanGoToRestart());
    }
    dayNames.clear();
    _myPlan!.days.clear();
    String dayName = ".";
    _myPlan!.details.forEach((element) {
      if (element.day != null && element.day != dayName) {
        dayNames.add(element.day!);
        dayName = element.day!;
      }
      if (_myPlan!.days[dayName] == null) {
        if (dayName == ".") {
          dayName = "";
        }
        _myPlan!.days[dayName] = [];
      }
      _myPlan!.days[dayName]!.add(element);
    });

    if (dayNames.length == 0) {
      dayNames.add("");
      _myPlan!.days[""] = [];
      _myPlan!.days[""]!.addAll(_myPlan!.details);
    }
    getActiveDayIndex();
  }

  CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) {
    CustomerTrainingPlanDetails? detail;
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return null;
    }

    for (final det in this._myPlan!.details) {
      if (det.customerTrainingPlanDetailsId == trainingPlanDetailsId) {
        detail = det;
        break;
      }
    }

    return detail;
  }

  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;
  }

  CustomerTrainingPlanDetails? getNext() {
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return null;
    }

    CustomerTrainingPlanDetails? next;
    int minStep = 99;
    for (final detail in this._myPlan!.details) {
      if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) {
        if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
          next = detail;
          minStep = 1;
          break;
        } else {
          final int step = detail.exercises.length;
          if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
            next = detail;
            minStep = step;
            if (detail.parallel != true) {
              break;
            }
          }
        }
      }
    }
    print("Next detail $next");

    return next;
  }

  bool isStarted() {
    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return false;
    }

    if (_myPlan!.details[0].state == ExercisePlanDetailState.start) {
      return false;
    } else {
      return true;
    }
  }

  bool isDayDone() {
    bool isDone = true;
    final String day = dayNames[activeDayIndex];
    for (var detail in _myPlan!.days[day]!) {
      if (!detail.state.equalsTo(ExercisePlanDetailState.finished) && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
        isDone = false;
      }
    }
    print("Is Day '$day' done: $isDone");
    return isDone;
  }

  double getOffset() {
    double offset = 5;
    if (_myPlan == null) {
      return offset;
    }
    int indexInProgress = 0;
    int indexInStart = 0;
    final String day = dayNames[this.activeDayIndex];
    for (var detail in _myPlan!.days[day]!) {
      if (detail.state == ExercisePlanDetailState.inProgress) {
        break;
      }
      if (detail.state == ExercisePlanDetailState.start) {
        break;
      }
      indexInStart++;
      indexInProgress++;
    }
    int index = indexInStart > indexInProgress ? indexInStart : indexInProgress;
    offset = index * 80;
    return offset;
  }

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

  int getActiveDayIndex() {
    if (restarting) {
      return 0;
    }
    if (_myPlan == null || _myPlan!.details.isEmpty) {
//      throw Exception("No defined Training Plan");
      return 0;
    }

    if (dayNames.isEmpty || dayNames.length == 1) {
      return 0;
    }

    activeDayIndex = 0;

    for (var day in dayNames) {
      if (_myPlan!.days[day] == null) {
        throw Exception("Wrong activated day: $day does not exist");
      }
      bool isDone = true;
      _myPlan!.days[day]!.forEach((element) {
        if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
          isDone = false;
        }
      });
      if (!isDone) {
        break;
      }
      activeDayIndex++;
    }

    if (activeDayIndex >= dayNames.length) {
      activeDayIndex = 0;
      this.add(TrainingPlanGoToRestart());
    }

    return activeDayIndex;
  }

  String getCustomAddSummary() {
    String summary = "";
    if (_myDetail == null || _myDetail!.set == null || _myDetail!.repeats == null) {
      return summary;
    }
    summary = getMyDetail()!.set!.toStringAsFixed(0) + " x " + getMyDetail()!.repeats!.toStringAsFixed(0);
    return summary;
  }

  String getExerciseName(Locale locale) {
    String exerciseName = "";
    if (_myDetail == null || _myDetail!.exerciseType == null) {
      return exerciseName;
    }
    exerciseName =
        AppLanguage().appLocal == Locale("en") ? getMyDetail()!.exerciseType!.name : getMyDetail()!.exerciseType!.nameTranslation;
    return exerciseName;
  }

  String getWeightByExerciseType(ExerciseType exerciseType) {
    double weight = 0;

    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return weight.toStringAsFixed(0);
    }

    for (var detail in _myPlan!.details) {
      if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        weight = detail.weight!;
        break;
      }
    }

    int decimal = weight % weight.round() == 0 ? 0 : 1;
    return weight.toStringAsFixed(decimal);
  }

  String getSetByExerciseType(ExerciseType exerciseType) {
    int value = 0;

    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return value.toStringAsFixed(0);
    }

    for (var detail in _myPlan!.details) {
      if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        value = detail.set!;
        break;
      }
    }

    return value.toStringAsFixed(0);
  }

  String getRepeatsByExerciseType(ExerciseType exerciseType) {
    int value = 0;

    if (_myPlan == null || _myPlan!.details.isEmpty) {
      return value.toStringAsFixed(0);
    }

    for (var detail in _myPlan!.details) {
      if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
        value = detail.repeats!;
        break;
      }
    }

    return value.toStringAsFixed(0);
  }

  bool existsAddedExerciseTypeInTree(String name) {
    bool exists = false;
    final List<WorkoutMenuTree>? listWorkoutTree = menuBloc.menuTreeRepository.sortedTree[name];
    if (listWorkoutTree != null) {
      listWorkoutTree.forEach((element) {
        if (element.exerciseType!.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added)) {
          exists = true;
        }
      });
    }
    return exists;
  }
}
