import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { CreateAppUsersStepDto } from './dto/create-app_users_step.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { AppUsersStep } from './entities/app_users_step.entity';
import { AppUserStepsRepository } from './app_users_steps.repository';
import { EntityManager, Repository } from 'typeorm';
import isValidUuidV4 from 'src/common/uuid validator/uuid-validate';
import { lan } from 'src/lan';
import { isEmpty } from 'src/common/helper';
import { AppUserRepository } from 'src/app_users/app_users.repository';
import * as moment from 'moment';
import { MultipleAppUsersStepDto } from './dto/multiple-app_users_step.dto';
import { AppUser } from 'src/app_users/entities/app_user.entity';

@Injectable()
export class AppUsersStepsService {
  constructor(
    @InjectRepository(AppUsersStep)
    private readonly userStepsEntity: Repository<AppUsersStep>,
    private readonly userStepsRepository: AppUserStepsRepository,
    private readonly userRepository: AppUserRepository,
    @InjectRepository(AppUser)
    private readonly userEntity: Repository<AppUser>,
  ) {}

  async create(
    createAppUsersStepDto: CreateAppUsersStepDto,
    manager: EntityManager,
  ) {
    if (!isEmpty(createAppUsersStepDto.app_user_id)) {
      const id = createAppUsersStepDto.app_user_id;

      if (!isValidUuidV4(id)) {
        throw new BadRequestException(lan('common.invalid_uuid_format'));
      }

      const user = await this.userRepository.findOne(id);

      if (isEmpty(user)) {
        throw new NotFoundException(lan('login.user_not_found'));
      }

      if (!isEmpty(createAppUsersStepDto.steps)) {
        const createAppUserSteps = new AppUsersStep();
        Object.assign(createAppUserSteps, createAppUsersStepDto);
        const returnData = await manager.save(createAppUserSteps);

        return returnData;
      }
    } else {
      throw new NotFoundException(lan('user.steps.user_id_required'));
    }
  }

  async updateTodayStreak(id: string) {
    const user = await this.userRepository.findOne(id);
    const goalValue = await this.userRepository.getUsersGoals();
    const latestGoal = goalValue.goal;

    const todaysSteps = await this.userStepsRepository.findUserStepsById(
      id,
      new Date().toUTCString(),
      true,
    );

    const groupedData = todaysSteps.reduce((stepsAccumulator, curr) => {
      const date = moment(curr.created_at).utc().format('YYYY-MM-DD'); // Format date to UTC

      // If this date is not yet in the accumulator, add it with the current steps
      if (!stepsAccumulator[date]) {
        stepsAccumulator[date] = Number(curr.steps);
      } else {
        // If this date is already in the accumulator, add the current steps to the existing steps
        stepsAccumulator[date] += Number(curr.steps);
      }

      return stepsAccumulator;
    }, {});

    const formattedData = Object.entries(groupedData).map(([date, steps]) => ({
      date: new Date(date),
      steps,
    }));

    const today = moment.utc().format('YYYY-MM-DD');
    const streakUpdatedDate = moment(user.streak_updated_at).format(
      'YYYY-MM-DD',
    );

    formattedData &&
      formattedData.map((data) => {
        if (data.steps >= latestGoal && streakUpdatedDate !== today) {
          user.current_streak = (
            (parseInt(user.current_streak) || 0) + 1
          ).toString();

          user.streak_updated_at = moment
            .utc(data.date)
            .format('YYYY-MM-DD HH:mm:ss.SSSSSS');

          if (
            parseInt(user.current_streak) > parseInt(user.best_streak) ||
            isEmpty(user.best_streak)
          ) {
            user.best_streak = user.current_streak;
          }
        }
      });

    await this.userEntity.save(user);
    return formattedData;
  }

  async createMultipleUserSteps(
    createAppUsersStepDto: MultipleAppUsersStepDto,
    manager: EntityManager,
  ) {
    if (!isEmpty(createAppUsersStepDto.app_user_id)) {
      const id = createAppUsersStepDto.app_user_id;

      if (!isValidUuidV4(id)) {
        throw new BadRequestException(lan('common.invalid_uuid_format'));
      }

      const user = await this.userRepository.findOne(id);

      if (isEmpty(user)) {
        throw new NotFoundException(lan('login.user_not_found'));
      }

      if (isEmpty(createAppUsersStepDto.user_steps)) {
        throw new NotFoundException(lan('user_steps.not_empty'));
      }

      for (const stepDto of createAppUsersStepDto.user_steps) {
        const { date, steps } = stepDto;

        // Create a new AppUsersStep entity for each step
        const createAppUserStep = new AppUsersStep();
        createAppUserStep.app_user_id = id;
        createAppUserStep.created_at = date;
        createAppUserStep.steps = steps;

        // Save the created step entity
        await this.userStepsEntity.save(createAppUserStep);
      }
    } else {
      throw new NotFoundException(lan('user.steps.user_id_required'));
    }
  }

  async updateMultipleStreaks(id: string) {
    const user = await this.userRepository.findOne(id);
    const lastStreakUpdated = user.streak_updated_at;

    const data = await this.userStepsRepository.findUserStepsById(
      id,
      lastStreakUpdated,
    );

    const goalValue = await this.userRepository.getUsersGoals();
    const latestGoal = goalValue.goal;

    const groupedData = data.reduce((stepsAccumulator, curr) => {
      const date = moment(curr.created_at).utc().format('YYYY-MM-DD'); // Format date to UTC

      // If this date is not yet in the accumulator, add it with the current steps
      if (!stepsAccumulator[date]) {
        stepsAccumulator[date] = Number(curr.steps);
      } else {
        // If this date is already in the accumulator, add the current steps to the existing steps
        stepsAccumulator[date] += Number(curr.steps);
      }

      return stepsAccumulator;
    }, {});

    // Convert groupedData into an array of objects
    const formattedData = Object.entries(groupedData).map(([date, steps]) => ({
      date: new Date(date),
      steps,
    }));

    formattedData &&
      formattedData.map((data) => {
        if (data.steps >= latestGoal) {
          user.current_streak = (
            (parseInt(user.current_streak) || 0) + 1
          ).toString();

          user.streak_updated_at = moment
            .utc(data.date)
            .format('YYYY-MM-DD HH:mm:ss.SSSSSS');

          if (
            parseInt(user.current_streak) > parseInt(user.best_streak) ||
            isEmpty(user.best_streak)
          ) {
            user.best_streak = user.current_streak;
          }
        } else {
          user.current_streak = '0';
        }
      });

    await this.userEntity.save(user);
    return formattedData;
  }

  async findAll(take: number, skip: number) {
    return await this.userStepsRepository.findAll(take, skip);
  }

  async findOne(id: string) {
    if (!isValidUuidV4(id)) {
      throw new BadRequestException(lan('common.invalid_uuid_format'));
    }

    const userSteps = await this.userStepsRepository.findOne(id);

    if (isEmpty(userSteps)) {
      throw new NotFoundException(lan('common.data_not_found'));
    }

    return userSteps;
  }

  async findHighestStepsWithDate(id: string) {
    const userSteps = await this.userStepsRepository.findUserStepsById(id);
    const goalValue = await this.userRepository.getUsersGoals();
    const dailyGoal = parseInt(goalValue.goal);
    const best_streak = await this.userRepository.findOne(id);

    if (isEmpty(userSteps) || userSteps.length === 0) {
      throw new NotFoundException(lan('user_steps.not_found'));
    }

    if (isEmpty(best_streak)) {
      throw new NotFoundException(lan('common.not_found'));
    }

    // Calculate total steps for each date
    const totalStepsByDate: Record<string, number> = userSteps.reduce(
      (accumulator, step) => {
        const date = moment(step.created_at).format('YYYY-MM-DD');
        accumulator[date] = (accumulator[date] || 0) + parseInt(step.steps);
        return accumulator;
      },
      {},
    );

    // Find the date with the highest total steps
    let highestStepsDate = null;
    let highestSteps = -1;

    Object.entries(totalStepsByDate).forEach(([date, totalSteps]) => {
      if (typeof totalSteps === 'number' && totalSteps > highestSteps) {
        highestSteps = totalSteps;
        highestStepsDate = date;
      }
    });

    // Calculate highest count for week

    const weekGroups = userSteps.reduce((stepsAccumulator, step) => {
      const week = moment(step.created_at).isoWeek();
      const year = moment(step.created_at).isoWeekYear();
      const weekKey = `${year}-W${week}`;
      stepsAccumulator[weekKey] =
        (stepsAccumulator[weekKey] || 0) + parseInt(step.steps);
      return stepsAccumulator;
    }, {});

    const highestWeek = Object.entries(weekGroups).reduce(
      (
        maxWeek: {
          total_step: number;
          week_start: string;
          day_total: Record<string, number>;
        },
        [weekKey, totalSteps],
      ) => {
        const [year, week] = weekKey.split('-W');
        const weekNumber = parseInt(week);
        const yearNumber = parseInt(year);
        if (
          typeof totalSteps === 'number' &&
          totalSteps > maxWeek.total_step &&
          weekNumber > 0
        ) {
          // Calculate the start of the week using the year and week number
          const weekStart = moment()
            .isoWeekYear(yearNumber)
            .isoWeek(weekNumber)
            .startOf('isoWeek');
          const dayTotals: Record<string, number> = {};
          let weekTotalSteps = 0;
          for (let i = 0; i < 7; i++) {
            const day = weekStart.clone().add(i, 'days').format('YYYY-MM-DD');
            const daySteps = totalStepsByDate[day] || 0;
            dayTotals[day] = daySteps;
            weekTotalSteps += daySteps;
          }
          return {
            total_step: weekTotalSteps,
            day_total: dayTotals,
          };
        } else {
          return maxWeek;
        }
      },
      {
        total_step: -1,
        day_total: {},
      } as {
        total_step: number;
        day_total: Record<string, number>;
      },
    );

    // Calculate last week and current week data
    const lastWeek = [];
    const currentWeek = [];
    const today = moment().startOf('day');
    const lastMonday = today.clone().subtract(1, 'weeks').startOf('isoWeek');
    const currentMonday = today.clone().startOf('isoWeek');

    let currentWeekTotal = 0;
    let lastWeekTotal = 0;

    for (let i = 0; i < 7; i++) {
      const lastWeekDate = lastMonday.clone().add(i, 'days');
      const currentWeekDate = currentMonday.clone().add(i, 'days');
      const formattedLastWeekDate = lastWeekDate.format('YYYY-MM-DD');
      const formattedCurrentWeekDate = currentWeekDate.format('YYYY-MM-DD');
      const lastWeekSteps = totalStepsByDate[formattedLastWeekDate] || 0;
      const currentWeekSteps = totalStepsByDate[formattedCurrentWeekDate] || 0;

      lastWeek.push({ date: formattedLastWeekDate, steps: lastWeekSteps });
      currentWeek.push({
        date: formattedCurrentWeekDate,
        steps: currentWeekSteps,
        day: currentWeekDate.format('dddd'),
      });

      lastWeekTotal += lastWeekSteps;
      currentWeekTotal += currentWeekSteps;
    }

    return {
      best_day: { highest_step: highestSteps, date: highestStepsDate },
      best_week: highestWeek,
      last_week: lastWeek,
      current_week: currentWeek,
      last_week_total: lastWeekTotal,
      current_week_total: currentWeekTotal,
      daily_goal: dailyGoal,
      best_streak: parseInt(best_streak.best_streak),
    };
  }

  async findLastRecord(id: string) {
    if (!isValidUuidV4(id)) {
      throw new BadRequestException(lan('common.invalid_uuid_format'));
    }

    const userSteps = await this.userStepsRepository.findLastRecord(id);

    if (isEmpty(userSteps)) {
      throw new NotFoundException(lan('common.data_not_found'));
    }

    return userSteps;
  }

  async findUserById(id: string) {
    if (!isValidUuidV4(id)) {
      throw new BadRequestException(lan('common.invalid_uuid_format'));
    }

    const data = await this.userStepsRepository.findUserById(id);
    let totalSteps = 0;

    if (data && data.user_steps) {
      totalSteps = data.user_steps.reduce(
        (total, step) => total + parseInt(step.steps),
        0,
      );
    }

    // Calculate steps for today, yesterday, week, and month
    let todaySteps = 0;
    let yesterdaySteps = 0;
    let weekSteps = 0;
    let monthSteps = 0;

    const today = moment().startOf('day');
    const yesterday = moment().subtract(1, 'day').startOf('day');
    const startOfWeek = moment().startOf('week');
    const startOfMonth = moment().startOf('month');

    if (data && data.user_steps) {
      data.user_steps.forEach((step) => {
        const stepDate = moment(step.created_at);
        if (stepDate.isSame(today, 'day')) {
          todaySteps += parseInt(step.steps);
        }
        if (stepDate.isSame(yesterday, 'day')) {
          yesterdaySteps += parseInt(step.steps);
        }
        if (stepDate.isSameOrAfter(startOfWeek)) {
          weekSteps += parseInt(step.steps);
        }
        if (stepDate.isSameOrAfter(startOfMonth)) {
          monthSteps += parseInt(step.steps);
        }
      });
    }

    // Add total steps and steps for today, yesterday, week, and month to the response data
    if (data) {
      data.total_steps = totalSteps;
      data.steps_today = todaySteps;
      data.steps_yesterday = yesterdaySteps;
      data.steps_week = weekSteps;
      data.steps_month = monthSteps;

      // Exclude some details from array
      delete data.user_steps;
      delete data.updated_at;
      delete data.deleted_at;
      delete data.password;
    }

    return data;
  }
}
