import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { TemplateRepository } from '../repositories/template.repository';
import { MeetingAttendanceError } from '../errors/meeting-attendance.error';
import { generateDualQRCodes, getMeetingAttendanceBaseUrl } from '../utils/meeting-utils';
import { Request } from 'express';
import { getMeetingRoleFromUser, isMeetingAdminRole } from '../utils/meeting-roles';
import {
  MEETING_ATTENDANCE_AUDIT_ACTIONS,
  MEETING_ATTENDANCE_AUDIT_RESOURCES,
} from '../constants/audit';
import { MeetingAttendanceAuditLogWriter } from './audit-log-writer.service';

@Injectable()
export class TemplatesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly templateRepository: TemplateRepository,
    private readonly auditLogWriter: MeetingAttendanceAuditLogWriter,
  ) {}

  async listTemplates(userEmail: string) {
    const user = await this.prisma.user.findUnique({
      where: { email: userEmail, deletedAt: null },
      include: { roles: { include: { role: true } } },
    });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }
    if (!getMeetingRoleFromUser(user)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    const templates = await this.templateRepository.listTemplates(user.id);
    return { templates };
  }

  async createTemplate(
    payload: {
      name?: string;
      title?: string;
      description?: string;
      duration?: string | number;
      location?: string;
      type?: string;
      isPublic?: boolean;
      attendeeIds?: string[];
    },
    userEmail: string,
    request?: Request,
    actor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    const user = await this.prisma.user.findUnique({
      where: { email: userEmail, deletedAt: null },
      include: { roles: { include: { role: true } } },
    });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }
    if (!getMeetingRoleFromUser(user)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    if (!payload.name || !payload.title || !payload.duration) {
      throw new MeetingAttendanceError(400, 'Template name, meeting title, and duration are required');
    }

    const template = await this.templateRepository.createTemplate({
      name: payload.name,
      title: payload.title,
      description: payload.description,
      duration: parseInt(String(payload.duration), 10),
      location: payload.location,
      type: (payload.type as any) || 'OFFLINE',
      isPublic: payload.isPublic || false,
      creator: { connect: { id: user.id } },
    });

    if (payload.attendeeIds && Array.isArray(payload.attendeeIds) && payload.attendeeIds.length > 0) {
      const attendeeData = payload.attendeeIds.map((userId) => ({
        templateId: template.id,
        userId,
      }));
      await this.templateRepository.createTemplateAttendees(attendeeData);
    }

    await this.auditLogWriter.log({
      request,
      actor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.TEMPLATE_CREATE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.TEMPLATE,
      statusCode: 201,
      resourceId: template.id,
      requestBody: {
        name: payload.name,
        title: payload.title,
        description: payload.description,
        duration: payload.duration,
        location: payload.location,
        type: payload.type,
        isPublic: payload.isPublic,
        attendeeIds: payload.attendeeIds,
      },
      changes: {
        action: 'create',
        newData: {
          templateId: template.id,
          name: template.name,
          title: template.title,
          duration: template.duration,
          isPublic: template.isPublic,
          attendeeCount: payload.attendeeIds?.length || 0,
        },
      },
    });

    return { template };
  }

  async createMeetingFromTemplate(
    templateId: string,
    payload: {
      startTime?: string;
      endTime?: string;
      customTitle?: string;
      customDescription?: string;
      customLocation?: string;
    },
    request: Request | undefined,
    userEmail: string,
    actor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    if (!payload.startTime || !payload.endTime) {
      throw new MeetingAttendanceError(400, 'Start time and end time are required');
    }

    const user = await this.prisma.user.findUnique({
      where: { email: userEmail, deletedAt: null },
      include: { roles: { include: { role: true } } },
    });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }
    if (!getMeetingRoleFromUser(user)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    const template = await this.templateRepository.findTemplateById(templateId);
    if (!template) {
      throw new MeetingAttendanceError(404, 'Template does not exist');
    }

    const meeting = await this.prisma.meeting.create({
      data: {
        title: payload.customTitle || template.title,
        description: payload.customDescription || template.description,
        startTime: new Date(payload.startTime),
        endTime: new Date(payload.endTime),
        location: payload.customLocation || template.location,
        type: template.type,
        creatorId: user.id,
      },
      include: {
        creator: { select: { displayName: true, email: true } },
      },
    });

    const baseUrl = getMeetingAttendanceBaseUrl(request);
    const qrCodes = await generateDualQRCodes(meeting.id, baseUrl);

    await this.prisma.meeting.update({
      where: { id: meeting.id },
      data: { qrCodeOnline: qrCodes.online, qrCodeOffline: qrCodes.offline },
    });

    if (template.templateAttendees.length > 0) {
      const requiredAttendeeData = template.templateAttendees.map((attendee) => ({
        meetingId: meeting.id,
        userId: attendee.userId,
      }));

      await this.prisma.meetingRequiredAttendee.createMany({ data: requiredAttendeeData });

      const attendanceData = template.templateAttendees
        .filter((attendee) => attendee.userId)
        .map((attendee) => ({
          userId: attendee.userId!,
          meetingId: meeting.id,
          status: 'NOT_CHECKED_IN' as const,
          checkinTime: null,
          checkinType: null,
          notes: null,
          isLate: false,
          isEarlyLeave: false,
        }));

      if (attendanceData.length > 0) {
        await this.prisma.meetingAttendance.createMany({ data: attendanceData });
      }
    }

    await this.auditLogWriter.log({
      request,
      actor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.MEETING_CREATE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.MEETING,
      statusCode: 201,
      resourceId: meeting.id,
      requestBody: {
        templateId,
        startTime: payload.startTime,
        endTime: payload.endTime,
        customTitle: payload.customTitle,
        customDescription: payload.customDescription,
        customLocation: payload.customLocation,
      },
      changes: {
        action: 'create_from_template',
        templateId,
        meetingId: meeting.id,
        title: meeting.title,
      },
    });

    return {
      meeting: { ...meeting, qrCodeOnline: qrCodes.online, qrCodeOffline: qrCodes.offline },
      message: 'Meeting created from template successfully',
    };
  }

  async updateTemplate(
    templateId: string,
    payload: {
      name?: string;
      title?: string;
      description?: string;
      duration?: string | number;
      location?: string;
      type?: string;
      isPublic?: boolean;
      attendeeIds?: string[];
    },
    userEmail: string,
    request?: Request,
    actor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    const user = await this.prisma.user.findUnique({
      where: { email: userEmail, deletedAt: null },
      include: { roles: { include: { role: true } } },
    });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    const template = await this.templateRepository.findTemplateById(templateId);
    if (!template) {
      throw new MeetingAttendanceError(404, 'Template does not exist');
    }

    const meetingRole = getMeetingRoleFromUser(user);
    if (template.creatorId !== user.id && !isMeetingAdminRole(meetingRole)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    const hasUpdateFields =
      payload.name !== undefined ||
      payload.title !== undefined ||
      payload.description !== undefined ||
      payload.duration !== undefined ||
      payload.location !== undefined ||
      payload.type !== undefined ||
      payload.isPublic !== undefined ||
      payload.attendeeIds !== undefined;

    if (!hasUpdateFields) {
      throw new MeetingAttendanceError(400, 'Update payload cannot be empty');
    }

    const updateData: Record<string, any> = {};
    if (payload.name !== undefined) updateData.name = payload.name;
    if (payload.title !== undefined) updateData.title = payload.title;
    if (payload.description !== undefined) updateData.description = payload.description;
    if (payload.location !== undefined) updateData.location = payload.location;
    if (payload.type !== undefined) updateData.type = payload.type;
    if (payload.isPublic !== undefined) updateData.isPublic = payload.isPublic;

    if (payload.duration !== undefined) {
      const durationValue = parseInt(String(payload.duration), 10);
      if (Number.isNaN(durationValue) || durationValue <= 0) {
        throw new MeetingAttendanceError(400, 'Template duration must be a positive number');
      }
      updateData.duration = durationValue;
    }

    if (Object.keys(updateData).length > 0) {
      await this.templateRepository.updateTemplate(templateId, updateData);
    }

    if (payload.attendeeIds && Array.isArray(payload.attendeeIds)) {
      await this.templateRepository.deleteTemplateAttendees(templateId);
      if (payload.attendeeIds.length > 0) {
        const attendeeData = payload.attendeeIds.map((userId) => ({
          templateId,
          userId,
        }));
        await this.templateRepository.createTemplateAttendees(attendeeData);
      }
    }

    const updatedTemplate = await this.templateRepository.findTemplateById(templateId);

    await this.auditLogWriter.log({
      request,
      actor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.TEMPLATE_UPDATE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.TEMPLATE,
      statusCode: 200,
      resourceId: templateId,
      requestBody: payload as Record<string, unknown>,
      changes: {
        action: 'update',
        before: template,
        after: updatedTemplate,
      },
    });
    return { template: updatedTemplate };
  }

  async deleteTemplate(
    templateId: string,
    userEmail: string,
    request?: Request,
    actor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    const user = await this.prisma.user.findUnique({
      where: { email: userEmail, deletedAt: null },
      include: { roles: { include: { role: true } } },
    });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    const template = await this.templateRepository.findTemplateById(templateId);
    if (!template) {
      throw new MeetingAttendanceError(404, 'Template does not exist');
    }

    const meetingRole = getMeetingRoleFromUser(user);
    if (template.creatorId !== user.id && !isMeetingAdminRole(meetingRole)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    await this.templateRepository.deleteTemplateAttendees(templateId);
    await this.templateRepository.deleteTemplate(templateId);

    await this.auditLogWriter.log({
      request,
      actor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.TEMPLATE_DELETE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.TEMPLATE,
      statusCode: 200,
      resourceId: templateId,
      changes: {
        action: 'delete',
        templateTitle: template.title,
        deletedData: template,
      },
    });

    return { message: 'Template deleted successfully' };
  }
}
