import { InjectRepository } from '@nestjs/typeorm';
import * as moment from 'moment';
import { AppUsersStep } from 'src/app_users_steps/entities/app_users_step.entity';
import { getCompleteUrl, isEmpty } from 'src/common/helper';
import { Brackets, Repository } from 'typeorm';
import { Group } from './entities/group.entity';
import { AppUser } from 'src/app_users/entities/app_user.entity';

export class GroupsRepository {
  constructor(
    @InjectRepository(Group)
    private readonly groupsRepository: Repository<Group>,
    @InjectRepository(AppUser)
    private readonly userRepository: Repository<AppUser>,
    @InjectRepository(AppUsersStep)
    private readonly appUserRepository: Repository<AppUsersStep>,
  ) {}

  async findAll(take: number, skip: number, search: string, headers: any) {
    let groupsObj = this.groupsRepository.createQueryBuilder('group');

    const currentDate = moment().format('YYYY-MM-DD');

    if (headers && headers['device-type'] === 'mobile') {
      groupsObj = groupsObj
        .andWhere('DATE(group.goal_end_date) > :currentDate', { currentDate })
        .andWhere('group.status = :status', { status: 1 });
    }

    if (search) {
      groupsObj = groupsObj.andWhere(
        new Brackets((qb) => {
          qb.where('group.name ILIKE :search', {
            search: `%${search}%`,
          }).orWhere('group.description ILIKE :search', {
            search: `%${search}%`,
          });
        }),
      );
    }

    const [result, count] = await this.groupsRepository
      .createQueryBuilder('group')
      .select('group')
      .addSelect(['owner.id', 'owner.name', 'owner.profile_picture'])
      .addSelect(['province.id', 'province.name'])
      .leftJoin('group.group_owner', 'owner')
      .leftJoin('group.province', 'province')
      .leftJoinAndSelect('group.user_groups', 'user_groups')
      .leftJoinAndSelect('group.news', 'news', 'news.status = :newsStatus', {
        newsStatus: 1,
      })
      .leftJoinAndSelect('news.news_media', 'news_media')
      .orderBy('group.created_at', 'DESC')
      .skip(skip)
      .take(take)
      .getManyAndCount();

    const data = await Promise.all(
      result.map(async (group) => {
        const userIdWithJoinDate = await this.getAppUsersWithCreatedAt(
          group.id,
        );
        let totalSteps: number = 0;

        if (!isEmpty(userIdWithJoinDate)) {
          const stepsPromises = userIdWithJoinDate.map(async (value) => {
            const data = await this.findUserSteps(value.userId);
            if (data) {
              const userJoinDate = moment(value.joinDate);
              data.forEach((step: AppUsersStep) => {
                const stepDate = moment(step.created_at);
                if (stepDate >= userJoinDate) {
                  totalSteps += parseInt(step.steps);
                }
              });
            }
          });
          await Promise.all(stepsPromises);
        }

        return {
          ...group,
          total_steps: totalSteps,
          user_groups:
            group.user_groups &&
            group.user_groups.map((group) => ({
              ...group,
              profile_picture: getCompleteUrl(group.profile_picture),
            })),
          icon: getCompleteUrl(group.icon),
          news: group.news.map((newsItem) => ({
            ...newsItem,
            news_media: newsItem.news_media.map((media) => ({
              ...media,
              media: getCompleteUrl(media.media),
            })),
          })),
          group_owner: {
            ...group.group_owner,
            profile_picture: getCompleteUrl(
              group?.group_owner?.profile_picture,
            ),
          },
        };
      }),
    );

    const response = {
      count,
      data,
    };

    return response;
  }

  async findAllExploreGroup(search?: string) {
    let query = this.groupsRepository.createQueryBuilder('group');

    if (search) {
      query = query.andWhere(
        new Brackets((qb) => {
          qb.where('group.name ILIKE :search', {
            search: `%${search}%`,
          }).orWhere('group.description ILIKE :search', {
            search: `%${search}%`,
          });
        }),
      );
    }

    const currentDate = moment().format('YYYY-MM-DD');
    query = query
      .andWhere('DATE(group.goal_end_date) > :currentDate', { currentDate })
      .andWhere('group.status = :status', { status: 1 });

    const result = await query
      .addSelect(['owner.id', 'owner.name', 'owner.profile_picture'])
      .addSelect(['province.id', 'province.name'])
      .leftJoin('group.group_owner', 'owner')
      .leftJoin('group.province', 'province')
      .orderBy('group.created_at', 'DESC')
      .leftJoinAndSelect('group.user_groups', 'user_groups')
      .leftJoinAndSelect('user_groups.user_groups', 'users')
      .getMany();

    const exploreGroups = await Promise.all(
      result.map(async (group) => {
        const userIdWithJoinDate = await this.getAppUsersWithCreatedAt(
          group.id,
        );
        let totalSteps: number = 0;

        if (!isEmpty(userIdWithJoinDate)) {
          const stepsPromises = userIdWithJoinDate.map(async (value) => {
            const data = await this.findUserSteps(value.userId);
            if (data) {
              const userJoinDate = moment(value.joinDate).utc(true).valueOf();
              data.forEach((step: AppUsersStep) => {
                const stepDate = moment(step.created_at).utc(true).valueOf();
                if (stepDate >= userJoinDate) {
                  totalSteps += parseInt(step.steps);
                }
              });
            }
          });
          await Promise.all(stepsPromises);
        }

        return {
          ...group,
          total_steps: totalSteps,
          user_groups:
            group.user_groups &&
            group.user_groups.map((group) => ({
              ...group,
              profile_picture: getCompleteUrl(group.profile_picture),
            })),
          group_owner: {
            ...group.group_owner,
            profile_picture: getCompleteUrl(
              group?.group_owner?.profile_picture,
            ),
          },
        };
      }),
    );

    return exploreGroups;
  }

  async findOne(id: string, url: boolean) {
    const data = await this.groupsRepository.findOne({
      where: {
        id,
      },
      relations: [
        'user_groups',
        'news',
        'news.news_media',
        'group_owner',
        'province',
      ],
    });

    if (data) {
      const groupIcon = getCompleteUrl(data.icon);

      const userDetails = await Promise.all(
        data.user_groups.map(async (group) => {
          const userIdWithJoinDate = await this.getAppUsersGroup(
            data.id,
            group.id,
          );

          let totalSteps: number = 0;

          if (!isEmpty(userIdWithJoinDate)) {
            const stepsPromises = userIdWithJoinDate.map(async (value) => {
              const data = await this.findUserSteps(value.userId);

              if (data) {
                const userJoinDate = moment(value.joinDate).utc(true).valueOf();
                data.forEach((step: AppUsersStep) => {
                  const stepDate = moment(step.created_at).utc(true).valueOf();

                  if (stepDate >= userJoinDate) {
                    totalSteps += parseInt(step.steps);
                  }
                });
              }
            });

            await Promise.all(stepsPromises);
          }

          return {
            id: group.id,
            name: group.name,
            profile_picture: group.profile_picture,
            total_steps: totalSteps,
          };
        }),
      );

      const newsMedia = data.news
        .filter((newsItem) => newsItem.status === 1)
        .map((newsItem) => ({
          ...newsItem,
          news_media: newsItem.news_media.map((media) => ({
            ...media,
            media: `${process.env.DOMAIN}${media.media}`,
          })),
        }));

      const userIdWithJoinDate = await this.getAppUsersWithCreatedAt(data.id);
      let totalSteps: number = 0;

      if (isEmpty(userIdWithJoinDate)) {
        // Handle the case where there are no users in the group
        totalSteps = 0;
      } else {
        // Map userIdWithJoinDate to an array of promises
        const stepsPromises = userIdWithJoinDate.map(async (value) => {
          const data = await this.findUserSteps(value.userId);
          if (data) {
            const userJoinDate = moment(value.joinDate).utc(true).valueOf();
            data.forEach((step: AppUsersStep) => {
              const stepDate = moment(step.created_at).utc(true).valueOf();
              if (stepDate >= userJoinDate) {
                totalSteps += parseInt(step.steps);
              }
            });
          }
        });
        // Wait for all promises to resolve
        await Promise.all(stepsPromises);
      }

      const response = {
        ...data,
        icon: groupIcon,
        user_groups: userDetails,
        news: newsMedia,
        total_steps: totalSteps,
      };

      return response;
    }
  }

  async findGroupUser(
    groupId: string,
    take: number,
    skip: number,
    search: string,
  ) {
    const usersQuery = this.userRepository
      .createQueryBuilder('user')
      .innerJoin('user.user_groups', 'group', 'group.id = :groupId', {
        groupId,
      })
      .leftJoinAndSelect('user.province', 'province')
      .select([
        'user.id',
        'user.name',
        'user.email',
        'user.profile_picture',
        'user.current_streak',
        'user.best_streak',
        'user.province_id',
        'user.whatsapp_no',
        'user.zipcode',
        'province.id',
        'province.name',
      ])
      .take(take)
      .skip(skip);

    if (search) {
      usersQuery.andWhere(
        new Brackets((qb) => {
          qb.where('user.name ILIKE :search', {
            search: `%${search}%`,
          }).orWhere('user.email ILIKE :search', { search: `%${search}%` });
        }),
      );
    }

    const [users, count] = await usersQuery.getManyAndCount();

    const formattedUsers = users.map((user) => ({
      id: user.id,
      name: user?.name,
      email: user?.email,
      profile_picture: getCompleteUrl(user?.profile_picture),
      current_streak: user?.current_streak,
      best_streak: user?.best_streak,
      zipcode: user?.zipcode,
      whatsapp_no: user?.whatsapp_no,
      province: {
        id: user.province?.id,
        name: user.province?.name,
      },
    }));

    return {
      count,
      data: formattedUsers,
    };
  }

  async findUserSteps(id: string) {
    const data = await this.appUserRepository.find({
      where: { app_user_id: id },
    });

    return data;
  }

  async findOneById(id: string) {
    const data = await this.groupsRepository.findOne({
      where: {
        id,
      },
    });

    const response = {
      ...data,
    };

    return response;
  }

  //this function will return all the users for specific group with their join date
  async getAppUsersWithCreatedAt(
    groupId: string,
  ): Promise<{ userId: string; joinDate: Date }[]> {
    const query = `
    SELECT user_groups.app_user_id, user_groups.created_at
    FROM user_groups
    WHERE user_groups.group_id = $1
  `;
    const results = await this.groupsRepository.query(query, [groupId]);

    return results.map((result) => ({
      userId: result.app_user_id,
      joinDate: new Date(result.created_at),
    }));
  }

  //this function will return the single user for specific group with his join date
  async getAppUsersGroup(
    groupId: string,
    userId: string,
  ): Promise<{ userId: string; joinDate: Date }[]> {
    const query = `
    SELECT user_groups.app_user_id, user_groups.created_at
    FROM user_groups
    WHERE user_groups.group_id = $1 and user_groups.app_user_id = $2
  `;

    const results = await this.groupsRepository.query(query, [groupId, userId]);

    return results.map((result) => ({
      userId: result.app_user_id,
      joinDate: new Date(result.created_at),
    }));
  }
}
