import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, Task, TaskStatus, TaskPriority, type PrismaClient } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import { ActivityService } from "../activity/activity.service";
import type { CreateTaskDto, UpdateTaskDto, QueryTasksDto } from "./dto";

type TaskWithRelations = Task & {
  assignee: { id: string; name: string; email: string } | null;
  creator: { id: string; name: string; email: string };
  project: { id: string; name: string; color: string | null } | null;
  subtasks?: (Task & {
    assignee: { id: string; name: string; email: string } | null;
  })[];
};

/**
 * Service for managing tasks
 */
@Injectable()
export class TasksService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly activityService: ActivityService
  ) {}

  private async withWorkspaceContextIfAvailable<T>(
    workspaceId: string | undefined,
    userId: string | undefined,
    fn: (client: PrismaClient) => Promise<T>
  ): Promise<T> {
    if (workspaceId && userId && typeof this.prisma.withWorkspaceContext === "function") {
      return this.prisma.withWorkspaceContext(userId, workspaceId, fn);
    }

    return fn(this.prisma);
  }

  /**
   * Create a new task
   */
  async create(
    workspaceId: string,
    userId: string,
    createTaskDto: CreateTaskDto
  ): Promise<Omit<TaskWithRelations, "subtasks">> {
    const assigneeConnection = createTaskDto.assigneeId
      ? { connect: { id: createTaskDto.assigneeId } }
      : undefined;

    const projectConnection = createTaskDto.projectId
      ? { connect: { id: createTaskDto.projectId } }
      : undefined;

    const parentConnection = createTaskDto.parentId
      ? { connect: { id: createTaskDto.parentId } }
      : undefined;

    const data: Prisma.TaskCreateInput = {
      title: createTaskDto.title,
      description: createTaskDto.description ?? null,
      dueDate: createTaskDto.dueDate ?? null,
      workspace: { connect: { id: workspaceId } },
      creator: { connect: { id: userId } },
      status: createTaskDto.status ?? TaskStatus.NOT_STARTED,
      priority: createTaskDto.priority ?? TaskPriority.MEDIUM,
      sortOrder: createTaskDto.sortOrder ?? 0,
      metadata: createTaskDto.metadata
        ? (createTaskDto.metadata as unknown as Prisma.InputJsonValue)
        : {},
      ...(createTaskDto.assignedAgent !== undefined && {
        assignedAgent: createTaskDto.assignedAgent,
      }),
      ...(assigneeConnection && { assignee: assigneeConnection }),
      ...(projectConnection && { project: projectConnection }),
      ...(parentConnection && { parent: parentConnection }),
    };

    // Set completedAt if status is COMPLETED
    if (data.status === TaskStatus.COMPLETED) {
      data.completedAt = new Date();
    }

    const task = await this.withWorkspaceContextIfAvailable(workspaceId, userId, async (client) => {
      return client.task.create({
        data,
        include: {
          assignee: {
            select: { id: true, name: true, email: true },
          },
          creator: {
            select: { id: true, name: true, email: true },
          },
          project: {
            select: { id: true, name: true, color: true },
          },
        },
      });
    });

    // Log activity
    await this.activityService.logTaskCreated(workspaceId, userId, task.id, {
      title: task.title,
    });

    return task;
  }

  /**
   * Get paginated tasks with filters
   */
  async findAll(
    query: QueryTasksDto,
    userId?: string
  ): Promise<{
    data: Omit<TaskWithRelations, "subtasks">[];
    meta: {
      total: number;
      page: number;
      limit: number;
      totalPages: number;
    };
  }> {
    const page = query.page ?? 1;
    const limit = query.limit ?? 50;
    const skip = (page - 1) * limit;

    // Build where clause
    const where: Prisma.TaskWhereInput = query.workspaceId
      ? {
          workspaceId: query.workspaceId,
        }
      : {};

    if (query.status) {
      where.status = Array.isArray(query.status) ? { in: query.status } : query.status;
    }

    if (query.priority) {
      where.priority = Array.isArray(query.priority) ? { in: query.priority } : query.priority;
    }

    if (query.assigneeId) {
      where.assigneeId = query.assigneeId;
    }

    if (query.projectId) {
      where.projectId = query.projectId;
    }

    if (query.parentId) {
      where.parentId = query.parentId;
    }

    if (query.dueDateFrom || query.dueDateTo) {
      where.dueDate = {};
      if (query.dueDateFrom) {
        where.dueDate.gte = query.dueDateFrom;
      }
      if (query.dueDateTo) {
        where.dueDate.lte = query.dueDateTo;
      }
    }

    // Execute queries in parallel
    const [data, total] = await this.withWorkspaceContextIfAvailable(
      query.workspaceId,
      userId,
      async (client) => {
        return Promise.all([
          client.task.findMany({
            where,
            include: {
              assignee: {
                select: { id: true, name: true, email: true },
              },
              creator: {
                select: { id: true, name: true, email: true },
              },
              project: {
                select: { id: true, name: true, color: true },
              },
            },
            orderBy: {
              createdAt: "desc",
            },
            skip,
            take: limit,
          }),
          client.task.count({ where }),
        ]);
      }
    );

    return {
      data,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Get a single task by ID
   */
  async findOne(id: string, workspaceId: string, userId?: string): Promise<TaskWithRelations> {
    const task = await this.withWorkspaceContextIfAvailable(workspaceId, userId, async (client) => {
      return client.task.findUnique({
        where: {
          id,
          workspaceId,
        },
        include: {
          assignee: {
            select: { id: true, name: true, email: true },
          },
          creator: {
            select: { id: true, name: true, email: true },
          },
          project: {
            select: { id: true, name: true, color: true },
          },
          subtasks: {
            include: {
              assignee: {
                select: { id: true, name: true, email: true },
              },
            },
          },
        },
      });
    });

    if (!task) {
      throw new NotFoundException(`Task with ID ${id} not found`);
    }

    return task;
  }

  /**
   * Update a task
   */
  async update(
    id: string,
    workspaceId: string,
    userId: string,
    updateTaskDto: UpdateTaskDto
  ): Promise<Omit<TaskWithRelations, "subtasks">> {
    const { task, existingTask } = await this.withWorkspaceContextIfAvailable(
      workspaceId,
      userId,
      async (client) => {
        const existingTask = await client.task.findUnique({
          where: { id, workspaceId },
        });

        if (!existingTask) {
          throw new NotFoundException(`Task with ID ${id} not found`);
        }

        // Build update data - only include defined fields
        const data: Prisma.TaskUpdateInput = {};

        if (updateTaskDto.title !== undefined) {
          data.title = updateTaskDto.title;
        }
        if (updateTaskDto.description !== undefined) {
          data.description = updateTaskDto.description;
        }
        if (updateTaskDto.status !== undefined) {
          data.status = updateTaskDto.status;
        }
        if (updateTaskDto.priority !== undefined) {
          data.priority = updateTaskDto.priority;
        }
        if (updateTaskDto.dueDate !== undefined) {
          data.dueDate = updateTaskDto.dueDate;
        }
        if (updateTaskDto.sortOrder !== undefined) {
          data.sortOrder = updateTaskDto.sortOrder;
        }
        if (updateTaskDto.metadata !== undefined) {
          data.metadata = updateTaskDto.metadata as unknown as Prisma.InputJsonValue;
        }
        if (updateTaskDto.assigneeId !== undefined && updateTaskDto.assigneeId !== null) {
          data.assignee = { connect: { id: updateTaskDto.assigneeId } };
        }
        if (updateTaskDto.projectId !== undefined && updateTaskDto.projectId !== null) {
          data.project = { connect: { id: updateTaskDto.projectId } };
        }
        if (updateTaskDto.parentId !== undefined && updateTaskDto.parentId !== null) {
          data.parent = { connect: { id: updateTaskDto.parentId } };
        }
        if (updateTaskDto.assignedAgent !== undefined) {
          data.assignedAgent = updateTaskDto.assignedAgent;
        }

        // Handle completedAt based on status changes
        if (updateTaskDto.status) {
          if (
            updateTaskDto.status === TaskStatus.COMPLETED &&
            existingTask.status !== TaskStatus.COMPLETED
          ) {
            data.completedAt = new Date();
          } else if (
            updateTaskDto.status !== TaskStatus.COMPLETED &&
            existingTask.status === TaskStatus.COMPLETED
          ) {
            data.completedAt = null;
          }
        }

        const task = await client.task.update({
          where: {
            id,
            workspaceId,
          },
          data,
          include: {
            assignee: {
              select: { id: true, name: true, email: true },
            },
            creator: {
              select: { id: true, name: true, email: true },
            },
            project: {
              select: { id: true, name: true, color: true },
            },
          },
        });

        return { task, existingTask };
      }
    );

    // Log activities
    await this.activityService.logTaskUpdated(workspaceId, userId, id, {
      changes: updateTaskDto as Prisma.JsonValue,
    });

    // Log completion if status changed to COMPLETED
    if (
      updateTaskDto.status === TaskStatus.COMPLETED &&
      existingTask.status !== TaskStatus.COMPLETED
    ) {
      await this.activityService.logTaskCompleted(workspaceId, userId, id);
    }

    // Log assignment if assigneeId changed
    if (
      updateTaskDto.assigneeId !== undefined &&
      updateTaskDto.assigneeId !== existingTask.assigneeId
    ) {
      await this.activityService.logTaskAssigned(
        workspaceId,
        userId,
        id,
        updateTaskDto.assigneeId ?? ""
      );
    }

    return task;
  }

  /**
   * Delete a task
   */
  async remove(id: string, workspaceId: string, userId: string): Promise<void> {
    const task = await this.withWorkspaceContextIfAvailable(workspaceId, userId, async (client) => {
      const task = await client.task.findUnique({
        where: { id, workspaceId },
      });

      if (!task) {
        throw new NotFoundException(`Task with ID ${id} not found`);
      }

      await client.task.delete({
        where: {
          id,
          workspaceId,
        },
      });

      return task;
    });

    // Log activity
    await this.activityService.logTaskDeleted(workspaceId, userId, id, {
      title: task.title,
    });
  }
}
