import { GroupMember, GroupRole } from '@/entities/group-member.entity';
import {
  Injectable,
  BadRequestException,
  NotFoundException,
} from '@nestjs/common';
import { UsersService } from '@/users/users.service';
import { User } from '@/entities/user.entity';
import { UpdateGroupDto } from './dto/update-group.dto';
import { CreateGroupDto } from './dto/create-group.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Group } from '@/entities/group.entity';
import { NotificationService } from '@/notification/notification.service';
import { NotificationHelper } from '@/helpers/notification.helper';
import { NotificationGateway } from '@/notification/notification.gateway';
import { Expense } from '@/entities/expense.entity';
import { ChatService } from '@/chat/chat.service';

@Injectable()
export class GroupService {
  constructor(
    @InjectRepository(Group)
    private groupRepo: Repository<Group>,

    @InjectRepository(GroupMember)
    private groupMemberRepo: Repository<GroupMember>,

    @InjectRepository(Expense)
    private readonly expenseRepository: Repository<Expense>,

    private usersService: UsersService,
    private notificationService: NotificationService,
    private readonly gateway: NotificationGateway,
    private readonly chatService: ChatService,
  ) {}

  // =============================
  // CREATE GROUP
  // =============================
  async create(userId: string, dto: CreateGroupDto) {
    const group = this.groupRepo.create({
      name: dto.name,
      description: dto.description,
    });

    await this.groupRepo.save(group);

    // add owner
    const member = this.groupMemberRepo.create({
      user: { id: userId },
      group,
      role: GroupRole.OWNER,
    });

    await this.groupMemberRepo.save(member);
    await this.chatService.ensureForGroup(group.id, [userId]);

    return group;
  }

  // =============================
  // UPDATE GROUP
  // =============================
  async update(groupId: string, userId: string, dto: UpdateGroupDto) {
    const group = await this.groupRepo.findOne({ where: { id: groupId } });
    if (!group) throw new NotFoundException('Group not found');

    await this.groupRepo.update(groupId, dto);

    return this.findById(groupId);
  }

  // =============================
  // DELETE GROUP
  // =============================
  async delete(groupId: string, userId: string) {
    const group = await this.groupRepo.findOne({
      where: { id: groupId },
      relations: ['trips'],
    });
    if (!group) throw new NotFoundException('Group not found');

    const hasClosedTrip = group.trips?.some(
      (trip) => trip.isCloseTrip === false,
    );

    if (hasClosedTrip) {
      throw new BadRequestException(
        'Không thể xóa nhóm vì vẫn còn chuyến đi chưa được hoàn thành',
      );
    }

    await this.groupRepo.softDelete(groupId);

    return { message: 'Deleted successfully' };
  }

  // =============================
  // GET GROUP DETAIL
  // =============================
  async findById(groupId: string, req?: any) {
    // if userId provided, use query builder to limit selected user fields and compute isCreate
    if (req?.user?.id) {
      const group = await this.groupRepo
        .createQueryBuilder('g')
        .leftJoinAndSelect('g.members', 'gm')
        .leftJoin('gm.user', 'user')
        .addSelect([
          'user.id',
          'user.name',
          'user.avatar',
          'user.phone',
          'user.email',
        ])
        .leftJoinAndSelect('g.trips', 'trip')
        .where('g.id = :groupId', { groupId })
        .getOne();

      if (!group) return null;

      return {
        ...group,
        isCreate: req.isLeader,
      } as any;
    }

    // fallback: return full relations
    return this.groupRepo.findOne({
      where: { id: groupId },
      relations: ['members', 'members.user', 'trips'],
    });
  }

  // =============================
  // LIST GROUPS OF USER
  // =============================
  async findMyGroups(userId: string) {
    const groups = await this.groupRepo
      .createQueryBuilder('g')
      .where((qb) => {
        const subQuery = qb
          .subQuery()
          .select('gm.groupId')
          .from('group_members', 'gm')
          .where('gm.userId = :userId', { userId })
          .getQuery();
        return 'g.id IN ' + subQuery;
      })
      .leftJoinAndSelect('g.members', 'gm')
      .leftJoinAndSelect('gm.user', 'user')
      .addSelect(['user.id', 'user.name', 'user.avatar'])
      .leftJoinAndSelect('g.trips', 'trip')
      .getMany();

    return groups.map((group) => {
      const isOwner = (group.members || []).some((m) => {
        return m.user?.id === userId && m.role === GroupRole.OWNER;
      });

      return {
        ...group,
        isCreate: isOwner,
      } as any;
    });
  }

  // =============================
  // MEMBERS
  // =============================
  async addMember(
    groupId: string,
    requesterId: string,
    dto: { contact: string; role?: GroupRole },
  ) {
    if (!groupId && !dto.contact) {
      throw new BadRequestException('Contact is required');
    }

    // find user by email or phone
    const contact = (dto.contact || '').trim();
    let user: User | null = null;
    const isEmail = /\S+@\S+\.\S+/.test(contact);
    if (isEmail) {
      user = await this.usersService.findByEmail(contact);
    } else {
      user = await this.usersService.findByPhone(contact);
    }
    if (!user) {
      throw new NotFoundException('User not found');
    }

    // check existing including soft-deleted entries
    const existing = await this.groupMemberRepo.findOne({
      where: { group: { id: groupId }, user: { id: user.id } },
      withDeleted: true,
    } as any);

    if (existing && !existing.deletedAt) {
      throw new BadRequestException('User is already a member');
    }

    const group = await this.groupRepo.findOne({ where: { id: groupId } });

    if (group) {
      this.gateway.server.to(`user_${user.id}`).socketsJoin(`group_${groupId}`);

      const payload = NotificationHelper.inviteToGroup({
        group,
        user,
        createdBy: requesterId,
      });

      await this.notificationService.createAndPush(payload);
    }

    if (existing && existing.deletedAt) {
      // previously removed member -> restore and update role
      await this.groupMemberRepo.restore((existing as any).id);
      await this.groupMemberRepo.update((existing as any).id, {
        role: dto.role || GroupRole.MEMBER,
      } as any);
      const restored = await this.groupMemberRepo.findOne({
        where: { id: (existing as any).id },
      });
      await this.chatService.addMember(groupId, user.id);
      return {
        id: (restored as any).id,
        user: { id: user.id, email: user.email, name: user.name },
        role: (restored as any).role,
      };
    }

    const member = this.groupMemberRepo.create({
      user: { id: user.id },
      group: { id: groupId },
      role: dto.role || GroupRole.MEMBER,
    } as any);

    const saved = await this.groupMemberRepo.save(member);
    await this.chatService.addMember(groupId, user.id);

    // return minimal member info
    return {
      id: (saved as any).id,
      user: { id: user.id, email: user.email, name: user.name },
      role: (saved as any).role,
    };
  }

  async removeMember(
    groupId: string,
    requesterId: string,
    targetUserId: string,
  ) {
    // find target member
    const target = await this.groupMemberRepo.findOne({
      where: { group: { id: groupId }, user: { id: targetUserId } },
    });

    if (!target) {
      throw new NotFoundException('Member not found');
    }

    // perform soft delete (guards enforce permission rules)
    await this.groupMemberRepo.softDelete({ id: target.id });
    await this.chatService.removeMember(groupId, targetUserId);
    return { message: 'Member removed successfully' };
  }

  async getGroupMembersWithDeletedButPaidAndParticipant(groupId: string) {
    const group = await this.groupRepo.findOne({
      where: { id: groupId },
    });
    if (!group) {
      throw new NotFoundException('Group not found');
    }

    const allMembers = await this.groupMemberRepo.find({
      where: { group: { id: groupId } },
      relations: ['user'],
      withDeleted: true,
    });

    const paidByResults = await this.expenseRepository
      .createQueryBuilder('expense')
      .innerJoin('expense.trip', 'trip')
      .innerJoin('trip.group', 'group')
      .where('group.id = :groupId', { groupId })
      .select('DISTINCT expense.paidById', 'userId')
      .getRawMany<{ userId: string }>();

    const paidByUserIdSet = new Set(paidByResults.map((item) => item.userId));

    const participantResults = await this.expenseRepository
      .createQueryBuilder('expense')
      .innerJoin('expense.trip', 'trip')
      .innerJoin('trip.group', 'group')
      .innerJoin('expense.participants', 'participant')
      .where('group.id = :groupId', { groupId })
      .select('DISTINCT participant.userId', 'userId')
      .getRawMany<{ userId: string }>();

    const participantUserIdSet = new Set(
      participantResults.map((item) => item.userId),
    );

    const relevantUserIds = new Set([
      ...paidByUserIdSet,
      ...participantUserIdSet,
    ]);

    const filteredMembers = allMembers.filter((member) => {
      const isDeleted = !!member.deletedAt;
      if (!isDeleted) return true;
      return relevantUserIds.has(member.user.id);
    });

    return filteredMembers.map((member) => ({
      id: member.user.id,
      name: member.user.name,
      avatar: member.user.avatar,
      bank: member.user.bank,
      bankAccNumber: member.user.bankAccNumber,
      phone: member.user.phone,
      email: member.user.email,
      role: member.role,
    }));
  }
}
