import { TodoStatus, PriorityStatus, Role } from "@prisma/client";
import prisma from "../../prismaClient";

export interface TodoListParams {
  page: number;
  limit: number;
  search: string;
  statuses: TodoStatus[];
  priorities: PriorityStatus[];
  sortBy: "" | "title" | "dueDate" | "status" | "priority" | "createdAt";
  sortDir: "asc" | "desc";
  assigneeUserId?: string;
  teamOfManagerId?: string;
}

export interface CreateTodoData {
  title: string;
  task?: string;
  status: TodoStatus;
  priority: PriorityStatus;
  dueDate: Date | string;
  createdById: string;
  assigneeIds: string[];
}

export interface UpdateTodoData {
  title?: string;
  task?: string;
  status?: TodoStatus;
  priority?: PriorityStatus;
  dueDate?: Date | string | null;
  assigneeIds?: string[];
}

export class TodoService {
  async createTodo(data: CreateTodoData) {
    const { title, task, status, priority, dueDate, createdById, assigneeIds } =
      data;

    // Validate: at least one admin or manager must be assigned
    if (!assigneeIds || assigneeIds.length === 0) {
      const err: any = new Error("At least one admin or manager is required");
      err.code = "BAD_REQUEST";
      throw err;
    }
    const supervisorsCount = await prisma.user.count({
      where: {
        id: { in: assigneeIds },
        role: { in: [Role.ADMIN, Role.MANAGER] },
      },
    });
    if (supervisorsCount === 0) {
      const err: any = new Error("At least one admin or manager is required");
      err.code = "BAD_REQUEST";
      throw err;
    }

    const todo = await prisma.todo.create({
      data: {
        title,
        // Prisma schema requires task: String (non-nullable)
        task: task ?? "",
        status,
        priority,
        // Prisma schema requires dueDate (non-nullable), so ensure it's always provided
        dueDate: new Date(dueDate),
        createdBy: { connect: { id: createdById } },
        assignees: { connect: assigneeIds.map((id) => ({ id })) },
      },
      include: { assignees: true, createdBy: true },
    });

    return todo;
  }

  private buildVisibilityAndWhere(
    params: TodoListParams,
    requesterRole?: Role,
    requesterId?: string
  ) {
    const where: any = {};

    if (params.search) {
      where.OR = [
        { title: { contains: params.search, mode: "insensitive" } },
        { task: { contains: params.search, mode: "insensitive" } },
      ];
    }

    if (params.statuses.length > 0) {
      where.status = { in: params.statuses };
    }

    if (params.priorities.length > 0) {
      where.priority = { in: params.priorities };
    }

    const andClauses: any[] = [];
    if (params.assigneeUserId) {
      andClauses.push({ assignees: { some: { id: params.assigneeUserId } } });
    }
    if (params.teamOfManagerId) {
      andClauses.push({
        assignees: { some: { managerId: params.teamOfManagerId } },
      });
    }

    if (requesterRole === Role.ADMIN && requesterId) {
      andClauses.push({ assignees: { some: { id: requesterId } } });
    } else if (requesterRole === Role.MANAGER && requesterId) {
      andClauses.push({
        OR: [
          { assignees: { some: { id: requesterId } } },
          { assignees: { some: { managerId: requesterId } } },
        ],
      });
    }

    if (andClauses.length) {
      where.AND = [...(where.AND || []), ...andClauses];
    }

    let orderBy: any = undefined;
    if (params.sortBy) {
      if (params.sortBy === "title") orderBy = { title: params.sortDir };
      else if (params.sortBy === "dueDate")
        orderBy = { dueDate: params.sortDir };
      else if (params.sortBy === "status") orderBy = { status: params.sortDir };
      else if (params.sortBy === "priority")
        orderBy = { priority: params.sortDir };
      else if (params.sortBy === "createdAt")
        orderBy = { createdAt: params.sortDir };
    }

    return { where, orderBy };
  }

  async listTodos(
    params: TodoListParams,
    requesterRole?: Role,
    requesterId?: string
  ): Promise<{ items: any[]; total: number }> {
    const { where, orderBy } = this.buildVisibilityAndWhere(
      params,
      requesterRole,
      requesterId
    );

    const total = await prisma.todo.count({ where });
    const items = await prisma.todo.findMany({
      where,
      include: { assignees: true, createdBy: true },
      orderBy,
      skip: (params.page - 1) * params.limit,
      take: params.limit,
    });

    return { items, total };
  }

  async updateTodo(id: string, data: UpdateTodoData) {
    // If assigneeIds were provided in update, enforce at least one admin among them
    if (typeof data.assigneeIds !== "undefined") {
      const ids = data.assigneeIds || [];
      if (ids.length === 0) {
        const err: any = new Error("At least one admin or manager is required");
        err.code = "BAD_REQUEST";
        throw err;
      }
      const supervisorsCount = await prisma.user.count({
        where: { id: { in: ids }, role: { in: [Role.ADMIN, Role.MANAGER] } },
      });
      if (supervisorsCount === 0) {
        const err: any = new Error("At least one admin or manager is required");
        err.code = "BAD_REQUEST";
        throw err;
      }
    }

    const todo = await prisma.todo.update({
      where: { id },
      data: {
        title: data.title,
        task: data.task,
        status: data.status,
        priority: data.priority,
        // Only include fields when provided; avoid explicitly setting undefined
        ...(typeof data.dueDate === "undefined"
          ? {}
          : data.dueDate === null
          ? {}
          : { dueDate: new Date(data.dueDate) }),
        ...(typeof data.assigneeIds !== "undefined"
          ? { assignees: { set: data.assigneeIds.map((i) => ({ id: i })) } }
          : {}),
      },
      include: { assignees: true, createdBy: true },
    });
    return todo;
  }

  async deleteTodo(id: string) {
    // Ensure exists
    const existing = await prisma.todo.findUnique({ where: { id } });
    if (!existing) {
      const err: any = new Error("Todo not found");
      err.code = "NOT_FOUND";
      throw err;
    }
    await prisma.todo.delete({ where: { id } });
  }

  async getUserTodos(
    userId: string,
    requesterRole?: Role,
    requesterId?: string
  ) {
    // Determine target user for manager override
    const targetUser = await prisma.user.findUnique({
      where: { id: userId },
      select: { role: true },
    });

    const visibilityClause = (() => {
      if (requesterRole === Role.MANAGER && requesterId) {
        if (targetUser?.role === Role.ADMIN) {
          return {};
        }
        return {
          OR: [
            { assignees: { some: { id: requesterId } } },
            { assignees: { some: { managerId: requesterId } } },
          ],
        };
      }
      if (requesterRole === Role.ADMIN && requesterId) {
        return { assignees: { some: { id: requesterId } } };
      }
      return {};
    })();

    const todos = await prisma.todo.findMany({
      where: {
        AND: [{ assignees: { some: { id: userId } } }, visibilityClause],
      },
      include: { assignees: true, createdBy: true },
    });

    return todos;
  }

  private async checkTodoVisibility(
    todoId: string,
    requesterRole?: Role,
    requesterId?: string
  ) {
    const todo = await prisma.todo.findUnique({
      where: { id: todoId },
      include: {
        assignees: { select: { id: true, managerId: true } },
        createdBy: { select: { id: true } },
      },
    });
    if (!todo) {
      const err: any = new Error("Todo not found");
      err.code = "NOT_FOUND";
      throw err;
    }

    const isAssignee = !!todo.assignees.find((a) => a.id === requesterId);
    const isCreator = todo.createdBy.id === requesterId;
    const isManagerOfAssignee = !!todo.assignees.find(
      (a) => a.managerId === requesterId
    );

    const allowed =
      !!requesterRole &&
      ((requesterRole === Role.ADMIN && isAssignee) ||
        (requesterRole === Role.MANAGER &&
          (isAssignee || isManagerOfAssignee || isCreator)));

    if (!allowed) {
      const err: any = new Error("Forbidden");
      err.code = "FORBIDDEN";
      throw err;
    }

    return { todo, isAssignee, isCreator };
  }

  async listComments(
    todoId: string,
    requesterRole?: Role,
    requesterId?: string
  ) {
    await this.checkTodoVisibility(todoId, requesterRole, requesterId);

    const comments = await prisma.comment.findMany({
      where: { todoId },
      include: {
        user: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            imageUrl: true,
            role: true,
          },
        },
      },
      orderBy: { createdAt: "asc" },
    });
    return comments;
  }

  async addComment(
    todoId: string,
    content: string,
    requesterRole?: Role,
    requesterId?: string
  ) {
    if (!content || !content.trim()) {
      const err: any = new Error("Content is required");
      err.code = "BAD_REQUEST";
      throw err;
    }

    await this.checkTodoVisibility(todoId, requesterRole, requesterId);

    const newComment = await prisma.comment.create({
      data: {
        content: content.trim(),
        todo: { connect: { id: todoId } },
        user: { connect: { id: requesterId! } },
      },
      include: {
        user: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            imageUrl: true,
            role: true,
          },
        },
      },
    });

    return newComment;
  }

  async updateComment(
    todoId: string,
    commentId: string,
    content: string,
    requesterRole?: Role,
    requesterId?: string
  ) {
    if (!content || !content.trim()) {
      const err: any = new Error("Content is required");
      err.code = "BAD_REQUEST";
      throw err;
    }

    await this.checkTodoVisibility(todoId, requesterRole, requesterId);

    const existing = await prisma.comment.findUnique({
      where: { id: commentId },
      select: { id: true, userId: true, todoId: true },
    });

    if (!existing || existing.todoId !== todoId) {
      const err: any = new Error("Comment not found");
      err.code = "NOT_FOUND";
      throw err;
    }
    if (existing.userId !== requesterId) {
      const err: any = new Error("Only the author can edit this comment");
      err.code = "FORBIDDEN";
      throw err;
    }

    const updated = await prisma.comment.update({
      where: { id: commentId },
      data: { content: content.trim() },
      include: {
        user: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            imageUrl: true,
            role: true,
          },
        },
      },
    });

    return updated;
  }

  async deleteComment(
    todoId: string,
    commentId: string,
    requesterRole?: Role,
    requesterId?: string
  ) {
    await this.checkTodoVisibility(todoId, requesterRole, requesterId);

    const existing = await prisma.comment.findUnique({
      where: { id: commentId },
      select: { id: true, userId: true, todoId: true },
    });

    if (!existing || existing.todoId !== todoId) {
      const err: any = new Error("Comment not found");
      err.code = "NOT_FOUND";
      throw err;
    }
    if (existing.userId !== requesterId) {
      const err: any = new Error("Only the author can delete this comment");
      err.code = "FORBIDDEN";
      throw err;
    }

    await prisma.comment.delete({ where: { id: commentId } });
  }
}

export const todoService = new TodoService();
