import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { Trip } from '@/entities/trip.entity';
import { Group } from '@/entities/group.entity';
import { GroupRole } from '@/entities/group-member.entity';
import { NotificationHelper } from '@/helpers/notification.helper';
import { NotificationService } from '@/notification/notification.service';
import { User } from '@/entities/user.entity';
import { ExpenseStatus } from '@/entities/expense.entity';
import { TripClosingMailService } from './trip-closing-mail.service';

@Injectable()
export class TripService {
  constructor(
    @InjectRepository(Trip)
    private tripRepo: Repository<Trip>,

    @InjectRepository(Group)
    private groupRepo: Repository<Group>,

    @InjectRepository(User)
    private userRepository: Repository<User>,

    private notificationService: NotificationService,
    private readonly tripClosingMailService: TripClosingMailService,

    private dataSource: DataSource,
  ) {}

  async create(userId: string, dto: any) {
    // ensure group exists
    const group = await this.groupRepo
      .createQueryBuilder('g')
      .leftJoinAndSelect('g.members', 'gm')
      .leftJoin('gm.user', 'user')
      .addSelect(['user.id', 'user.name', 'user.avatar'])
      .leftJoinAndSelect('g.trips', 'trip')
      .where('g.id = :groupId', { groupId: dto.groupId })
      .getOne();
    if (!group) throw new NotFoundException('Group not found');

    const trip = this.tripRepo.create({
      name: dto.name,
      infor: dto.infor,
      location: dto.location,
      startDate: dto.startDate,
      endDate: dto.endDate,
      group: { id: dto.groupId } as any,
    } as Trip);

    const savedTrip = await this.tripRepo.save(trip);

    const user = await this.userRepository.findOne({ where: { id: userId } });

    if (group && user) {
      const payload: any[] = [];

      group.members.forEach((m) => {
        if (m.user.id !== userId) {
          const notification = NotificationHelper.newTrip({
            trip: savedTrip,
            group,
            user: m.user,
            createdBy: user,
          });
          payload.push(notification);
        }
      });
      await this.notificationService.createManyAndPush(group.id, payload);
    }

    return savedTrip;
  }

  async findOne(id: string, req?: any) {
    const trip = await this.tripRepo
      .createQueryBuilder('trip')
      .leftJoinAndSelect('trip.group', 'group')
      .leftJoinAndSelect('group.members', 'member')
      .leftJoinAndSelect('member.user', 'user')
      .leftJoinAndSelect('trip.maps', 'map')
      .where('trip.id = :id', { id })
      .getOne();

    if (!trip) throw new NotFoundException('Trip not found');

    return {
      ...trip,
      group: {
        id: trip.group.id,
        name: trip.group.name,
        description: trip.group.description,
        members: trip.group.members.map((m) => ({
          id: m.user.id,
          name: m.user.name,
          avatar: m.user.avatar,
          phone: m.user.phone,
          bank: m.user.bank,
          bankAccNumber: m.user.bankAccNumber,
          role: m.role,
        })),
      },
      maps: trip.maps ? trip.maps.map((map) => ({
        id: map.id,
        name: map.name,
        active: map.active
      })) : [],
      isLeader: req?.isLeader,
    };
  }

  async findAll(filter?: { groupId?: string }) {
    const qb = this.tripRepo
      .createQueryBuilder('t')
      .leftJoinAndSelect('t.group', 'g');
    if (filter?.groupId)
      qb.where('t.groupId = :groupId', { groupId: filter.groupId });
    return qb.getMany();
  }

  async update(id: string, userId: string, dto: any) {
    const trip = await this.tripRepo.findOne({
      where: { id },
      relations: ['group'],
    });
    if (!trip) throw new NotFoundException('Trip not found');
    await this.tripRepo.update(id, dto as any);
    return this.findOne(id);
  }

  async remove(id: string, userId: string) {
    const trip = await this.tripRepo.findOne({
      where: { id },
      relations: ['group'],
    });
    if (!trip) throw new NotFoundException('Chuyến đi không tồn tại');
    if (!trip.isCloseTrip)
      throw new BadRequestException('Vui lòng kết thúc chuyến đi để xóa');
    await this.tripRepo.softDelete(id);
    return { message: 'Trip deleted' };
  }

  async close(userId: string, id: string) {
    const trip = await this.dataSource.transaction(async (manager) => {
      const tripRepo = manager.getRepository(Trip);
      const trip = await tripRepo.findOne({
        where: { id },
        relations: [
          'group',
          'group.members',
          'group.members.user',
          'expenses',
          'expenses.paidBy',
          'expenses.participants',
          'expenses.participants.user',
        ],
      });

      if (!trip) {
        throw new NotFoundException('Chuyến đi không tồn tại');
      }

      if (trip.isCloseTrip) {
        throw new BadRequestException('Chuyến đi đã được kết thúc');
      }

      trip.expenses = (trip.expenses ?? []).filter(
        (expense) => expense.status === ExpenseStatus.APPROVED,
      );
      trip.isCloseTrip = true;

      await tripRepo.save(trip);

      if (trip.group) {
        const payload: any[] = [];

        trip.group.members.forEach((m) => {
          if (m.user.id !== userId) {
            const notification = NotificationHelper.closeTrip({
              trip,
              group: trip.group,
              user: m.user,
              createdBy: userId,
            });
            payload.push(notification);
          }
        });

        await this.notificationService.createManyAndPush(
          trip.group.id,
          payload,
        );
      }

      return trip;
    });

    const email = await this.tripClosingMailService.send(trip);
    return { message: 'Kết thúc chuyến đi thành công', email };
  }

  async findAllByUser(userId: string) {
    const qb = await this.tripRepo
      .createQueryBuilder('trip')
      .leftJoinAndSelect('trip.group', 'group')
      .leftJoinAndSelect('trip.expenses', 'expenses')
      .leftJoinAndSelect('group.members', 'member')
      .leftJoinAndSelect('member.user', 'user')
      .where((qb) => {
        const subQuery = qb
          .subQuery()
          .select('1')
          .from('group_members', 'm')
          .where('m.groupId = group.id')
          .andWhere('m.userId = :userId', { userId })
          .getQuery();
        return 'EXISTS ' + subQuery;
      })
      .andWhere('trip.deletedAt IS NULL')
      .getMany();

    const data = qb.map((item) => {
      return {
        id: item.id,
        name: item.name,
        group: { id: item.group.id, name: item.group.name },
        startDate: item.startDate,
        endDate: item.endDate,
        location: item.location,
        isCloseTrip: item.isCloseTrip,
        isLeader: item.group.members.find((item) => item.role === 'owner')
          ? true
          : false,
        members: item.group.members.map((member) => {
          return {
            id: member.id,
            user: {
              id: member.user.id,
              name: member.user.name,
              avatar: member.user.avatar,
            },
          };
        }),
        expenses: item.expenses.filter(
          (item) => item.status === ExpenseStatus.APPROVED,
        ),
      };
    });

    return data;
  }
}
