import { Types, PipelineStage, FilterQuery } from 'mongoose';

import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import { Role } from '@/modules/roles/roles.model';
import {
  IRoleDoc,
  IRoleFilter,
  NewCreatedRole,
} from '@/modules/roles/roles.interface';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { CommonTypes, Status } from '@/shared/constants/enum.constant';
import { Permissions } from '../permissions/permissions.model';
import { safeDeleteById } from '@/shared/utils/guard/ref-guard';
import User from '../user/user.model';
import { getObjectId } from '@/shared/utils/commonHelper';

const { RoleResponseCodes } = responseCodes;

export const createRole = async (data: NewCreatedRole): Promise<boolean> => {
  const role = new Role(data);

  if (data?.parentRole) {
    const parent = await Role.findById(data.parentRole)
      .populate('permissions')
      .lean();

    if (!parent)
      throw new ApiError(
        defaultStatus.OK,
        'Parent role not found',
        true,
        '',
        RoleResponseCodes.ROLE_NOT_FOUND,
      );

    const inheritedPermissions = await Promise.all(
      parent.permissions.map(async (perm) => {
        const { _id, ...rest } = perm;
        const payload = {
          ...rest,
          inherited: true,
        };

        const newPermission = new Permissions(payload);
        return await newPermission.save();
      }),
    );

    role.permissions = [
      ...((role.permissions as Types.ObjectId[]) || []),
      ...(inheritedPermissions.map((perm) => perm._id) as Types.ObjectId[]),
    ];
  }

  const saved = await role.save();
  if (!saved)
    throw new ApiError(
      defaultStatus.OK,
      'Failed to create role',
      true,
      '',
      RoleResponseCodes.ROLE_CREATION_FAILED,
    );

  return true;
};

export const getRoleById = async (id: string) => {
  const role = await Role.findById(id);
  if (!role)
    throw new ApiError(
      defaultStatus.OK,
      'Role not found',
      true,
      '',
      RoleResponseCodes.ROLE_NOT_FOUND,
    );

  return role;
};

export const updateRole = async ({
  name,
  description,
  company,
  id,
}: {
  name: string;
  description?: string;
  company: Types.ObjectId;
  id?: string;
}): Promise<IRoleDoc | null> => {
  try {
    if (id) {
      const roleWithSameName = await Role.findOne({
        name: new RegExp(`^${name}$`, 'i'),
        company: getObjectId(company),
      });

      if (roleWithSameName)
        throw new ApiError(
          defaultStatus.CONFLICT,
          'Role with same name already exists',
          true,
          '',
          RoleResponseCodes.ROLE_ALREADY_EXISTS,
        );
      const existingRole = await Role.findById(id);
      if (existingRole && existingRole.name === CommonTypes.ADMIN)
        throw new ApiError(
          defaultStatus.CONFLICT,
          'Cannot update Admin role',
          true,
          '',
          RoleResponseCodes.ROLE_UPDATE_FORBIDDEN,
        );
    }
    const updateData: Partial<IRoleDoc> = { name, company: company};
    if (description !== undefined) updateData.description = description;

    const role = await Role.findOneAndUpdate(
      id ? { _id: id } : { name, company: company },
      id ? { $set: updateData } : { $setOnInsert: updateData },
      {
        new: true,
        upsert: !id,
        runValidators: true,
      },
    );

    if (!role)
      throw new ApiError(
        defaultStatus.CONFLICT,
        id ? 'Role not found' : 'Failed to create role',
        true,
        '',
        RoleResponseCodes.ROLE_NOT_FOUND,
      );

    return role;
  } catch (_error) {
    console.log('🚀 ~ updateRole ~ _error:', _error);
    if (_error instanceof ApiError) throw _error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update role',
      true,
      '',
      RoleResponseCodes.ROLE_ERROR,
    );
  }
};

export const deleteRole = async (id: string): Promise<boolean> => {
  try {
    const isUsedByActiveUser = await User.exists({
      'company.role': getObjectId(id),
      $or: [{ status: Status.ACTIVE }],
    });

    if (isUsedByActiveUser)
      throw new ApiError(
        defaultStatus.CONFLICT,
        'Role is assigned to active user(s). Remove it from those users or deactivate them before deleting.',
        true,
        '',
        RoleResponseCodes.ROLE_IN_USE,
      );

    await safeDeleteById(Role, id, RoleResponseCodes.ROLE_IN_USE);
    return true;
  } catch (error) {
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.CONFLICT,
      'Failed to delete role',
      true,
      '',
      RoleResponseCodes.ROLE_ERROR,
    );
  }
};

export const queryRoles = async (filter: IRoleFilter = {}, options = {}) => {
  const { search, ...otherFilters } = filter;

  let updatedFilter: FilterQuery<IRoleDoc> = {
    ...otherFilters,
  };

  if (search)
    updatedFilter.$and = [
      { name: { $ne: CommonTypes.SUPERADMIN } },
      {
        $or: [
          { name: { $regex: search, $options: 'i' } },
          { description: { $regex: search, $options: 'i' } },
        ],
      },
    ];
  else updatedFilter.name = { $ne: CommonTypes.SUPERADMIN };

  const pipeline: PipelineStage[] = [
    {
      $match: updatedFilter,
    },

    {
      $lookup: {
        from: 'users',
        let: { roleId: '$_id' },
        pipeline: [
          {
            $match: {
              $expr: { $in: ['$$roleId', '$company.role'] },
            },
          },
        ],
        as: 'assignedUsers',
      },
    },

    {
      $lookup: {
        from: 'permissions',
        localField: 'permissions',
        foreignField: '_id',
        as: 'permissionDocs',
      },
    },

    {
      $addFields: {
        filteredPermissions: {
          $filter: {
            input: '$permissionDocs',
            cond: {
              $not: {
                $in: ['$this.group', ['Dashboard', 'Settings', 'Support']],
              },
            },
          },
        },
      },
    },

    {
      $addFields: {
        activePermissionCount: {
          $sum: {
            $map: {
              input: '$permissionDocs',
              as: 'permDoc',
              in: {
                $cond: {
                  if: {
                    $and: [
                      {
                        $not: {
                          $in: [
                            '$$permDoc.group',
                            ['Dashboard', 'Settings', 'Support'],
                          ],
                        },
                      },
                      { $isArray: '$$permDoc.permissions' },
                    ],
                  },
                  then: {
                    $size: {
                      $filter: {
                        input: '$$permDoc.permissions',
                        as: 'perm',
                        cond: {
                          $and: [
                            { $isArray: '$$perm.access' },
                            { $gt: [{ $size: '$$perm.access' }, 0] },
                          ],
                        },
                      },
                    },
                  },
                  else: 0,
                },
              },
            },
          },
        },
      },
    },

    {
      $project: {
        name: 1,
        description: 1,
        usersCount: { $size: '$assignedUsers' },
        activePermissionCount: 1,
        assignedUserIds: '$assignedUsers._id',
        createdAt: 1,
        updatedAt: 1,
        parentRole: 1,
      },
    },

    {
      $sort: { usersCount: -1 },
    },
  ];

  return Role.paginate(
    {},
    {
      ...options,
      aggregation: pipeline,
    },
  );
};
