import { Role, RiskStatus, User, Prisma } from "@prisma/client";
import prisma from "../../prismaClient";

export interface UserWithRelations extends User {
  assignedTodos?: any[];
  createdTodos?: any[];
}

export interface UserListParams {
  page: number;
  limit: number;
  search: string;
  risks: string[];
  sortBy: string;
  sortDir: "asc" | "desc";
  roleType: "user" | "admin";
}

export interface UserListResult {
  items: UserWithRelations[];
  total: number;
}

export class UserRepository {
  /**
   * Count users matching the given criteria
   */
  async countUsers(where: Prisma.UserWhereInput): Promise<number> {
    return await prisma.user.count({ where });
  }

  /**
   * Find many users with standard Prisma ordering
   */
  async findManyUsers(
    where: Prisma.UserWhereInput,
    orderBy?: Prisma.UserOrderByWithRelationInput,
    skip?: number,
    take?: number
  ): Promise<UserWithRelations[]> {
    return await prisma.user.findMany({
      where,
      include: {
        assignedTodos: true,
        createdTodos: true,
      },
      orderBy,
      skip,
      take,
    });
  }

  /**
   * Find users with custom risk-based sorting using raw SQL
   */
  async findUsersWithRiskSort(
    where: any,
    search: string,
    sortDir: "asc" | "desc",
    page: number,
    limit: number
  ): Promise<UserWithRelations[]> {
    const whereSqlParts: string[] = [];
    const params: any[] = [];

    // role filter
    if (where.role && where.role.in) {
      const p1 = params.length + 1;
      const p2 = params.length + 2;
      whereSqlParts.push(`"role" IN ($${p1}::"Role", $${p2}::"Role")`);
      params.push("ADMIN", "MANAGER");
    } else if (where.role) {
      const p = params.length + 1;
      whereSqlParts.push(`"role" = $${p}::"Role"`);
      params.push("USER");
    }

    // search filter
    if (where.OR && Array.isArray(where.OR) && where.OR.length) {
      const s = `%${search}%`;
      const p1 = params.length + 1;
      const p2 = params.length + 2;
      const p3 = params.length + 3;
      const p4 = params.length + 4;
      whereSqlParts.push(
        `("firstName" ILIKE $${p1} OR "lastName" ILIKE $${p2} OR "email" ILIKE $${p3} OR "title" ILIKE $${p4})`
      );
      params.push(s, s, s, s);
    }

    // risks filter
    if (where.riskStatus && where.riskStatus.in && where.riskStatus.in.length) {
      const placeholders = where.riskStatus.in
        .map(
          (_r: string, i: number) => `$${params.length + 1 + i}::"RiskStatus"`
        )
        .join(", ");
      whereSqlParts.push(`"riskStatus" IN (${placeholders})`);
      params.push(...where.riskStatus.in);
    }

    const whereSql = whereSqlParts.length
      ? `WHERE ${whereSqlParts.join(" AND ")}`
      : "";
    const offset = (page - 1) * limit;
    const dir = sortDir === "desc" ? "DESC" : "ASC";

    const query = `
      SELECT "id"
      FROM "User"
      ${whereSql}
      ORDER BY CASE "riskStatus"
        WHEN 'NO_RISK'::"RiskStatus" THEN 0
        WHEN 'LOW_RISK'::"RiskStatus" THEN 1
        WHEN 'MODERATE_RISK'::"RiskStatus" THEN 2
        WHEN 'HIGH_RISK'::"RiskStatus" THEN 3
      END ${dir}, "firstName" ASC
      LIMIT ${limit} OFFSET ${offset}
    `;

    const idRows: { id: string }[] = await prisma.$queryRawUnsafe(
      query,
      ...params
    );
    const ids = idRows.map((r) => r.id);

    if (ids.length === 0) {
      return [];
    }

    const fetched = await prisma.user.findMany({
      where: { id: { in: ids } },
      include: { assignedTodos: true, createdTodos: true },
    });

    // preserve order from ids
    const orderMap = new Map(ids.map((id, idx) => [id, idx] as const));
    return fetched.sort(
      (a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0)
    );
  }

  /**
   * Find all users with minimal fields for logs
   */
  async findUsersForLogs(): Promise<
    Pick<User, "id" | "firstName" | "lastName" | "email">[]
  > {
    return await prisma.user.findMany({
      select: { id: true, firstName: true, lastName: true, email: true },
    });
  }

  /**
   * Find a single user by ID
   */
  async findUserById(id: string): Promise<UserWithRelations | null> {
    return await prisma.user.findUnique({
      where: { id },
      include: {
        assignedTodos: true,
        createdTodos: true,
        manager: true,
      },
    });
  }

  /**
   * Find a user by email
   */
  async findUserByEmail(email: string): Promise<User | null> {
    return await prisma.user.findUnique({ where: { email } });
  }

  /**
   * Create a new user
   */
  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    return await prisma.user.create({ data });
  }

  /**
   * Update a user
   */
  async updateUser(id: string, data: Prisma.UserUpdateInput): Promise<User> {
    return await prisma.user.update({
      where: { id },
      data,
    });
  }

  /**
   * Delete a user
   */
  async deleteUser(id: string): Promise<void> {
    await prisma.user.delete({ where: { id } });
  }

  /**
   * Delete multiple users
   */
  async deleteUsers(ids: string[]): Promise<void> {
    await prisma.user.deleteMany({
      where: { id: { in: ids } },
    });
  }

  /**
   * Delete todos created by a user
   */
  async deleteTodosByCreator(createdById: string): Promise<void> {
    await prisma.todo.deleteMany({ where: { createdById } });
  }

  /**
   * Delete todos created by multiple users
   */
  async deleteTodosByCreators(createdByIds: string[]): Promise<void> {
    await prisma.todo.deleteMany({
      where: { createdById: { in: createdByIds } },
    });
  }

  /**
   * Find users by IDs with specific fields
   */
  async findUsersByIds(ids: string[]): Promise<Pick<User, "id" | "role">[]> {
    return await prisma.user.findMany({
      where: { id: { in: ids } },
      select: { id: true, role: true },
    });
  }

  /**
   * Find a user with specific fields for validation
   */
  async findUserForValidation(
    id: string
  ): Promise<Pick<User, "id" | "role"> | null> {
    return await prisma.user.findUnique({
      where: { id },
      select: { id: true, role: true },
    });
  }
}

export const userRepository = new UserRepository();
