import { Injectable } from '@nestjs/common';

import { Prisma } from '@/generated/prisma/client';
import { PrismaService } from '@/prisma/prisma.service';

@Injectable()
export class UsersRepository {
  constructor(private readonly prisma: PrismaService) {}

  private readonly publicSelect = {
    id: true,
    email: true,
    name: true,
    position: true,
    avatar: true,
    isActive: true,
    deletedAt: true,
    createdAt: true,
    updatedAt: true,
  } satisfies Prisma.UserSelect;

  // publicSelect plus the user's (non-deleted) roles. Used by the admin
  // list/detail endpoints so the UI can show and edit role assignments.
  private readonly adminSelect = {
    ...this.publicSelect,
    roles: {
      where: { role: { deletedAt: null } },
      select: {
        role: { select: { id: true, name: true } },
      },
    },
    // Most recent session, so the admin table can show the user's last device.
    sessions: {
      orderBy: { createdAt: 'desc' },
      take: 1,
      select: {
        ipAddress: true,
        userAgent: true,
        deviceType: true,
        browser: true,
        os: true,
        createdAt: true,
        loggedOutAt: true,
      },
    },
  } satisfies Prisma.UserSelect;

  findById(id: number) {
    return this.prisma.user.findUnique({
      where: { id },
    });
  }

  findByEmail(email: string) {
    return this.prisma.user.findUnique({
      where: { email },
    });
  }

  findProfileById(id: number) {
    return this.prisma.user.findUnique({
      where: { id },
      select: this.publicSelect,
    });
  }

  findActiveById(id: number) {
    return this.prisma.user.findFirst({
      where: {
        id,
        deletedAt: null,
      },
      select: this.adminSelect,
    });
  }

  create(data: {
    email: string;
    password: string;
    name?: string;
    position?: string;
    avatar?: string;
    isActive?: boolean;
    deletedAt?: Date | null;
  }) {
    return this.prisma.user.create({
      data: {
        ...data,
        isActive: data.isActive ?? true,
        deletedAt: data.deletedAt ?? null,
      },
      select: this.publicSelect,
    });
  }

  updateUser(
    id: number,
    data: {
      name?: string;
      email?: string;
      password?: string;
      position?: string;
      avatar?: string;
      isActive?: boolean;
      deletedAt?: Date | null;
    },
  ) {
    return this.prisma.user.update({
      where: { id },
      data,
      select: this.publicSelect,
    });
  }

  async softDelete(id: number) {
    const now = new Date();

    return this.prisma.$transaction(async (tx) => {
      const user = await tx.user.update({
        where: { id },
        data: {
          isActive: false,
          deletedAt: now,
        },
        select: this.publicSelect,
      });

      await tx.userSession.updateMany({
        where: {
          userId: id,
          loggedOutAt: null,
        },
        data: {
          loggedOutAt: now,
        },
      });

      return user;
    });
  }

  closeActiveSessions(userId: number, loggedOutAt = new Date()) {
    return this.prisma.userSession.updateMany({
      where: {
        userId,
        loggedOutAt: null,
      },
      data: {
        loggedOutAt,
      },
    });
  }

  findLoginTrailsByUserId(userId: number) {
    return this.prisma.userSession.findMany({
      where: {
        userId,
      },
      orderBy: {
        createdAt: 'desc',
      },
      take: 100,
      select: {
        id: true,
        userId: true,
        ipAddress: true,
        userAgent: true,
        deviceType: true,
        browser: true,
        os: true,
        createdAt: true,
        loggedOutAt: true,
      },
    });
  }

  updatePassword(id: number, hashedPassword: string) {
    return this.prisma.user.update({
      where: { id },
      data: { password: hashedPassword },
      select: { id: true },
    });
  }

  setPendingEmail(
    id: number,
    pendingEmail: string,
    hashedToken: string,
    expiresAt: Date,
  ) {
    return this.prisma.user.update({
      where: { id },
      data: {
        pendingEmail,
        emailVerificationToken: hashedToken,
        emailVerificationExpiresAt: expiresAt,
      },
      select: { id: true },
    });
  }

  applyEmailChange(id: number, newEmail: string) {
    return this.prisma.user.update({
      where: { id },
      data: {
        email: newEmail,
        pendingEmail: null,
        emailVerificationToken: null,
        emailVerificationExpiresAt: null,
      },
      select: this.publicSelect,
    });
  }

  clearPendingEmail(id: number) {
    return this.prisma.user.update({
      where: { id },
      data: {
        pendingEmail: null,
        emailVerificationToken: null,
        emailVerificationExpiresAt: null,
      },
      select: { id: true },
    });
  }

  findMany() {
    return this.prisma.user.findMany({
      where: {
        deletedAt: null,
      },
      orderBy: {
        createdAt: 'desc',
      },
      select: this.adminSelect,
    });
  }

  // --- user_has_roles pivot ---

  findUserRole(userId: number, roleId: number) {
    return this.prisma.userHasRoles.findUnique({
      where: { userId_roleId: { userId, roleId } },
    });
  }

  addRoles(userId: number, roleIds: number[]) {
    return this.prisma.userHasRoles.createMany({
      data: roleIds.map((roleId) => ({ userId, roleId })),
      skipDuplicates: true,
    });
  }

  // Replace the user's roles with exactly `roleIds` (empty clears all).
  setRoles(userId: number, roleIds: number[]) {
    return this.prisma.$transaction(async (tx) => {
      await tx.userHasRoles.deleteMany({ where: { userId } });

      if (roleIds.length > 0) {
        await tx.userHasRoles.createMany({
          data: roleIds.map((roleId) => ({ userId, roleId })),
          skipDuplicates: true,
        });
      }
    });
  }

  removeRole(userId: number, roleId: number) {
    return this.prisma.userHasRoles.delete({
      where: { userId_roleId: { userId, roleId } },
      select: { userId: true, roleId: true },
    });
  }

  // --- user_has_permissions pivot (direct grants) ---

  findUserPermission(userId: number, permissionId: number) {
    return this.prisma.userHasPermissions.findUnique({
      where: { userId_permissionId: { userId, permissionId } },
    });
  }

  addPermissions(userId: number, permissionIds: number[]) {
    return this.prisma.userHasPermissions.createMany({
      data: permissionIds.map((permissionId) => ({ userId, permissionId })),
      skipDuplicates: true,
    });
  }

  removePermission(userId: number, permissionId: number) {
    return this.prisma.userHasPermissions.delete({
      where: { userId_permissionId: { userId, permissionId } },
      select: { userId: true, permissionId: true },
    });
  }
}
