import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, Project } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import { ActivityService } from "../activity/activity.service";
import { ProjectStatus } from "@prisma/client";
import type { CreateProjectDto, UpdateProjectDto, QueryProjectsDto } from "./dto";

type ProjectWithRelations = Project & {
  creator: { id: string; name: string; email: string };
  _count: { tasks: number; events: number };
};

type ProjectWithDetails = Project & {
  creator: { id: string; name: string; email: string };
  tasks: {
    id: string;
    title: string;
    status: string;
    priority: string;
    dueDate: Date | null;
  }[];
  events: {
    id: string;
    title: string;
    startTime: Date;
    endTime: Date | null;
  }[];
  _count: { tasks: number; events: number };
};

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

  /**
   * Create a new project
   */
  async create(
    workspaceId: string,
    userId: string,
    createProjectDto: CreateProjectDto
  ): Promise<ProjectWithRelations> {
    const data: Prisma.ProjectCreateInput = {
      ...(createProjectDto.domainId
        ? { domain: { connect: { id: createProjectDto.domainId } } }
        : {}),
      name: createProjectDto.name,
      description: createProjectDto.description ?? null,
      color: createProjectDto.color ?? null,
      startDate: createProjectDto.startDate ?? null,
      endDate: createProjectDto.endDate ?? null,
      workspace: { connect: { id: workspaceId } },
      creator: { connect: { id: userId } },
      status: createProjectDto.status ?? ProjectStatus.PLANNING,
      metadata: createProjectDto.metadata
        ? (createProjectDto.metadata as unknown as Prisma.InputJsonValue)
        : {},
    };

    const project = await this.prisma.project.create({
      data,
      include: {
        creator: {
          select: { id: true, name: true, email: true },
        },
        _count: {
          select: { tasks: true, events: true },
        },
      },
    });

    // Log activity
    await this.activityService.logProjectCreated(workspaceId, userId, project.id, {
      name: project.name,
    });

    return project;
  }

  /**
   * Get paginated projects with filters
   */
  async findAll(query: QueryProjectsDto): Promise<{
    data: ProjectWithRelations[];
    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.ProjectWhereInput = query.workspaceId
      ? {
          workspaceId: query.workspaceId,
        }
      : {};

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

    if (query.startDateFrom || query.startDateTo) {
      where.startDate = {};
      if (query.startDateFrom) {
        where.startDate.gte = query.startDateFrom;
      }
      if (query.startDateTo) {
        where.startDate.lte = query.startDateTo;
      }
    }

    // Execute queries in parallel
    const [data, total] = await Promise.all([
      this.prisma.project.findMany({
        where,
        include: {
          creator: {
            select: { id: true, name: true, email: true },
          },
          _count: {
            select: { tasks: true, events: true },
          },
        },
        orderBy: {
          createdAt: "desc",
        },
        skip,
        take: limit,
      }),
      this.prisma.project.count({ where }),
    ]);

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

  /**
   * Get a single project by ID
   */
  async findOne(id: string, workspaceId: string): Promise<ProjectWithDetails> {
    const project = await this.prisma.project.findUnique({
      where: {
        id,
        workspaceId,
      },
      include: {
        creator: {
          select: { id: true, name: true, email: true },
        },
        tasks: {
          select: {
            id: true,
            title: true,
            status: true,
            priority: true,
            dueDate: true,
          },
          orderBy: { sortOrder: "asc" },
        },
        events: {
          select: {
            id: true,
            title: true,
            startTime: true,
            endTime: true,
          },
          orderBy: { startTime: "asc" },
        },
        _count: {
          select: { tasks: true, events: true },
        },
      },
    });

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

    return project;
  }

  /**
   * Update a project
   */
  async update(
    id: string,
    workspaceId: string,
    userId: string,
    updateProjectDto: UpdateProjectDto
  ): Promise<ProjectWithRelations> {
    // Verify project exists
    const existingProject = await this.prisma.project.findUnique({
      where: { id, workspaceId },
    });

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

    // Build update data, only including defined fields
    const updateData: Prisma.ProjectUpdateInput = {};
    if (updateProjectDto.name !== undefined) updateData.name = updateProjectDto.name;
    if (updateProjectDto.description !== undefined)
      updateData.description = updateProjectDto.description;
    if (updateProjectDto.status !== undefined) updateData.status = updateProjectDto.status;
    if (updateProjectDto.startDate !== undefined) updateData.startDate = updateProjectDto.startDate;
    if (updateProjectDto.endDate !== undefined) updateData.endDate = updateProjectDto.endDate;
    if (updateProjectDto.color !== undefined) updateData.color = updateProjectDto.color;
    if (updateProjectDto.domainId !== undefined)
      updateData.domain = updateProjectDto.domainId
        ? { connect: { id: updateProjectDto.domainId } }
        : { disconnect: true };
    if (updateProjectDto.domainId !== undefined)
      updateData.domain = updateProjectDto.domainId
        ? {
            connect: {
              id: updateProjectDto.domainId,
            },
          }
        : { disconnect: true };
    if (updateProjectDto.metadata !== undefined) {
      updateData.metadata = updateProjectDto.metadata as unknown as Prisma.InputJsonValue;
    }

    const project = await this.prisma.project.update({
      where: {
        id,
        workspaceId,
      },
      data: updateData,
      include: {
        creator: {
          select: { id: true, name: true, email: true },
        },
        _count: {
          select: { tasks: true, events: true },
        },
      },
    });

    // Log activity
    await this.activityService.logProjectUpdated(workspaceId, userId, id, {
      changes: updateProjectDto as Prisma.JsonValue,
    });

    return project;
  }

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

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

    await this.prisma.project.delete({
      where: {
        id,
        workspaceId,
      },
    });

    // Log activity
    await this.activityService.logProjectDeleted(workspaceId, userId, id, {
      name: project.name,
    });
  }
}
