import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { MeetingAttendanceError } from '../errors/meeting-attendance.error';
import { getMeetingRoleFromUser, getMeetingRolePriorityList, isMeetingAdminRole, normalizeMeetingRole } from '../utils/meeting-roles';
import { Prisma } from '@prisma/client';
import {
  MEETING_ATTENDANCE_AUDIT_ACTIONS,
  MEETING_ATTENDANCE_AUDIT_RESOURCES,
} from '../constants/audit';
import { MeetingAttendanceAuditLogWriter } from './audit-log-writer.service';
import type { Request } from 'express';

const MEETING_ROLE_CODES = getMeetingRolePriorityList();

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

  async getUserByEmail(email: string) {
    const user = await this.prisma.user.findUnique({
      where: { email, deletedAt: null },
      include: {
        roles: { include: { role: true } },
        departmentMemberships: {
          where: { leftAt: null },
          include: {
            department: { select: { id: true, name: true, code: true } },
            position: { select: { id: true, name: true, level: true } },
          },
        },
      },
    });

    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    const meetingRole = getMeetingRoleFromUser(user);
    if (!meetingRole) {
      throw new MeetingAttendanceError(403, 'User does not have meeting attendance access');
    }

    return { ...user, meetingRole };
  }

  async listUsers(query: { page?: string; limit?: string; search?: string; role?: string; department?: string }) {
    const page = parseInt(query.page || '1', 10);
    const limit = parseInt(query.limit || '10', 10);
    const skip = (page - 1) * limit;

    const where: Prisma.UserWhereInput = {
      deletedAt: null,
    };

    const filters: Prisma.UserWhereInput[] = [
      {
        roles: {
          some: {
            role: {
              code: { in: MEETING_ROLE_CODES },
            },
          },
        },
      },
    ];

    if (query.search) {
      where.OR = [
        { displayName: { contains: query.search, mode: 'insensitive' } },
        { email: { contains: query.search, mode: 'insensitive' } },
        { username: { contains: query.search, mode: 'insensitive' } },
        { employeeId: { contains: query.search, mode: 'insensitive' } },
      ];
    }

    if (query.role) {
      const parsedRole = normalizeMeetingRole(query.role);
      if (parsedRole) {
        filters.push({
          roles: {
            some: {
              role: {
                code: parsedRole,
              },
            },
          },
        });
      }
    }

    if (query.department) {
      filters.push({
        departmentMemberships: {
          some: {
            leftAt: null,
            department: {
              name: { contains: query.department, mode: 'insensitive' },
            },
          },
        },
      });
    }

    if (filters.length > 0) {
      where.AND = filters;
    }

    const [users, total] = await Promise.all([
      this.prisma.user.findMany({
        where,
        include: {
          roles: { include: { role: true } },
          departmentMemberships: {
            where: { leftAt: null },
            include: {
              department: { select: { id: true, name: true, code: true } },
              position: { select: { id: true, name: true, level: true } },
            },
          },
        },
        skip,
        take: limit,
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.user.count({ where }),
    ]);

    const userIds = users.map((user) => user.id);

    const [meetingCounts, attendanceCounts] = await Promise.all([
      this.prisma.meeting.groupBy({
        by: ['creatorId'],
        _count: { _all: true },
        where: { creatorId: { in: userIds } },
      }),
      this.prisma.meetingAttendance.groupBy({
        by: ['userId'],
        _count: { _all: true },
        where: { userId: { in: userIds } },
      }),
    ]);

    const meetingCountMap = new Map(meetingCounts.map((item) => [item.creatorId, item._count._all]));
    const attendanceCountMap = new Map(attendanceCounts.map((item) => [item.userId, item._count._all]));

    const departments = new Set<string>();

    const formattedUsers = users.map((user) => {
      const primaryMembership =
        user.departmentMemberships?.find((membership) => membership.isPrimary) ||
        user.departmentMemberships?.[0];

      if (primaryMembership?.department?.name) {
        departments.add(primaryMembership.department.name);
      }

      const meetingRole = getMeetingRoleFromUser(user);

      return {
        id: user.id,
        email: user.email,
        displayName: user.displayName,
        role: meetingRole || 'Employee',
        department: primaryMembership?.department || null,
        position: primaryMembership?.position || null,
        isActive: user.status === 'ACTIVE',
        createdAt: user.createdAt,
        createdMeetingsCount: meetingCountMap.get(user.id) || 0,
        attendancesCount: attendanceCountMap.get(user.id) || 0,
      };
    });

    return {
      users: formattedUsers,
      pagination: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit),
      },
      departments: Array.from(departments),
    };
  }

  async createUser(
    payload: { email?: string; userId?: string; role?: string },
    request?: Request,
    actor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    if (!payload.email && !payload.userId) {
      throw new MeetingAttendanceError(400, 'Email or userId is required');
    }

    const role = normalizeMeetingRole(payload.role) || 'Employee';

    const user = payload.userId
      ? await this.prisma.user.findUnique({ where: { id: payload.userId, deletedAt: null } })
      : await this.prisma.user.findUnique({ where: { email: payload.email!, deletedAt: null } });

    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    await this.updateMeetingRoles(user.id, role);

    await this.auditLogWriter.log({
      request,
      actor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.USER_CREATE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.USER,
      statusCode: 201,
      resourceId: user.id,
      requestBody: payload as Record<string, unknown>,
      changes: {
        action: 'assign_meeting_role',
        userId: user.id,
        userEmail: user.email,
        role,
      },
    });

    return {
      user: {
        id: user.id,
        email: user.email,
        displayName: user.displayName,
        role,
      },
      message: 'Meeting attendance role assigned successfully',
    };
  }

  async getUserDetails(id: string) {
    const user = await this.prisma.user.findUnique({
      where: { id, deletedAt: null },
      include: {
        roles: { include: { role: true } },
        departmentMemberships: {
          where: { leftAt: null },
          include: {
            department: { select: { id: true, name: true, code: true } },
            position: { select: { id: true, name: true, level: true } },
          },
        },
      },
    });

    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    const meetingRole = getMeetingRoleFromUser(user) || 'Employee';
    const primaryMembership =
      user.departmentMemberships?.find((membership) => membership.isPrimary) || user.departmentMemberships?.[0];

    const [createdMeetings, attendances] = await Promise.all([
      this.prisma.meeting.findMany({
        where: { creatorId: user.id },
        select: { id: true, title: true, startTime: true, status: true },
        orderBy: { startTime: 'desc' },
        take: 10,
      }),
      this.prisma.meetingAttendance.findMany({
        where: { userId: user.id },
        select: {
          id: true,
          status: true,
          checkinTime: true,
          meeting: { select: { id: true, title: true, startTime: true } },
        },
        orderBy: { createdAt: 'desc' },
        take: 10,
      }),
    ]);

    return {
      id: user.id,
      email: user.email,
      displayName: user.displayName,
      role: meetingRole,
      department: primaryMembership?.department || null,
      position: primaryMembership?.position || null,
      isActive: user.status === 'ACTIVE',
      createdAt: user.createdAt,
      createdMeetings,
      attendances,
    };
  }

  async updateUser(
    id: string,
    payload: { role?: string },
    actor: { id: string; role: string },
    request?: Request,
    auditActor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    if (!isMeetingAdminRole(actor.role as any)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    const user = await this.prisma.user.findUnique({ where: { id, deletedAt: null } });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    const role = normalizeMeetingRole(payload.role);
    if (!role) {
      throw new MeetingAttendanceError(400, 'Invalid role');
    }

    await this.updateMeetingRoles(user.id, role);

    await this.auditLogWriter.log({
      request,
      actor: auditActor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.USER_UPDATE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.USER,
      statusCode: 200,
      resourceId: user.id,
      requestBody: payload as Record<string, unknown>,
      changes: {
        action: 'update_role',
        userId: user.id,
        userEmail: user.email,
        role,
      },
    });

    return { message: 'Meeting attendance role updated successfully' };
  }

  async disableUser(
    id: string,
    actor: { id: string; role: string },
    request?: Request,
    auditActor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    if (!isMeetingAdminRole(actor.role as any)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    if (actor.id === id) {
      throw new MeetingAttendanceError(400, 'You cannot remove your own access');
    }

    const user = await this.prisma.user.findUnique({ where: { id, deletedAt: null } });
    if (!user) {
      throw new MeetingAttendanceError(404, 'User does not exist');
    }

    await this.prisma.userRole.deleteMany({
      where: {
        userId: id,
        role: {
          code: { in: MEETING_ROLE_CODES },
        },
      },
    });

    await this.auditLogWriter.log({
      request,
      actor: auditActor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.USER_DISABLE,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.USER,
      statusCode: 200,
      resourceId: id,
      changes: {
        action: 'disable',
        userEmail: user.email,
        userName: user.displayName,
      },
    });

    return { message: 'Meeting attendance access removed' };
  }

  async searchUsers(query: string, limit: string) {
    const parsedLimit = parseInt(limit || '20', 10);
    const users = await this.prisma.user.findMany({
      where: {
        deletedAt: null,
        status: 'ACTIVE',
        OR: [
          { displayName: { contains: query, mode: 'insensitive' } },
          { email: { contains: query, mode: 'insensitive' } },
          { username: { contains: query, mode: 'insensitive' } },
        ],
      },
      include: {
        departmentMemberships: {
          where: { leftAt: null },
          include: {
            department: { select: { id: true, name: true, code: true } },
            position: { select: { id: true, name: true, level: true } },
          },
        },
      },
      take: parsedLimit,
      orderBy: { displayName: 'asc' },
    });

    const results = users.map((user) => {
      const primaryMembership =
        user.departmentMemberships?.find((membership) => membership.isPrimary) || user.departmentMemberships?.[0];

      return {
        id: user.id,
        displayName: user.displayName,
        email: user.email,
        department: primaryMembership?.department || null,
        position: primaryMembership?.position || null,
      };
    });

    return { users: results };
  }

  async importUsers(
    payload: { users?: Array<{ email?: string; role?: string }> },
    request?: Request,
    actor?: { userId?: string; id?: string; email?: string; displayName?: string; username?: string; roles?: any[] },
  ) {
    if (!payload.users || !Array.isArray(payload.users) || payload.users.length === 0) {
      throw new MeetingAttendanceError(400, 'User data cannot be empty');
    }

    const results = {
      success: 0,
      failed: 0,
      errors: [] as string[],
      updated: [] as Array<{ email: string; role: string }>,
    };

    for (let i = 0; i < payload.users.length; i += 1) {
      const userData = payload.users[i];
      const rowNumber = i + 2;

      try {
        if (!userData.email) {
          results.failed += 1;
          results.errors.push(`Row ${rowNumber}: Email cannot be empty`);
          continue;
        }

        const role = normalizeMeetingRole(userData.role) || 'Employee';

        const user = await this.prisma.user.findUnique({
          where: { email: userData.email.trim().toLowerCase(), deletedAt: null },
        });

        if (!user) {
          results.failed += 1;
          results.errors.push(`Row ${rowNumber}: User ${userData.email} does not exist`);
          continue;
        }

        await this.updateMeetingRoles(user.id, role);

        results.success += 1;
        results.updated.push({ email: user.email, role });
      } catch (error) {
        results.failed += 1;
        results.errors.push(
          `Row ${rowNumber}: Failed to assign role - ${error instanceof Error ? error.message : 'Unknown error'}`,
        );
      }
    }

    await this.auditLogWriter.log({
      request,
      actor,
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.USER_IMPORT,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.USER,
      statusCode: 200,
      requestBody: { total: payload.users.length },
      changes: {
        success: results.success,
        failed: results.failed,
        updated: results.updated.map((item) => item.email),
        errors: results.errors,
      },
    });

    return {
      message: `Batch import completed, ${results.success} succeeded, ${results.failed} failed`,
      results,
    };
  }

  async resetPassword() {
    throw new MeetingAttendanceError(400, 'Please reset passwords from the organization module');
  }

  async permanentDeleteUser(id: string, actor: { id: string; role: string }) {
    if (!isMeetingAdminRole(actor.role as any)) {
      throw new MeetingAttendanceError(403, 'Insufficient permissions');
    }

    if (actor.id === id) {
      throw new MeetingAttendanceError(400, 'You cannot remove your own access');
    }

    await this.prisma.userRole.deleteMany({
      where: {
        userId: id,
        role: {
          code: { in: MEETING_ROLE_CODES },
        },
      },
    });

    return { message: 'Meeting attendance access removed' };
  }

  private async updateMeetingRoles(userId: string, roleCode: string) {
    const role = await this.prisma.role.findUnique({ where: { code: roleCode } });
    if (!role) {
      throw new MeetingAttendanceError(404, 'Role does not exist');
    }

    await this.prisma.$transaction([
      this.prisma.userRole.deleteMany({
        where: {
          userId,
          role: {
            code: { in: MEETING_ROLE_CODES },
          },
        },
      }),
      this.prisma.userRole.create({
        data: {
          userId,
          roleId: role.id,
          organizationId: null,
        },
      }),
    ]);
  }
}
