import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { distinctUntilChanged, forkJoin, map, retry, tap } from 'rxjs';
import { ToastDispatchers } from 'src/app/core/stores/index.dispatchers';

import { FoodCardProps } from 'src/app/shared/interfaces/components/cards/food-card/food-card.interface';
import {
  EditMeal,
  EditedFood,
  EditedRecipe,
} from 'src/app/shared/interfaces/services/api-fit/request/meal.interface';
import { Ingredient } from 'src/app/shared/interfaces/services/api-fit/response/ingredient.interface';
import { Meal } from 'src/app/shared/interfaces/services/api-fit/response/meal.interface';
import { Recipe } from 'src/app/shared/interfaces/services/api-fit/response/recipe.interface';

import { ApiFitService } from 'src/app/shared/services/api-fit/api-fit.service';

@Component({
  selector: 'app-details',
  templateUrl: './details.component.html',
  styleUrls: ['./details.component.scss'],
})
export class DetailsComponent {
  loadedWithSuccess = true;
  meal: Meal = {
    id: 0,
    mealTime: '',
    mealDate: new Date(),
    foods: [],
    recipes: [],
    nutrients: [],
  };

  mealFoods: {
    card: FoodCardProps;
    ingredient: Ingredient;
  }[] = [];

  mealRecipes: {
    card: FoodCardProps;
    ingredients: Ingredient[];
  }[] = [];

  ingredients: Ingredient[] = [];

  foodToEdit: FoodCardProps | null = null;
  foodToRemove: FoodCardProps | null = null;

  isModalAddFoodOpen = false;
  searchType: 'food' | 'recipe' = 'food';

  maxFoods = 48;
  maxRecipes = 16;

  constructor(
    private activatedRoute: ActivatedRoute,
    private router: Router,
    private ApiFitService: ApiFitService,
    private toastDispatchers: ToastDispatchers,
  ) {}

  ngOnDestroy() {
    this.loadedWithSuccess && this.editMeal();
  }

  async ngOnInit() {
    try {
      const mealId = await this.getMealIdFromUrl();

      this.meal = await this.fetchMealById(mealId);

      await this.setMealNutrients();
      this.updateIngredientsList();
    } catch (err) {
      this.loadedWithSuccess = false;
      this.router.navigate(['/app/meals']);
    }
  }

  getMealIdFromUrl() {
    return new Promise(
      (
        resolve: (mealId: Meal['id']) => void,
        reject: (err: unknown) => void,
      ) => {
        this.activatedRoute.params.subscribe({
          next: (p) => resolve(p['id']),
          error: reject,
        });
      },
    );
  }

  fetchMealById(id: Meal['id']) {
    return new Promise(
      (resolve: (meal: Meal) => void, reject: (err: unknown) => void) => {
        this.ApiFitService.getMealById(id).subscribe({
          next: (r) => resolve(r.data),
          error: reject,
        });
      },
    );
  }

  async setMealNutrients() {
    return new Promise(
      (
        resolve: (value: unknown) => void,
        reject: (reason: unknown) => void
      ) => {
        const ingredientsRequests = [
          ...this.meal.recipes.map((recipe) => this.addRecipeData(recipe)),
          ...this.meal.foods.map((food) => this.addFoodData(food)),
        ];
    
        forkJoin(ingredientsRequests)
        .pipe(
          distinctUntilChanged(),
          retry(2)
        )
        .subscribe({
          next: () => {
            resolve(true);
          },
          error: reject
        });
      }
    );
  }

  addRecipeData(recipe: Meal['recipes'][0]) {
    return this.ApiFitService.getRecipeIngredients({
      recipeId: recipe.id,
    }).pipe(
      map((response) => {
        return response.data.map((ingredient) => {
          return {
            ...ingredient,
            quantity:
              (ingredient.quantity / recipe.servings) * recipe.consumedServings,
          };
        });
      }),

      tap({
        next: (ingredients) => {
          this.mealRecipes = [
            ...this.mealRecipes,
            {
              card: {
                ...recipe,
                kcal: recipe.consumedKcal,
                quantity: recipe.consumedServings,
                type: 'recipe',
              },
              ingredients,
            },
          ];
        },
      }),
    );
  }

  addFoodData(food: Meal['foods'][0]) {
    return this.ApiFitService.getFoodById(food.id).pipe(
      map((response) => {
        return {
          ...response.data,
          quantity: food.quantity,
        } as Ingredient;
      }),

      tap({
        next: (ingredient) => {
          this.mealFoods = [
            ...this.mealFoods,
            {
              card: {
                ...ingredient,
                kcal: food.kcal,
                type: 'food',
              },
              ingredient,
            },
          ];
        },
      }),
    );
  }

  updateIngredientsList() {
    this.ingredients = [
      ...this.mealFoods.map((f) => f.ingredient),
      ...this.mealRecipes.map((r) => r.ingredients).flat(),
    ];
  }

  editFood(foodToEdit: FoodCardProps) {
    this.foodToEdit = Object.create(foodToEdit);
  }

  updateFood(editedFood: FoodCardProps | null) {
    const foodIndex = this.mealFoods.findIndex(
      (f) => f.card.id === editedFood?.id,
    );

    if (!editedFood || foodIndex === -1) return;

    const food = this.mealFoods[foodIndex];

    food.ingredient.quantity = editedFood.quantity;

    food.card = {
      ...food.card,
      ...editedFood,
    };

    this.updateIngredientsList();
    this.foodToEdit = null;
  }

  updateRecipe(editedRecipe: FoodCardProps | null) {
    const recipeIndex = this.mealRecipes.findIndex(
      (r) => r.card.id === editedRecipe?.id,
    );

    if (!editedRecipe || recipeIndex === -1) return;

    const recipe = this.mealRecipes[recipeIndex];

    recipe.ingredients = recipe.ingredients.map((i) => {
      return {
        ...i,
        quantity: (editedRecipe.quantity * i.quantity) / recipe.card.quantity,
      };
    });

    recipe.card = {
      ...recipe.card,
      ...editedRecipe,
    };

    this.updateIngredientsList();
  }

  excludeItem() {
    const id = this.foodToRemove?.id;

    if (!this.foodToRemove) return;

    if (this.foodToRemove.type === 'food') {
      this.mealFoods = this.mealFoods.filter((r) => r.card.id !== id);
    } else {
      this.mealRecipes = this.mealRecipes.filter((r) => r.card.id !== id);
    }

    this.foodToRemove = null;
    this.updateIngredientsList();
  }

  toggleAddFoodModal(type: 'food' | 'recipe') {
    if (type === 'food' && this.mealFoods.length >= this.maxFoods) return;
    if (type === 'recipe' && this.mealRecipes.length >= this.maxRecipes) return;

    this.isModalAddFoodOpen = !this.isModalAddFoodOpen;
    this.searchType = type;
  }

  addFood(food: FoodCardProps) {
    if (food.type === 'food') {
      this.addFoodData(food).subscribe({
        next: () => this.updateIngredientsList(),
      });
    } else {
      const recipe = Object.create(food) as Recipe;

      this.addRecipeData({
        id: food.id,
        consumedKcal: food.kcal,
        consumedServings: food.quantity,
        name: food.name,
        servings: recipe.servings,
      }).subscribe({
        next: () => this.updateIngredientsList(),
      });
    }
  }

  editMeal() {
    const addedFoods: EditedFood[] = this.mealFoods
      .filter((f) => this.meal.foods.every((food) => food.id !== f.card.id))
      .map((f) => f.card);
    const editedFoods: EditedFood[] = this.mealFoods
      .filter(({ ingredient }) =>
        this.meal.foods.find(
          (food) =>
            ingredient.id === food.id && food.quantity !== ingredient.quantity,
        ),
      )
      .map((f) => f.card);
    const removedFoods: EditedFood[] = this.meal.foods
      .filter((food) =>
        this.mealFoods.every(({ ingredient }) => ingredient.id !== food.id),
      )
      .map((f) => {
        return {
          ...f,
          delete: true,
        };
      });

    const addedRecipes: EditedRecipe[] = this.mealRecipes
      .filter((r) =>
        this.meal.recipes.every((recipe) => recipe.id !== r.card.id),
      )
      .map((r) => {
        return {
          ...r.card,
          consumedServings: r.card.quantity,
        };
      });
    const editedRecipes: EditedRecipe[] = this.mealRecipes
      .filter((r) =>
        this.meal.recipes.find(
          (recipe) =>
            recipe.id === r.card.id &&
            recipe.consumedServings !== r.card.quantity,
        ),
      )
      .map((r) => {
        return {
          ...r.card,
          consumedServings: r.card.quantity,
        };
      });
    const removedRecipes: EditedRecipe[] = this.meal.recipes
      .filter((r) => this.mealRecipes.every(({ card }) => card.id !== r.id))
      .map((r) => {
        return {
          ...r,
          quantity: 0,
          delete: true,
        };
      });

    const foods = [...addedFoods, ...editedFoods, ...removedFoods];

    const recipes = [...addedRecipes, ...editedRecipes, ...removedRecipes];

    if (!foods.length && !recipes.length) return;

    const mealDate = new Date(this.meal.mealDate).toISOString();
    const payload: EditMeal = {
      date: mealDate,
    };

    if (foods.length) payload.foods = foods;
    if (recipes.length) payload.recipes = recipes;

    this.ApiFitService.editMeal(this.meal.id, {
      ...payload,
      date: mealDate,
    }).subscribe({
      next: () => {
        this.toastDispatchers.showSuccess({
          message: 'Refeição editada com sucesso',
          isVisible: true,
          showCloseButton: true,
          shouldHideAutomatically: true,
          delay: 3000,
        });
      },
    });
  }
}
