import { Types } from 'mongoose';

import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import {
  BROKER_SEED_PERMISSIONS,
  BUILDER_SEED_PERMISSIONS,
  COMPANY_SEED_PERMISSIONS,
  COMPANY_SETTINGS_SEED_PERMISSIONS,
  DASHBOARD_PERMISSIONS,
} from '@/modules/permissions/permissions.constant';
import { Role } from '@/modules/roles/roles.model';
import { CompanyType } from '@/shared/constants/enum.constant';
import {
  IPermissionDoc,
  NewCreatedPermission,
} from '@/modules/permissions/permissions.interface';
import { CONSTANTS } from '@/shared/constants';
import {
  addSeedingPermissions,
  handleNonSeedingPermissions,
  processPermissions,
} from './permissions.helper';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import User from '@/modules/user/user.model';

const { PermissionResponseCodes } = responseCodes;

export const getSeedPermissionsForCompanyType = (
  companyType: string,
): NewCreatedPermission => {
  const basePermissions = COMPANY_SEED_PERMISSIONS;
  const typeBasedPermissions =
    companyType === CompanyType.BROKER
      ? BROKER_SEED_PERMISSIONS
      : BUILDER_SEED_PERMISSIONS;

  const settingsPermissions = COMPANY_SETTINGS_SEED_PERMISSIONS;

  return [...basePermissions, ...typeBasedPermissions, ...settingsPermissions];
};

export const addOrUpdatePermission = async (
  companyId: Types.ObjectId,
  roleId: Types.ObjectId,
  payload: NewCreatedPermission,
  isCompanySeeding: boolean = false,
) => {
  try {
    // Get current role permissions once at the start
    const currentRole = await Role.findById(roleId).select('permissions name');

    const currentPermissionIds = currentRole?.permissions || [];

    // Process payload based on seeding type
    const processedPayload = isCompanySeeding
      ? addSeedingPermissions(payload)
      : await handleNonSeedingPermissions(
          payload,
          companyId,
          roleId,
          currentPermissionIds,
        );

    const finalPayload = [...processedPayload, ...DASHBOARD_PERMISSIONS];

    // Process all permissions in the final payload
    const newPermissionIds = await processPermissions(
      finalPayload,
      currentPermissionIds,
      companyId,
    );

    // Update role with new permissions (only if there are new ones)
    if (newPermissionIds.length > 0)
      await Role.findByIdAndUpdate(
        roleId,
        { $addToSet: { permissions: { $each: newPermissionIds } } },
        { new: true },
      );

    await User.updateMany(
      {
        'company.id': companyId,
        'company.role': roleId,
      },
      { $set: { lastPermissionUpdate: new Date() } },
    );
  } catch (_error) {
    if (_error instanceof ApiError) throw _error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update permission',
      true,
      '',
      PermissionResponseCodes.PERMISSION_ERROR,
    );
  }
};

export const getRoleWithPermissions = async (
  companyId: Types.ObjectId,
  roleId: Types.ObjectId,
  companyType?: string,
) => {
  const role = await Role.findOne({
    _id: roleId,
    company: companyId,
  }).populate<{ permissions: IPermissionDoc[] }>('permissions');

  let permissions = role?.permissions as IPermissionDoc[];
  const allPermissions = getSeedPermissionsForCompanyType(companyType);

  // If no permissions found, create default permissions with NO_ACCESS
  if (!permissions || permissions.length === 0) {
    permissions = allPermissions.map((group) => ({
      group: group.group,
      groupAccess: CONSTANTS.NO_ACCESS,
      permissions: group.permissions.map((p) => ({
        id: p.id,
        key: p.key,
        moduleName: p.moduleName,
        access: CONSTANTS.NO_ACCESS,
      })),
      company: companyId,
    })) as IPermissionDoc[];
  } else {
    // Check for missing groups and add them with NO_ACCESS
    const existingGroups = new Set(permissions.map((p) => p.group));
    const missingGroups = allPermissions.filter(
      (group) => !existingGroups.has(group.group),
    );

    // Add missing groups with NO_ACCESS
    const missingPermissions = missingGroups.map((group) => ({
      group: group.group,
      groupAccess: CONSTANTS.NO_ACCESS,
      permissions: group.permissions.map((p) => ({
        id: p.id,
        key: p.key,
        moduleName: p.moduleName,
        access: CONSTANTS.NO_ACCESS,
      })),
      company: companyId,
    })) as IPermissionDoc[];

    permissions = [...permissions, ...missingPermissions];

    // Also check for missing permissions within existing groups
    permissions = permissions.map((existingGroup) => {
      const seedGroup = allPermissions.find(
        (sg) => sg.group === existingGroup.group,
      );
      if (!seedGroup) return existingGroup;

      const existingPermIds = new Set(
        existingGroup.permissions.map((p) => p.id),
      );
      const missingPerms = seedGroup.permissions.filter(
        (sp) => !existingPermIds.has(sp.id),
      );

      if (missingPerms.length > 0) {
        const additionalPerms = missingPerms.map((p) => ({
          id: p.id,
          key: p.key,
          moduleName: p.moduleName,
          access: CONSTANTS.NO_ACCESS,
        }));

        return {
          ...existingGroup,
          permissions: [...existingGroup.permissions, ...additionalPerms],
        };
      }

      return existingGroup;
    }) as IPermissionDoc[];
  }

  const flatPermissions = permissions.flatMap((group) =>
    group.permissions.map((perm) => ({
      id: perm.id,
      group: group.group,
      key: perm.key,
      moduleName: perm.moduleName,
      access: perm.access,
    })),
  );

  return flatPermissions;
};
