import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { CreateAchievementDto } from './dto/create-achievement.dto';
import { UpdateAchievementDto } from './dto/update-achievement.dto';
import { AchievementRepository } from './achievements.repository';
import { InjectRepository } from '@nestjs/typeorm';
import { Achievement } from './entities/achievement.entity';
import { Between, DataSource, Repository } from 'typeorm';
import isValidUuidV4 from 'src/common/uuid validator/uuid-validate';
import { lan } from 'src/lan';
import { isEmpty } from 'src/common/helper';
import { CreateUserAchievementDto } from './dto/create-user-achievement.dto';
import { UserAchievement } from './entities/user-achievement.entity';
import { AppUserRepository } from 'src/app_users/app_users.repository';
import { StreakRepository } from 'src/streaks/streaks.repository';
import { UserStreak } from 'src/streaks/entities/user-streak.entity';

@Injectable()
export class AchievementsService {
  constructor(
    @InjectRepository(Achievement)
    private readonly achievementEntity: Repository<Achievement>,
    private readonly achievementRepository: AchievementRepository,
    @InjectRepository(UserAchievement)
    private readonly userAchievementEntity: Repository<UserAchievement>,
    private readonly appUserRepository: AppUserRepository,
    private readonly streakRepository: StreakRepository,
    @InjectRepository(UserStreak)
    private readonly userStreakEntity: Repository<UserStreak>,
    private readonly connection: DataSource,
  ) {}

  async create(createAchievementDto: CreateAchievementDto) {
    if (await this.achievementRepository.isUnique(createAchievementDto.name)) {
      throw new BadRequestException(lan('data_already_exists'));
    }
    const achievement = new Achievement();
    Object.assign(achievement, createAchievementDto);

    const data = await this.achievementEntity.save(achievement);

    return data;
  }

  async findAll(
    take: number,
    skip: number,
    key: string,
    order: string,
    headers: any,
  ) {
    return this.achievementRepository.getAllAchievements(
      take,
      skip,
      key,
      order,
      headers,
    );
  }

  async findAllUserAchievement(
    take: number,
    skip: number,
    userId: string,
    date: string,
  ) {
    if (userId) {
      const data = await this.appUserRepository.findUserId(userId);
      if (isEmpty(data)) {
        throw new BadRequestException(lan('common.data_not_found'));
      }
    }
    return await this.achievementRepository.findAllUserAchievement(
      take,
      skip,
      userId,
      date,
    );
  }

  async findAllAchievement(
    take: number,
    skip: number,
    key: string,
    order: string,
    search: string,
    recent: boolean,
    province: string,
  ) {
    return await this.achievementRepository.findAllAchievement(
      take,
      skip,
      key,
      order,
      search,
      recent,
      province,
    );
  }

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

    const data = await this.achievementRepository.findById(id);

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

  async update(id: string, updateAchievementDto: UpdateAchievementDto) {
    const data = await this.achievementRepository.findById(id);

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

    const achievement = new Achievement();
    Object.assign(achievement, updateAchievementDto);

    const updatedAchievement = await this.achievementEntity.update(
      id,
      achievement,
    );

    return updatedAchievement;
  }

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

    const achievement = await this.achievementRepository.findById(id);

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

    await this.achievementEntity.softDelete(id);
  }

  async checkAndAddUserAchievement(
    createUserAchievementDto: CreateUserAchievementDto,
  ) {
    const { user_id, steps } = createUserAchievementDto;

    const queryRunner = this.connection.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const user = await this.appUserRepository.findOne(user_id);
      if (!user) {
        throw new NotFoundException('User not found');
      }

      const achievements: any =
        await this.achievementRepository.getAllAchievements();

      for (const achievement of achievements?.data) {
        if (Number(steps) >= Number(achievement.name)) {
          const existingUserAchievement = await queryRunner.manager.findOne(
            UserAchievement,
            {
              where: {
                user_id: user_id,
                achievement_id: achievement?.id,
              },
            },
          );

          if (!existingUserAchievement) {
            const userAchievement = queryRunner.manager.create(
              UserAchievement,
              {
                user_id: user_id,
                achievement_id: achievement?.id,
                achievement_goal: achievement?.name,
                status: 1,
              },
            );

            await queryRunner.manager.save(UserAchievement, userAchievement);
          }
        }
      }

      const streaks: any = await this.streakRepository.getAllStreaks();
      for (const streak of streaks?.data) {
        if (parseInt(user.current_streak) >= parseInt(streak.streak_count)) {
          const existingUserStreak = await queryRunner.manager.findOne(
            UserStreak,
            {
              where: {
                app_user_id: user_id,
                streak_id: streak.id,
              },
            },
          );

          if (!existingUserStreak) {
            const userStreak = queryRunner.manager.create(UserStreak, {
              app_user_id: user_id,
              streak_id: streak.id,
              streak_count: streak.streak_count,
            });

            await queryRunner.manager.save(UserStreak, userStreak);
          }
        }
      }

      await queryRunner.commitTransaction();
    } catch (error) {
      await queryRunner.rollbackTransaction();
      throw error;
    } finally {
      await queryRunner.release();
    }
  }
}
