import { Component } from '@angular/core';
import {
  Meal,
  MealTime,
  MealsByDate,
} from 'src/app/shared/interfaces/services/api-fit/response/meal.interface';
import { ApiFitService } from 'src/app/shared/services/api-fit/api-fit.service';

import {
  primaryColor,
  quaternaryColor,
  secondaryColor,
} from 'src/app/shared/components/progress/progress.component';
import { QuantifiedNutrient } from 'src/app/shared/interfaces/services/api-fit/response/nutrient.interface';
import { ActivatedRoute, Router } from '@angular/router';
import { map } from 'rxjs';
import { UserService } from 'src/app/shared/services/user/user.service';
import { NutrientsService } from 'src/app/shared/services/nutrients/nutrients.service';
import {
  Intake,
  NutrientIntake,
} from 'src/app/shared/interfaces/services/api-fit/response/intake.interface';
import { LoaderDispatchers } from 'src/app/core/stores/index.dispatchers';

@Component({
  selector: 'app-meals',
  templateUrl: './meals.component.html',
  styleUrls: ['./meals.component.scss'],
})
export class MealsComponent {
  today = new Date();
  mealDate = new Date();

  consumedMeals: Meal[] = [];
  mealsByDate: MealsByDate[] = [];
  mealsByWeek: MealsByDate[] = [];
  mealTimes: MealTime[] = [];

  charts: {
    title: string;
    subtitle: string;
    fill: primaryColor;
    stroke: secondaryColor;
    subtitleColor: quaternaryColor;
    percent: number;
  }[] = [
    {
      title: '0kcal',
      subtitle: 'Calorias',
      fill: 'var(--yellow-100)',
      stroke: 'var(--yellow-050)',
      subtitleColor: 'var(--yellow-300)',
      percent: 0,
    },
    {
      title: '0g',
      subtitle: 'Carboidratos',
      fill: 'var(--green-100)',
      stroke: 'var(--green-050)',
      subtitleColor: 'var(--green-300)',
      percent: 0,
    },
    {
      title: '0g',
      subtitle: 'Proteínas',
      fill: 'var(--blue-100)',
      stroke: 'var(--blue-050)',
      subtitleColor: 'var(--blue-300)',
      percent: 0,
    },
    {
      title: '0g',
      subtitle: 'Gorduras',
      fill: 'var(--red-100)',
      stroke: 'var(--red-050)',
      subtitleColor: 'var(--red-300)',
      percent: 0,
    },
  ];

  constructor(
    private apiFitService: ApiFitService,
    private route: ActivatedRoute,
    private userService: UserService,
    private router: Router,
    private nutrientsService: NutrientsService,
    private loaderDispatchers: LoaderDispatchers
  ) {}

  async ngOnInit() {
    try {
      this.loaderDispatchers.showLoader();

      this.mealDate = await this.getDateFromQuery();

      this.mealTimes = await this.fetchMealsTimes();
      this.mealsByWeek = await this.userService.fetchMealsByWeek(this.mealDate);
  
      await this.setDailyMeals();
      await this.calculateCharts();
    } finally {
      this.loaderDispatchers.hideLoader();
    }
  }

  getDateFromQuery() {
    return new Promise(
      (resolve: (date: Date) => void, reject: (err: unknown) => void) => {
        this.route.queryParams.subscribe({
          next: (params) => {
            if (params['date']) {
              const dateInQuery = new Date(params['date']);
              const timezoneOffset = dateInQuery.getTimezoneOffset();
              const date = new Date(
                dateInQuery.getTime() + timezoneOffset * 60000,
              );

              if (date > this.today) {
                this.router.navigate(['/app/meals'], {
                  queryParams: {}
                });
                resolve(this.today);
              }

              resolve(date);
            }

            resolve(new Date());
          },
          error: reject,
        });
      },
    );
  }

  async handleDateChange(date: Date) {
    try {
      this.loaderDispatchers.showLoader();

      this.updateDateInQuery(date);

      await this.setDailyMeals();
      await this.calculateCharts();
    } finally {
      this.loaderDispatchers.hideLoader();
    }
  }

  updateDateInQuery(newDate: Date) {
    const timezoneOffset = newDate.getTimezoneOffset();
    const date = new Date(newDate.getTime() + timezoneOffset * 60000);
    const dateString = date.toISOString().split('T')[0];

    this.router.navigate(['/app/meals'], {
      queryParams: {
        date: dateString,
      },
    });
  }

  sortMealTimesById(a: MealTime, b: MealTime): number {
    if (a.id < b.id) return -1;
    if (a.id > b.id) return 1;
    return 0;
  }

  fetchMealsTimes() {
    return new Promise(
      (
        resolve: (mealTimes: MealTime[]) => void,
        reject: (error: unknown) => void,
      ) => {
        this.apiFitService
          .getMealTimes()
          .pipe(
            map((res) => {
              return {
                ...res,
                data: res.data.sort(this.sortMealTimesById),
              };
            }),
          )
          .subscribe({
            next: (res) => resolve(res.data),
            error: reject,
          });
      },
    );
  }


  async calculateCharts() {
    return new Promise(
      (
        resolve: (value: unknown) => void,
        reject: (reason: unknown) => void
      ) => {
        Promise.all(
          this.consumedMeals
            .filter((meal) => meal.id)
            .map((meal) => this.getMealData(meal.id)),
        ).then(mealsData => {
          const mealsNutrients = mealsData.map((meal) => meal.nutrients);
    
          let macroNutrients = {
            Calorias: {
              intake: 0,
              needIntake: this.nutrientsService.getDailyEnergyExpenditure(),
              unit: 'kcal',
            },
            Carboidrato: {
              intake: 0,
              needIntake: 0,
              unit: 'g',
            },
            Proteína: {
              intake: 0,
              needIntake: 0,
              unit: 'g',
            },
            Lipídeos: {
              intake: 0,
              needIntake: 0,
              unit: 'g',
            },
          };
      
          macroNutrients = mealsData.reduce((acc, meal) => {
            meal.foods.forEach((food) => {
              const kcal = isNaN(food.kcal) ? 0 : food.kcal;
              acc['Calorias'].intake += kcal;
            });
      
            meal.recipes.forEach((recipe) => {
              const kcal = isNaN(recipe.consumedKcal) ? 0 : recipe.consumedKcal;
              acc['Calorias'].intake += kcal;
            });
      
            return acc;
          }, macroNutrients);
      
          macroNutrients = mealsNutrients.reduce((acc, mealNutrients) => {
            const getNutrientQuantity = (
              quantity: QuantifiedNutrient['quantity'],
            ) => {
              const quantityToReturn = parseFloat(quantity.toString());
              return isNaN(quantityToReturn) ? 0 : quantityToReturn;
            };
      
            if (!mealNutrients) return acc;
      
            const carboIndex = mealNutrients.findIndex(
              (n) => n.name === 'Carboidrato',
            );
            if (carboIndex !== -1) {
              const carbo = mealNutrients[carboIndex];
              const recommendedIntake = carbo.intakes?.[
                'personal' as keyof NutrientIntake
              ] as Intake;
      
              acc['Carboidrato'].unit = recommendedIntake.unit;
              acc['Carboidrato'].needIntake = recommendedIntake.quantity ?? 0;
              acc['Carboidrato'].intake += getNutrientQuantity(carbo.quantity);
            }
      
            const protIndex = mealNutrients.findIndex((n) => n.name === 'Proteína');
            if (protIndex !== -1) {
              const prot = mealNutrients[protIndex];
              const recommendedIntake = prot.intakes?.[
                'personal' as keyof NutrientIntake
              ] as Intake;
      
              acc['Proteína'].unit = recommendedIntake.unit;
              acc['Proteína'].needIntake = recommendedIntake.quantity ?? 0;
              acc['Proteína'].intake += getNutrientQuantity(prot.quantity);
            }
      
            const lipIndex = mealNutrients.findIndex((n) => n.name === 'Lipídeos');
            if (lipIndex !== -1) {
              const lip = mealNutrients[lipIndex];
              const recommendedIntake = lip.intakes?.[
                'personal' as keyof NutrientIntake
              ] as Intake;
      
              acc['Lipídeos'].unit = recommendedIntake.unit;
              acc['Lipídeos'].needIntake = recommendedIntake.quantity ?? 0;
              acc['Lipídeos'].intake += getNutrientQuantity(lip.quantity);
            }
      
            return acc;
          }, macroNutrients);
      
          this.charts[0].title = `${macroNutrients['Calorias'].intake}${macroNutrients['Calorias'].unit}`;
          this.charts[0].percent = this.calculateChartPercentage(
            macroNutrients['Calorias'].intake,
            macroNutrients['Calorias'].needIntake,
          );
          
          this.charts[1].title = `${macroNutrients['Carboidrato'].intake}${macroNutrients['Carboidrato'].unit}`;
          this.charts[1].percent = this.calculateChartPercentage(
            macroNutrients['Carboidrato'].intake,
            macroNutrients['Carboidrato'].needIntake,
          );
      
          this.charts[2].title = `${macroNutrients['Proteína'].intake}${macroNutrients['Proteína'].unit}`;
          this.charts[2].percent = this.calculateChartPercentage(
            macroNutrients['Proteína'].intake,
            macroNutrients['Proteína'].needIntake,
          );
      
          this.charts[3].title = `${macroNutrients['Lipídeos'].intake}${macroNutrients['Lipídeos'].unit}`;
          this.charts[3].percent = this.calculateChartPercentage(
            macroNutrients['Lipídeos'].intake,
            macroNutrients['Lipídeos'].needIntake,
          );

          resolve(true);
        }).catch(reject);
      }
    );
  }

  calculateChartPercentage(intake: number, needIntake: number): number {
    const percentage = Math.round((intake * 100) / needIntake);
    if (isNaN(percentage)) return 0;

    return percentage > 100 ? 100 : percentage;
  }

  getMealData(mealId: number) {
    return new Promise(
      (resolve: (meal: Meal) => void, reject: (error: unknown) => void) => {
        this.apiFitService.getMealById(mealId).subscribe({
          next: (r) => resolve(r.data),
          error: reject,
        });
      },
    );
  }

  async setDailyMeals() {
    return new Promise(
      (
        resolve: (success: boolean) => void,
        reject: (err: unknown) => void
      ) => {
        this.userService.fetchMealsByWeek(this.mealDate)
        .then(mealsByWeek => {
          this.mealsByWeek = mealsByWeek;

          const meals = this.mealsByWeek.find(
            (mealsByDate) =>
              mealsByDate.date.split('T')[0] ===
              this.mealDate.toISOString().split('T')[0],
          )?.meals;
      
          this.consumedMeals = this.mealTimes.map((time) => {
            const meal = meals?.find((meal) => meal.mealTime === time.name);
      
            if (!meal) {
              return {
                id: 0,
                mealTime: time.name,
                mealDate: this.mealDate,
                foods: [],
                recipes: [],
                nutrients: [],
              };
            }
      
            return meal;
          });
      
          resolve(true);
        }).catch(reject);
      }
    );
  }
}
