import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, Event } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import { ActivityService } from "../activity/activity.service";
import type { CreateEventDto, UpdateEventDto, QueryEventsDto } from "./dto";

type EventWithRelations = Event & {
  creator: { id: string; name: string; email: string };
  project: { id: string; name: string; color: string | null } | null;
};

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

  /**
   * Create a new event
   */
  async create(
    workspaceId: string,
    userId: string,
    createEventDto: CreateEventDto
  ): Promise<EventWithRelations> {
    const projectConnection = createEventDto.projectId
      ? { connect: { id: createEventDto.projectId } }
      : undefined;

    const data: Prisma.EventCreateInput = {
      title: createEventDto.title,
      description: createEventDto.description ?? null,
      startTime: createEventDto.startTime,
      endTime: createEventDto.endTime ?? null,
      location: createEventDto.location ?? null,
      workspace: { connect: { id: workspaceId } },
      creator: { connect: { id: userId } },
      allDay: createEventDto.allDay ?? false,
      metadata: createEventDto.metadata
        ? (createEventDto.metadata as unknown as Prisma.InputJsonValue)
        : {},
      ...(projectConnection && { project: projectConnection }),
    };

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

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

    return event;
  }

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

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

    if (query.allDay !== undefined) {
      where.allDay = query.allDay;
    }

    if (query.startFrom || query.startTo) {
      where.startTime = {};
      if (query.startFrom) {
        where.startTime.gte = query.startFrom;
      }
      if (query.startTo) {
        where.startTime.lte = query.startTo;
      }
    }

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

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

  /**
   * Get a single event by ID
   */
  async findOne(id: string, workspaceId: string): Promise<EventWithRelations> {
    const event = await this.prisma.event.findUnique({
      where: {
        id,
        workspaceId,
      },
      include: {
        creator: {
          select: { id: true, name: true, email: true },
        },
        project: {
          select: { id: true, name: true, color: true },
        },
      },
    });

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

    return event;
  }

  /**
   * Update an event
   */
  async update(
    id: string,
    workspaceId: string,
    userId: string,
    updateEventDto: UpdateEventDto
  ): Promise<EventWithRelations> {
    // Verify event exists
    const existingEvent = await this.prisma.event.findUnique({
      where: { id, workspaceId },
    });

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

    // Build update data, only including defined fields (excluding projectId)
    const updateData: Prisma.EventUpdateInput = {};
    if (updateEventDto.title !== undefined) updateData.title = updateEventDto.title;
    if (updateEventDto.description !== undefined)
      updateData.description = updateEventDto.description;
    if (updateEventDto.startTime !== undefined) updateData.startTime = updateEventDto.startTime;
    if (updateEventDto.endTime !== undefined) updateData.endTime = updateEventDto.endTime;
    if (updateEventDto.allDay !== undefined) updateData.allDay = updateEventDto.allDay;
    if (updateEventDto.location !== undefined) updateData.location = updateEventDto.location;
    if (updateEventDto.recurrence !== undefined) {
      updateData.recurrence = updateEventDto.recurrence as unknown as Prisma.InputJsonValue;
    }
    if (updateEventDto.metadata !== undefined) {
      updateData.metadata = updateEventDto.metadata as unknown as Prisma.InputJsonValue;
    }
    // Handle project relation separately
    if (updateEventDto.projectId !== undefined) {
      updateData.project = { connect: { id: updateEventDto.projectId } };
    }

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

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

    return event;
  }

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

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

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

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