import { HttpStatus, Injectable } from '@nestjs/common';
import {
  DatabaseService,
  ErrorResponse,
  logger,
  UploadService,
} from '../../config';
import { createGeneralError } from '../../utils';
import {
  createEventDto,
  eventFilterDto,
  eventResponseSchema,
  eventsResponseSchema,
  updateEventDto,
} from './events.dto';

@Injectable()
export class EventsService {
  constructor(
    private readonly db: DatabaseService,
    private readonly uploadService: UploadService,
  ) {}

  private async moveEventImage(image: any): Promise<any | undefined> {
    if (
      !image ||
      typeof image !== 'object' ||
      typeof image.file_name !== 'string' ||
      image.file_name.trim() === ''
    ) {
      return undefined;
    }

    const moved = await this.uploadService.moveImage(image, 'events');
    return {
      file_name: moved.file_name,
      original_name: moved.original_name,
      file_size: moved.file_size,
      mime_type: moved.mime_type,
    };
  }

  private includeMembers() {
    return {
      members: {
        select: {
          id: true,
          employee_id: true,
          first_name: true,
          last_name: true,
          nickname: true,
          image: true,
        },
      },
    };
  }

  private async ensureMembersExist(memberIds: string[], owner_id: string) {
    if (memberIds.length === 0) {
      return;
    }

    const count = await this.db.members.count({
      where: {
        owner_id,
        id: {
          in: memberIds,
        },
      },
    });

    if (count !== memberIds.length) {
      throw createGeneralError(
        'One or more members are invalid',
        'member_ids',
        HttpStatus.BAD_REQUEST,
      );
    }
  }

  async createEvent(data: createEventDto, owner_id: string) {
    try {
      const memberIds = data.member_ids ?? [];
      await this.ensureMembersExist(memberIds, owner_id);
      const image = await this.moveEventImage(data.image);

      const event = await this.db.events.create({
        data: {
          title: data.title,
          category: data.category ?? 'meeting',
          ...(image ? { image } : {}),
          date: data.date,
          start_time: data.start_time,
          end_time: data.end_time,
          attendees: memberIds.length,
          ...(data.location ? { location: data.location } : {}),
          ...(data.event_owner ? { event_owner: data.event_owner } : {}),
          ...(data.description ? { description: data.description } : {}),
          ...(memberIds.length > 0
            ? {
                members: {
                  connect: memberIds.map((id) => ({ id })),
                },
              }
            : {}),
          owner: {
            connect: {
              id: owner_id,
            },
          },
        },
        include: this.includeMembers(),
      });

      const { data: parsedEvent } = eventResponseSchema.safeParse(event);

      return {
        data: parsedEvent,
        message: 'Event created successfully',
        status: HttpStatus.CREATED,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in createEvent: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getEvents(query: eventFilterDto, owner_id: string) {
    try {
      const page = query.page ?? 1;
      const limit = query.limit ?? 10;
      const skip = (page - 1) * limit;

      const where: any = { owner_id };

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

      if (query.search) {
        where.OR = [
          {
            title: {
              contains: query.search,
            },
          },
          {
            location: {
              contains: query.search,
            },
          },
          {
            event_owner: {
              contains: query.search,
            },
          },
          {
            description: {
              contains: query.search,
            },
          },
        ];
      }

      if (query.from || query.to) {
        where.date = {
          ...(query.from ? { gte: query.from } : {}),
          ...(query.to ? { lte: query.to } : {}),
        };
      }

      const [events, total] = await Promise.all([
        this.db.events.findMany({
          where,
          include: this.includeMembers(),
          orderBy: {
            date: 'asc',
          },
          skip,
          take: limit,
        }),
        this.db.events.count({ where }),
      ]);

      const { data: parsedEvents } = eventsResponseSchema.safeParse(events);
      const totalPages = Math.ceil(total / limit);

      return {
        data: parsedEvents,
        message: 'Events retrieved successfully',
        status: HttpStatus.OK,
        details: {
          pagination: {
            total_items: total,
            total_pages: totalPages,
            current_page: page,
            items_per_page: limit,
            has_next_page: page < totalPages,
            has_previous_page: page > 1,
            next_page: page < totalPages ? page + 1 : null,
            previous_page: page > 1 ? page - 1 : null,
          },
        },
      };
    } catch (error) {
      logger.error(`Error in getEvents: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getEventById(id: string, owner_id: string) {
    try {
      const event = await this.db.events.findUnique({
        where: { id, owner_id },
        include: this.includeMembers(),
      });

      if (!event) {
        throw createGeneralError(
          'Event not found',
          'event',
          HttpStatus.NOT_FOUND,
        );
      }

      const { data: parsedEvent } = eventResponseSchema.safeParse(event);

      return {
        data: parsedEvent,
        message: 'Event retrieved successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getEventById: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateEvent(id: string, data: updateEventDto, owner_id: string) {
    try {
      const existingEvent = await this.db.events.findUnique({
        where: { id, owner_id },
        include: {
          members: {
            select: { id: true },
          },
        },
      });

      if (!existingEvent) {
        throw createGeneralError(
          'Event not found',
          'event',
          HttpStatus.NOT_FOUND,
        );
      }

      if (data.member_ids) {
        await this.ensureMembersExist(data.member_ids, owner_id);
      }

      const nextImage =
        data.image === null
          ? {}
          : data.image
            ? await this.moveEventImage(data.image)
            : undefined;

      const updatedEvent = await this.db.events.update({
        where: { id },
        data: {
          ...(data.title ? { title: data.title } : {}),
          ...(nextImage !== undefined ? { image: nextImage } : {}),
          ...(data.category ? { category: data.category } : {}),
          ...(data.date ? { date: data.date } : {}),
          ...(data.start_time ? { start_time: data.start_time } : {}),
          ...(data.end_time ? { end_time: data.end_time } : {}),
          ...(data.location !== undefined ? { location: data.location } : {}),
          ...(data.event_owner !== undefined
            ? { event_owner: data.event_owner }
            : {}),
          ...(data.description !== undefined
            ? { description: data.description }
            : {}),
          ...(data.member_ids
            ? {
                attendees: data.member_ids.length,
                members: {
                  set: data.member_ids.map((memberId) => ({ id: memberId })),
                },
              }
            : {}),
        },
        include: this.includeMembers(),
      });

      const { data: parsedEvent } = eventResponseSchema.safeParse(updatedEvent);

      return {
        data: parsedEvent,
        message: 'Event updated successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in updateEvent: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async deleteEvent(id: string, owner_id: string) {
    try {
      const existingEvent = await this.db.events.findUnique({
        where: { id, owner_id },
      });

      if (!existingEvent) {
        throw createGeneralError(
          'Event not found',
          'event',
          HttpStatus.NOT_FOUND,
        );
      }

      await this.db.events.delete({
        where: { id },
      });

      return {
        data: null,
        message: 'Event deleted successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in deleteEvent: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }
}
