import { logger } from '@goodgo/logger';
import { PrismaClient } from '@prisma/client';
import { Request } from 'express';

import { getPrismaClient } from '../../../config/database.config';
import { auditService } from '../../../core/events/audit.service';
import { NotFoundError, ConflictError } from '../../../errors/http-error';
import { GroupRepository } from '../../../repositories/group.repository';
import { OrganizationRepository } from '../../../repositories/organization.repository';
import { CreateGroupDto, UpdateGroupDto } from '../identity.dto';

/**
 * EN: Group Service
 * VI: Service nhóm
 */
export class GroupService {
  private prisma: PrismaClient;
  private groupRepo: GroupRepository;
  private orgRepo: OrganizationRepository;

  constructor() {
    this.prisma = getPrismaClient();
    this.groupRepo = new GroupRepository(this.prisma);
    this.orgRepo = new OrganizationRepository(this.prisma);
  }

  /**
   * EN: Create group
   * VI: Tạo nhóm
   */
  async create(organizationId: string | undefined, data: CreateGroupDto, req?: Request): Promise<any> {
    // Verify organization exists if provided
    if (organizationId) {
      const org = await this.orgRepo.findById(organizationId);
      if (!org) {
        throw new NotFoundError('Organization');
      }
    }

    // Check if group name already exists in organization
    // EN: Handle null organizationId for unique constraint
    // VI: Xử lý organizationId null cho unique constraint
    const existing = organizationId
      ? await this.prisma.group.findUnique({
          where: {
            organizationId_name: {
              organizationId,
              name: data.name,
            },
          },
        })
      : await this.prisma.group.findFirst({
          where: {
            organizationId: null,
            name: data.name,
          },
        });

    if (existing) {
      throw new ConflictError('Group name already exists / Tên nhóm đã tồn tại');
    }

    const group = await this.groupRepo.create({
      name: data.name,
      organizationId: organizationId || null,
      description: data.description || null,
      isActive: true,
    });

    if (req) {
      await auditService.logFromRequest(req, 'GROUP_CREATED', {
        groupId: group.id,
        organizationId,
        success: true,
      });
    }

    logger.info('Group created', { groupId: group.id, organizationId });

    return group;
  }

  /**
   * EN: Get group by ID
   * VI: Lấy nhóm theo ID
   */
  async getById(id: string): Promise<any> {
    const group = await this.groupRepo.findById(id);
    if (!group) {
      throw new NotFoundError('Group');
    }
    return group;
  }

  /**
   * EN: Get groups by organization
   * VI: Lấy các nhóm theo tổ chức
   */
  async getByOrganization(organizationId: string): Promise<any[]> {
    return this.groupRepo.findByOrganization(organizationId);
  }

  /**
   * EN: Update group
   * VI: Cập nhật nhóm
   */
  async update(id: string, data: UpdateGroupDto, req?: Request): Promise<any> {
    const group = await this.groupRepo.findById(id);
    if (!group) {
      throw new NotFoundError('Group');
    }

    // Check name conflict if name is being changed
    if (data.name && data.name !== group.name) {
      // EN: Handle null organizationId for unique constraint
      // VI: Xử lý organizationId null cho unique constraint
      const existing = group.organizationId
        ? await this.prisma.group.findUnique({
            where: {
              organizationId_name: {
                organizationId: group.organizationId,
                name: data.name,
              },
            },
          })
        : await this.prisma.group.findFirst({
            where: {
              organizationId: null,
              name: data.name,
            },
          });

      if (existing) {
        throw new ConflictError('Group name already exists / Tên nhóm đã tồn tại');
      }
    }

    const updated = await this.groupRepo.update(id, {
      ...(data.name && { name: data.name }),
      ...(data.description !== undefined && { description: data.description }),
      ...(data.isActive !== undefined && { isActive: data.isActive }),
    });

    if (req) {
      await auditService.logFromRequest(req, 'GROUP_UPDATED', {
        groupId: updated.id,
        changes: data,
        success: true,
      });
    }

    logger.info('Group updated', { groupId: updated.id });

    return updated;
  }

  /**
   * EN: Delete group
   * VI: Xóa nhóm
   */
  async delete(id: string, req?: Request): Promise<boolean> {
    const group = await this.groupRepo.findById(id);
    if (!group) {
      throw new NotFoundError('Group');
    }

    // Check if group has members
    const memberCount = await this.groupRepo.getMemberCount(id);
    if (memberCount > 0) {
      throw new ConflictError('Cannot delete group with members / Không thể xóa nhóm có thành viên');
    }

    await this.groupRepo.delete(id);

    if (req) {
      await auditService.logFromRequest(req, 'GROUP_DELETED', {
        groupId: id,
        success: true,
      });
    }

    logger.info('Group deleted', { groupId: id });

    return true;
  }

  /**
   * EN: Add member to group
   * VI: Thêm thành viên vào nhóm
   */
  async addMember(groupId: string, userId: string, role: string = 'member', expiresAt?: Date, req?: Request): Promise<any> {
    const group = await this.groupRepo.findById(groupId);
    if (!group) {
      throw new NotFoundError('Group');
    }

    const user = await this.prisma.user.findUnique({ where: { id: userId } });
    if (!user) {
      throw new NotFoundError('User');
    }

    await this.groupRepo.addMember(groupId, userId, role, expiresAt);

    if (req) {
      await auditService.logFromRequest(req, 'USER_ADDED_TO_GROUP', {
        groupId,
        userId,
        role,
        success: true,
      });
    }

    logger.info('Member added to group', { groupId, userId, role });

    return { groupId, userId, role };
  }

  /**
   * EN: Remove member from group
   * VI: Xóa thành viên khỏi nhóm
   */
  async removeMember(groupId: string, userId: string, req?: Request): Promise<boolean> {
    const group = await this.groupRepo.findById(groupId);
    if (!group) {
      throw new NotFoundError('Group');
    }

    await this.groupRepo.removeMember(groupId, userId);

    if (req) {
      await auditService.logFromRequest(req, 'USER_REMOVED_FROM_GROUP', {
        groupId,
        userId,
        success: true,
      });
    }

    logger.info('Member removed from group', { groupId, userId });

    return true;
  }

  /**
   * EN: Get group members
   * VI: Lấy thành viên của nhóm
   */
  async getMembers(groupId: string): Promise<any[]> {
    return this.groupRepo.getMembers(groupId);
  }

  /**
   * EN: Assign permission to group
   * VI: Gán quyền cho nhóm
   */
  async assignPermission(groupId: string, permissionId: string, req?: Request): Promise<void> {
    const group = await this.groupRepo.findById(groupId);
    if (!group) {
      throw new NotFoundError('Group');
    }

    await this.groupRepo.assignPermission(groupId, permissionId);

    if (req) {
      await auditService.logFromRequest(req, 'PERMISSION_ASSIGNED_TO_GROUP', {
        groupId,
        permissionId,
        success: true,
      });
    }

    logger.info('Permission assigned to group', { groupId, permissionId });
  }

  /**
   * EN: Revoke permission from group
   * VI: Thu hồi quyền khỏi nhóm
   */
  async revokePermission(groupId: string, permissionId: string, req?: Request): Promise<void> {
    const group = await this.groupRepo.findById(groupId);
    if (!group) {
      throw new NotFoundError('Group');
    }

    await this.groupRepo.revokePermission(groupId, permissionId);

    if (req) {
      await auditService.logFromRequest(req, 'PERMISSION_REVOKED_FROM_GROUP', {
        groupId,
        permissionId,
        success: true,
      });
    }

    logger.info('Permission revoked from group', { groupId, permissionId });
  }
}

export const groupService = new GroupService();
