import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';

import { PrismaService } from '../infra/prisma.service';
import { resolveVerificationIcon } from './verification.constants';

import { SettingsService } from './settings.service';

import { NotificationsService } from './notifications.service';
import { MinioService } from '../infra/minio.service';
import { BotFatherSeedService } from './bot/bot-father-seed.service';
import { BotFatherService } from './bot/bot-father.service';
import {
  BOTFATHER_BOT_USERNAME,
  BOTFATHER_DISPLAY_NAME,
  BOTFATHER_USER_USERNAME,
  GET_MY_ID_BOT_USERNAME,
  GET_MY_ID_DISPLAY_NAME,
  GET_MY_ID_USER_USERNAME
} from './bot/bot-father.constants';
import { normalizeBotUsername } from './bot/bot-token.util';
import { HUMAN_USER_WHERE, isProtectedSystemAccount } from './system-account.util';



const INVITE_TTL_DAYS = 7;



@Injectable()

export class FamilyService {

  constructor(

    private readonly prisma: PrismaService,

    private readonly settings: SettingsService,

    private readonly notifications: NotificationsService,

    private readonly minio: MinioService,

    private readonly botFatherSeed: BotFatherSeedService,

    private readonly botFather: BotFatherService

  ) {}



  async create(ownerId: string, name: string) {

    const trimmedName = name.trim();

    if (!trimmedName) {

      throw new BadRequestException('Укажите название семьи');

    }



    const group = await this.prisma.$transaction(async (tx) => {

      const created = await tx.familyGroup.create({

        data: {

          ownerId,

          name: trimmedName,

          members: { create: { userId: ownerId, role: 'owner' } }

        },

        include: { members: { include: this.memberUserInclude() } }

      });



      const generalRoom = await tx.chatRoom.create({

        data: {

          groupId: created.id,

          type: 'GENERAL',

          name: 'Общий чат',

          createdById: ownerId,

          members: { create: { userId: ownerId } }

        }

      });



      return { ...created, generalRoomId: generalRoom.id };

    });



    return this.toGroup(group, ownerId);

  }



  async list(userId: string) {

    const groups = await this.prisma.familyGroup.findMany({

      where: { OR: [{ ownerId: userId }, { members: { some: { userId } } }] },

      include: { members: { include: this.memberUserInclude() } },

      orderBy: { updatedAt: 'desc' }

    });

    return { groups: await Promise.all(groups.map((group) => this.toGroup(group, userId))) };

  }



  async get(requesterId: string, groupId: string) {

    const group = await this.getGroupWithMembers(groupId);

    this.assertMember(group, requesterId);

    return this.toGroup(group, requesterId);

  }



  async update(requesterId: string, groupId: string, name: string) {

    const group = await this.getGroupWithMembers(groupId);

    this.assertOwner(group, requesterId);

    const trimmedName = name.trim();

    if (!trimmedName) {

      throw new BadRequestException('Укажите название семьи');

    }

    const updated = await this.prisma.familyGroup.update({

      where: { id: groupId },

      data: { name: trimmedName },

      include: { members: { include: this.memberUserInclude() } }

    });

    return this.toGroup(updated, requesterId);

  }



  async delete(requesterId: string, groupId: string) {

    const group = await this.getGroupWithMembers(groupId);

    this.assertOwner(group, requesterId);

    await this.destroyGroupWithCleanup(group);

    return { count: 1 };

  }

  async destroyOwnedGroupsForUser(userId: string) {

    const groups = await this.prisma.familyGroup.findMany({

      where: { ownerId: userId },

      include: { members: { include: this.memberUserInclude() } }

    });

    for (const group of groups) {

      await this.destroyGroupWithCleanup(group);

    }

  }

  async leaveAllMembershipsForUser(userId: string) {

    const memberships = await this.prisma.familyMember.findMany({

      where: { userId, role: { not: 'owner' } }

    });

    for (const member of memberships) {

      await this.removeMember(userId, member.id);

    }

  }

  private async destroyGroupWithCleanup(group: {

    id: string;

    ownerId: string;

    name: string;

    avatarStorageKey: string | null;

    members: Array<{ userId: string }>;

  }) {

    const rooms = await this.prisma.chatRoom.findMany({

      where: { groupId: group.id },

      include: { messages: { select: { storageKey: true } } }

    });

    const storageKeys: string[] = [];

    if (group.avatarStorageKey) {

      storageKeys.push(group.avatarStorageKey);

    }

    for (const room of rooms) {

      if (room.avatarStorageKey) {

        storageKeys.push(room.avatarStorageKey);

      }

      for (const message of room.messages) {

        if (message.storageKey) {

          storageKeys.push(message.storageKey);

        }

      }

    }

    await this.minio.deleteObjects(storageKeys);

    await this.prisma.familyGroup.delete({ where: { id: group.id } });

    for (const member of group.members) {

      await this.notifications.publishRealtime(

        member.userId,

        'family_group_deleted',

        'Семья удалена',

        `Семья «${group.name}» была удалена`,

        { groupId: group.id, groupName: group.name }

      );

      if (member.userId === group.ownerId) {

        continue;

      }

      await this.notifications.create(

        member.userId,

        'family_group_deleted',

        'Семья удалена',

        `Семья «${group.name}» была удалена создателем`,

        { groupId: group.id, groupName: group.name }

      );

    }

  }



  async addMember(groupId: string, userId: string, role: string) {

    const group = await this.prisma.familyGroup.findUnique({

      where: { id: groupId },

      include: { members: true }

    });

    if (!group) {

      throw new BadRequestException('Семейная группа не найдена');

    }

    const maxMembers = await this.settings.getNumber('MAX_FAMILY_MEMBERS', 6);

    if (group.members.length >= maxMembers) {

      throw new BadRequestException(`В группе может быть не более ${maxMembers} участников`);

    }

    const targetUser = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { isSystemAccount: true, linkedBotId: true }
    });

    if (!targetUser) {
      throw new BadRequestException('Пользователь не найден');
    }

    if (isProtectedSystemAccount(targetUser)) {
      if (role !== 'bot' || !targetUser.linkedBotId) {
        throw new BadRequestException('Системные учётные записи нельзя добавить в семью напрямую');
      }
    }



    const member = await this.prisma.$transaction(async (tx) => {

      const created = await tx.familyMember.create({ data: { groupId, userId, role } });

      const rooms = await tx.chatRoom.findMany({ where: { groupId } });

      for (const room of rooms) {

        if (room.type === 'GENERAL' && role !== 'bot') {

          await tx.chatRoomMember.upsert({

            where: { roomId_userId: { roomId: room.id, userId } },

            create: { roomId: room.id, userId },

            update: {}

          });

        }

      }

      return created;

    });



    const user = await this.loadMemberUser(member.userId);

    return this.toMember({ ...member, user: user ?? undefined });

  }



  async removeMember(requesterId: string, memberId: string) {

    const member = await this.prisma.familyMember.findUnique({

      where: { id: memberId },

      include: { group: { include: { members: true } } }

    });

    if (!member) {

      throw new NotFoundException('Участник не найден');

    }

    const isSelf = member.userId === requesterId;

    const isOwner = member.group.ownerId === requesterId;

    if (!isSelf && !isOwner) {

      throw new ForbiddenException('Недостаточно прав для удаления участника');

    }

    if (member.role === 'owner') {

      throw new BadRequestException('Нельзя удалить владельца семьи');

    }



    await this.prisma.$transaction(async (tx) => {

      await tx.chatRoomMember.deleteMany({ where: { userId: member.userId, room: { groupId: member.groupId } } });

      await tx.familyMember.delete({ where: { id: memberId } });

    });

    return { count: 1 };

  }



  async sendInvite(requesterId: string, groupId: string, options: { target?: string; inviteeUserId?: string }) {
    const group = await this.getGroupWithMembers(groupId);
    this.assertMember(group, requesterId);

    const invitee = options.inviteeUserId
      ? await this.prisma.user.findFirst({
          where: { id: options.inviteeUserId, status: 'ACTIVE', deletedAt: null }
        })
      : options.target
        ? await this.findUserByContact(options.target)
        : null;

    if (!invitee) {
      throw new BadRequestException('Пользователь не найден. Выберите его из результатов поиска.');
    }

    const inviter = await this.prisma.user.findUnique({ where: { id: requesterId } });
    const isBotInvite = isProtectedSystemAccount(invitee);
    const inviteeAlreadyInFamily = group.members.some((member) => member.userId === invitee.id);

    if (invitee.id === requesterId) {
      throw new BadRequestException('Нельзя пригласить самого себя');
    }

    if (isBotInvite) {
      const visibleBotUserIds = await this.getVisibleBotUserIds(groupId, requesterId);
      if (visibleBotUserIds.has(invitee.id)) {
        throw new BadRequestException('Этот бот уже доступен вам в семье');
      }
    } else if (inviteeAlreadyInFamily) {
      throw new BadRequestException('Пользователь уже состоит в семье');
    }

    const existingPending = await this.prisma.familyInvite.findFirst({
      where: { groupId, inviteeUserId: invitee.id, status: 'PENDING' }
    });
    if (existingPending && !isBotInvite) {
      throw new BadRequestException('Приглашение уже отправлено');
    }

    if (isBotInvite) {
      const invite = await this.prisma.familyInvite.create({
        data: {
          groupId,
          inviterId: requesterId,
          inviteeUserId: invitee.id,
          targetEmail: invitee.email ?? undefined,
          targetPhone: invitee.phone ?? undefined,
          status: 'ACCEPTED',
          expiresAt: new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1000)
        },
        include: {
          group: true,
          inviter: true
        }
      });
      if (!inviteeAlreadyInFamily) {
        await this.addMember(groupId, invitee.id, 'bot');
      }
      await this.notifications.create(
        requesterId,
        'family_bot_added',
        'Бот добавлен в семью',
        `${invitee.displayName} добавлен(а) в «${group.name}» по приглашению`,
        { groupId, inviteId: invite.id, botUserId: invitee.id },
        { skipPublish: true }
      );
      return this.toInvite(invite);
    }

    const invite = await this.prisma.familyInvite.create({
      data: {
        groupId,
        inviterId: requesterId,
        inviteeUserId: invitee.id,
        targetEmail: invitee.email ?? undefined,
        targetPhone: invitee.phone ?? undefined,
        expiresAt: new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1000)
      },
      include: {
        group: true,
        inviter: true
      }
    });

    await this.notifications.create(
      invitee.id,
      'family_invite',
      'Приглашение в семью',
      `${inviter?.displayName ?? 'Участник'} приглашает вас в «${group.name}»`,
      { inviteId: invite.id, groupId, groupName: group.name }
    );

    return this.toInvite(invite);
  }

  async searchInviteUsers(requesterId: string, groupId: string, query: string) {
    const group = await this.getGroupWithMembers(groupId);
    this.assertMember(group, requesterId);

    const trimmed = query.trim();
    if (trimmed.length < 2) {
      return { users: [] };
    }

    const excludedIds = [...group.members.filter((member) => !member.user?.linkedBotId).map((member) => member.userId), requesterId];
    const visibleBotUserIds = await this.getVisibleBotUserIds(groupId, requesterId);
    const botFatherUserId = await this.botFatherSeed.getBotFatherUserId();
    const botFatherBotId = await this.botFatherSeed.getBotFatherBotId();
    const getMyIdUserId = await this.botFatherSeed.getGetMyIdBotUserId();
    const getMyIdBotId = await this.botFatherSeed.getGetMyIdBotId();
    const botFatherMatches =
      /bot\s*father|бот\s*father|botfather/i.test(trimmed) &&
      !visibleBotUserIds.has(botFatherUserId);
    const getMyIdMatches =
      /get\s*my\s*id|getmyid|мой\s*id|айди|chat[_\s-]*id/i.test(trimmed) &&
      !visibleBotUserIds.has(getMyIdUserId);
    const digits = trimmed.replace(/\D/g, '');
    const normalizedBotQuery = normalizeBotUsername(trimmed.replace(/^@/, ''));

    const [users, matchedBots] = await Promise.all([
      this.prisma.user.findMany({
        where: {
          status: 'ACTIVE',
          deletedAt: null,
          isSystemAccount: false,
          linkedBotId: null,
          id: { notIn: excludedIds },
          OR: [
            { displayName: { contains: trimmed, mode: 'insensitive' } },
            { username: { contains: trimmed, mode: 'insensitive' } },
            ...(trimmed.includes('@') ? [{ email: { contains: trimmed, mode: 'insensitive' as const } }] : []),
            ...(digits.length >= 4 ? [{ phone: { contains: digits } }] : [])
          ]
        },
        orderBy: { displayName: 'asc' },
        take: 8,
        select: {
          id: true,
          displayName: true,
          email: true,
          phone: true,
          username: true,
          avatarStorageKey: true,
          isVerified: true,
          verificationIcon: true
        }
      }),
      normalizedBotQuery.length >= 2
        ? this.prisma.bot.findMany({
            where: {
              isActive: true,
              OR: [
                { name: { contains: trimmed, mode: 'insensitive' } },
                { username: { contains: normalizedBotQuery, mode: 'insensitive' } }
              ]
            },
            orderBy: { name: 'asc' },
            take: 8,
            include: {
              owner: { select: { id: true, displayName: true } },
              linkedSystemUser: { select: { id: true } }
            }
          })
        : Promise.resolve([])
    ]);

    const botCandidates: Array<{
      id: string;
      botId: string;
      displayName: string;
      username?: string;
      hasAvatar: boolean;
      isVerified: boolean;
      verificationIcon?: string;
      isBot: boolean;
      botUsername: string;
      ownerDisplayName?: string;
    }> = [];

    for (const bot of matchedBots) {
      if (botFatherMatches && bot.username === BOTFATHER_BOT_USERNAME) continue;
      if (getMyIdMatches && bot.username === GET_MY_ID_BOT_USERNAME) continue;
      const { userId } = bot.linkedSystemUser
        ? { userId: bot.linkedSystemUser.id }
        : await this.botFather.ensureBotSystemUser(bot.id);
      if (visibleBotUserIds.has(userId)) continue;
      botCandidates.push({
        id: userId,
        botId: bot.id,
        displayName: bot.name,
        username: `@${bot.username}`,
        hasAvatar: false,
        isVerified: true,
        verificationIcon: resolveVerificationIcon('bot'),
        isBot: true,
        botUsername: bot.username,
        ownerDisplayName: bot.owner.displayName
      });
    }

    const humanCandidates = users.map((user) => ({
      id: user.id,
      displayName: user.displayName,
      email: user.email,
      phone: user.phone,
      username: user.username,
      hasAvatar: Boolean(user.avatarStorageKey),
      isVerified: user.isVerified,
      verificationIcon: user.isVerified ? resolveVerificationIcon(user.verificationIcon) : undefined,
      isBot: false,
      botUsername: undefined,
      botId: undefined,
      ownerDisplayName: undefined
    }));

    return {
      users: [
        ...(botFatherMatches
          ? [
              {
                id: botFatherUserId,
                displayName: BOTFATHER_DISPLAY_NAME,
                email: undefined,
                phone: undefined,
                username: BOTFATHER_USER_USERNAME,
                hasAvatar: false,
                isVerified: true,
                verificationIcon: resolveVerificationIcon('bot'),
                isBot: true,
                botUsername: BOTFATHER_BOT_USERNAME,
                botId: botFatherBotId,
                ownerDisplayName: undefined
              }
            ]
          : []),
        ...(getMyIdMatches
          ? [
              {
                id: getMyIdUserId,
                displayName: GET_MY_ID_DISPLAY_NAME,
                email: undefined,
                phone: undefined,
                username: GET_MY_ID_USER_USERNAME,
                hasAvatar: false,
                isVerified: true,
                verificationIcon: resolveVerificationIcon('bot'),
                isBot: true,
                botUsername: GET_MY_ID_BOT_USERNAME,
                botId: getMyIdBotId,
                ownerDisplayName: undefined
              }
            ]
          : []),
        ...botCandidates,
        ...humanCandidates.filter((candidate) => !botCandidates.some((bot) => bot.id === candidate.id))
      ].slice(0, 12)
    };
  }



  async respondInvite(userId: string, inviteId: string, accept: boolean) {

    const invite = await this.prisma.familyInvite.findUnique({

      where: { id: inviteId },

      include: { group: { include: { members: true } }, inviter: true }

    });

    if (!invite) {

      throw new NotFoundException('Приглашение не найдено');

    }

    if (invite.status !== 'PENDING') {

      throw new BadRequestException('Приглашение уже обработано');

    }

    if (invite.expiresAt.getTime() < Date.now()) {

      await this.prisma.familyInvite.update({ where: { id: inviteId }, data: { status: 'EXPIRED' } });

      throw new BadRequestException('Срок действия приглашения истёк');

    }

    if (invite.inviteeUserId && invite.inviteeUserId !== userId) {

      throw new ForbiddenException('Это приглашение предназначено другому пользователю');

    }



    if (!accept) {

      const declined = await this.prisma.familyInvite.update({

        where: { id: inviteId },

        data: { status: 'DECLINED', inviteeUserId: invite.inviteeUserId ?? userId },

        include: { group: true, inviter: true }

      });

      await this.notifications.dismissFamilyInvite(userId, inviteId);

      return this.toInvite(declined);

    }



    const maxMembers = await this.settings.getNumber('MAX_FAMILY_MEMBERS', 6);

    if (invite.group.members.length >= maxMembers) {

      throw new BadRequestException(`В семье уже максимум ${maxMembers} участников`);

    }



    await this.prisma.$transaction(async (tx) => {

      await tx.familyInvite.update({

        where: { id: inviteId },

        data: { status: 'ACCEPTED', inviteeUserId: userId }

      });

      await tx.familyMember.create({

        data: { groupId: invite.groupId, userId, role: 'member' }

      });

      const rooms = await tx.chatRoom.findMany({ where: { groupId: invite.groupId } });

      for (const room of rooms) {

        if (room.type === 'GENERAL') {

          await tx.chatRoomMember.upsert({

            where: { roomId_userId: { roomId: room.id, userId } },

            create: { roomId: room.id, userId },

            update: {}

          });

        }

      }

    });



    const user = await this.prisma.user.findUnique({ where: { id: userId } });

    await this.notifications.create(

      invite.inviterId,

      'family_invite_accepted',

      'Приглашение принято',

      `${user?.displayName ?? 'Пользователь'} присоединился к «${invite.group.name}»`,

      { groupId: invite.groupId, userId }

    );



    const updated = await this.prisma.familyInvite.findUnique({

      where: { id: inviteId },

      include: { group: true, inviter: true }

    });

    await this.notifications.dismissFamilyInvite(userId, inviteId);

    return this.toInvite(updated!);

  }



  async listInvites(userId: string, groupId?: string) {

    const invites = await this.prisma.familyInvite.findMany({

      where: {

        ...(groupId ? { groupId } : {}),
        inviteeUserId: userId,
        status: 'PENDING'
      },

      include: { group: true, inviter: true },

      orderBy: { createdAt: 'desc' }

    });

    return { invites: invites.map((invite) => this.toInvite(invite)) };

  }



  async assertFamilyMember(groupId: string, userId: string) {

    const group = await this.getGroupWithMembers(groupId);

    this.assertMember(group, userId);

    return group;

  }



  private async getGroupWithMembers(groupId: string) {

    const group = await this.prisma.familyGroup.findUnique({

      where: { id: groupId },

      include: { members: { include: this.memberUserInclude() } }

    });

    if (!group) {

      throw new NotFoundException('Семейная группа не найдена');

    }

    return group;

  }



  private assertMember(group: { members: Array<{ userId: string }> }, userId: string) {

    if (!group.members.some((member) => member.userId === userId)) {

      throw new ForbiddenException('Вы не состоите в этой семье');

    }

  }



  private assertOwner(group: { ownerId: string }, userId: string) {

    if (group.ownerId !== userId) {

      throw new ForbiddenException('Только владелец семьи может выполнить это действие');

    }

  }



  private async findUserByContact(target: string) {
    const trimmed = target.trim();
    if (!trimmed) {
      return null;
    }

    if (trimmed.includes('@')) {
      return this.prisma.user.findFirst({
        where: { email: { equals: trimmed, mode: 'insensitive' }, status: 'ACTIVE', deletedAt: null, ...HUMAN_USER_WHERE }
      });
    }

    const digits = trimmed.replace(/\D/g, '');
    if (/^\+?\d/.test(trimmed) && digits.length >= 10) {
      return this.prisma.user.findFirst({
        where: {
          status: 'ACTIVE',
          deletedAt: null,
          ...HUMAN_USER_WHERE,
          OR: [{ phone: trimmed }, { phone: { endsWith: digits.slice(-10) } }]
        }
      });
    }

    return this.prisma.user.findFirst({
      where: {
        status: 'ACTIVE',
        deletedAt: null,
        ...HUMAN_USER_WHERE,
        OR: [
          { username: { equals: trimmed, mode: 'insensitive' } },
          { displayName: { equals: trimmed, mode: 'insensitive' } }
        ]
      }
    });
  }



  private async toGroup(group: {

    id: string;

    ownerId: string;

    name: string;

    hasAvatar: boolean;

    createdAt: Date;

    updatedAt: Date;

    members: Array<{

      id: string;

      groupId: string;

      userId: string;

      role: string;

      createdAt: Date;

      user?: {
        displayName: string;
        avatarStorageKey: string | null;
        isVerified: boolean;
        verificationIcon: string | null;
        linkedBotId?: string | null;
        linkedBot?: { username: string } | null;
      };

    }>;

  }, viewerId?: string) {
    const visibleBotUserIds = viewerId ? await this.getVisibleBotUserIds(group.id, viewerId) : new Set<string>();
    const visibleMembers = group.members.filter((member) => !member.user?.linkedBotId || visibleBotUserIds.has(member.userId));

    return {

      id: group.id,

      ownerId: group.ownerId,

      name: group.name,

      hasAvatar: group.hasAvatar,

      createdAt: group.createdAt.toISOString(),

      updatedAt: group.updatedAt.toISOString(),

      members: visibleMembers.map((member) => this.toMember(member)),

      botFatherAvailable: !visibleMembers.some(
        (member) => member.user?.linkedBot?.username === BOTFATHER_BOT_USERNAME
      ),

      getMyIdBotAvailable: !visibleMembers.some(
        (member) => member.user?.linkedBot?.username === GET_MY_ID_BOT_USERNAME
      )

    };

  }

  private async getVisibleBotUserIds(groupId: string, viewerId: string) {
    const rooms = await this.prisma.chatRoom.findMany({
      where: {
        groupId,
        type: 'BOT',
        members: { some: { userId: viewerId } }
      },
      include: {
        members: {
          include: { user: { select: { linkedBotId: true } } }
        }
      }
    });
    return new Set(
      rooms
        .flatMap((room) => room.members)
        .filter((member) => Boolean(member.user.linkedBotId))
        .map((member) => member.userId)
    );
  }



  private toMember(member: {

    id: string;

    groupId: string;

    userId: string;

    role: string;

    createdAt: Date;

    user?: {
      displayName: string;
      avatarStorageKey: string | null;
      isVerified: boolean;
      verificationIcon: string | null;
      linkedBotId?: string | null;
      linkedBot?: { username: string } | null;
    };

  }) {

    return {

      id: member.id,

      groupId: member.groupId,

      userId: member.userId,

      role: member.role,

      displayName: member.user?.displayName ?? 'Участник',

      hasAvatar: Boolean(member.user?.avatarStorageKey),

      isVerified: member.user?.isVerified ?? false,

      verificationIcon: member.user?.isVerified ? resolveVerificationIcon(member.user?.verificationIcon) : undefined,

      isBot: Boolean(member.user?.linkedBotId),

      botUsername: member.user?.linkedBot?.username ?? undefined,

      createdAt: member.createdAt.toISOString()

    };

  }



  private memberUserInclude() {

    return {

      user: {

        include: {

          linkedBot: { select: { username: true } }

        }

      }

    };

  }



  private loadMemberUser(userId: string) {

    return this.prisma.user.findUnique({

      where: { id: userId },

      include: { linkedBot: { select: { username: true } } }

    });

  }



  private toInvite(invite: {

    id: string;

    groupId: string;

    inviterId: string;

    inviteeUserId: string | null;

    targetEmail: string | null;

    targetPhone: string | null;

    status: string;

    expiresAt: Date;

    createdAt: Date;

    group: { name: string };

    inviter: { displayName: string };

  }) {

    return {

      id: invite.id,

      groupId: invite.groupId,

      groupName: invite.group.name,

      inviterId: invite.inviterId,

      inviterName: invite.inviter.displayName,

      inviteeUserId: invite.inviteeUserId ?? undefined,

      targetEmail: invite.targetEmail ?? undefined,

      targetPhone: invite.targetPhone ?? undefined,

      status: invite.status,

      expiresAt: invite.expiresAt.toISOString(),

      createdAt: invite.createdAt.toISOString()

    };

  }

}


