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

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

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

import { NotificationsService } from './notifications.service';



const INVITE_TTL_DAYS = 7;



@Injectable()

export class FamilyService {

  constructor(

    private readonly prisma: PrismaService,

    private readonly settings: SettingsService,

    private readonly notifications: NotificationsService

  ) {}



  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: { user: true } } }

      });



      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);

  }



  async list(userId: string) {

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

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

      include: { members: { include: { user: true } } },

      orderBy: { updatedAt: 'desc' }

    });

    return { groups: groups.map((group) => this.toGroup(group)) };

  }



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

    const group = await this.getGroupWithMembers(groupId);

    this.assertMember(group, requesterId);

    return this.toGroup(group);

  }



  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: { user: true } } }

    });

    return this.toGroup(updated);

  }



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

    const group = await this.getGroupWithMembers(groupId);

    this.assertOwner(group, requesterId);

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

    return { count: 1 };

  }



  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 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') {

          await tx.chatRoomMember.upsert({

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

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

            update: {}

          });

        }

      }

      return created;

    });



    return this.toMember(member);

  }



  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, target: string) {

    const group = await this.getGroupWithMembers(groupId);

    this.assertMember(group, requesterId);



    const trimmedTarget = target.trim();

    if (!trimmedTarget) {

      throw new BadRequestException('Укажите email, телефон или логин приглашаемого');

    }



    const invitee = await this.findUserByContact(trimmedTarget);

    if (invitee?.id === requesterId) {

      throw new BadRequestException('Нельзя пригласить самого себя');

    }

    if (invitee && group.members.some((member) => member.userId === invitee.id)) {

      throw new BadRequestException('Пользователь уже состоит в семье');

    }



    const existingPending = invitee

      ? await this.prisma.familyInvite.findFirst({

          where: { groupId, inviteeUserId: invitee.id, status: 'PENDING' }

        })

      : null;

    if (existingPending) {

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

    }



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

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

      data: {

        groupId,

        inviterId: requesterId,

        inviteeUserId: invitee?.id,

        targetEmail: trimmedTarget.includes('@') ? trimmedTarget : undefined,

        targetPhone: !trimmedTarget.includes('@') && /^\+?\d/.test(trimmedTarget) ? trimmedTarget : undefined,

        expiresAt: new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1000)

      },

      include: {

        group: true,

        inviter: true

      }

    });



    if (invitee) {

      await this.notifications.create(

        invitee.id,

        'family_invite',

        'Приглашение в семью',

        `${inviter?.displayName ?? 'Участник'} приглашает вас в «${group.name}»`,

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

      );

    }



    return this.toInvite(invite);

  }



  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: { user: true } } }

    });

    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) {

    if (target.includes('@')) {

      return this.prisma.user.findFirst({ where: { email: target, status: 'ACTIVE' } });

    }

    const normalizedPhone = target.replace(/\D/g, '');

    return this.prisma.user.findFirst({

      where: {

        status: 'ACTIVE',

        OR: [{ phone: target }, { phone: { contains: normalizedPhone.slice(-10) } }, { username: target }]

      }

    });

  }



  private 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 };

    }>;

  }) {

    return {

      id: group.id,

      ownerId: group.ownerId,

      name: group.name,

      hasAvatar: group.hasAvatar,

      createdAt: group.createdAt.toISOString(),

      updatedAt: group.updatedAt.toISOString(),

      members: group.members.map((member) => this.toMember(member))

    };

  }



  private toMember(member: {

    id: string;

    groupId: string;

    userId: string;

    role: string;

    createdAt: Date;

    user?: { displayName: string; avatarStorageKey: string | null };

  }) {

    return {

      id: member.id,

      groupId: member.groupId,

      userId: member.userId,

      role: member.role,

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

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

      createdAt: member.createdAt.toISOString()

    };

  }



  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()

    };

  }

}


