import mongoose, { Types } from 'mongoose';

import {
  Lead,
  BuyLead,
  SellLead,
  RentLead,
  LeaseLead,
} from '@/modules/lead/lead.model';

import {
  ILeadDoc,
  NewCreatedLead,
  IBuyLead,
  ISellLead,
  IRentLead,
  ILeaseLead,
  UpdateLeadBody,
  PopulatedLead,
} from '@/modules/lead/lead.interface';

import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { computeLeadTrendsWithChange } from './lead.helper';
import { Contact } from '../contacts/contacts.model';
import { validateCustomFields } from '@/shared/utils/customFieldValidation';
import { CustomFormNames } from '../customFields/customFields.constant';
import { getObjectId, parseSafeIds } from '@/shared/utils/commonHelper';
import {
  getConversionRatePipeline,
  getLeadScoreBucketPipeline,
  getLeadStageBreakdownPipeline,
  getMonthlyLeadTotalsPipeline,
} from './leadAnalytics.pipeline';
import {
  deleteWithReferences,
  safeDeleteById,
} from '@/shared/utils/guard/ref-guard';
import { individualProperties } from '../individualProperties';
import * as rulesService from '@/modules/rules/rules.service';

import { TemplateName } from '@/shared/constants';
import config from '@/shared/config/config';
import { Team } from '../teams/teams.model';
import { sendEmailWithActiveTemplate } from '../communication/email/email.helper';
import { Project } from '../project';
import { LeadScoreConfig } from '../leadScore/leadScore.model';
import { createNotification } from '../notification/notification.service';
import Source from '../master/constructionStatus/source/source.model';
import {
  NotificationStatus,
  NotificationType,
  UserType,
} from '../notification/notification.constant';
import { Activity } from '../activity/activity.model';
import { ActivityStatus } from '@/shared/constants/enum.constant';
import { Tasks } from '../tasks/tasks.model';
import { TaskActivityType } from '../tasks/tasks.constant';
import { LeadStage } from '../master/leadStage/leadStage.model';
import User from '@/modules/user/user.model';

const { LeadResponseCodes } = responseCodes;

const isClosedLeadStage = (stageName?: string | null): boolean => {
  const normalized = stageName?.toLowerCase()?.trim();
  if (!normalized) return false;
  return normalized.includes('won');
};

/**
 * assignedTo may be a string/array from query params, or already normalized by
 * the lead controller (ObjectId or { $in: ObjectId[] }). parseSafeIds does not
 * handle plain { $in: ... } objects, which would drop the filter.
 */
const applyAssignedToFilter = (
  finalFilter: Record<string, unknown>,
  assignedTo: unknown,
): void => {
  if (!assignedTo) return;
  if (
    assignedTo instanceof Types.ObjectId ||
    (typeof assignedTo === 'object' && assignedTo !== null && '$in' in assignedTo)
  ) {
    finalFilter.assignedTo = assignedTo;
  } else {
    const parsed = parseSafeIds(assignedTo);
    if (parsed !== undefined) finalFilter.assignedTo = parsed;
  }
};

const normalizeUserTypeForRule = (v?: string): string =>
  String(v || '')
    .toLowerCase()
    .replace(/[\s_-]/g, '');

export const assertLeadReassignmentAllowedByTeamRule = async ({
  companyId,
  oldAssigneeId,
  newAssigneeId,
  requestingUserType,
}: {
  companyId: string | Types.ObjectId;
  oldAssigneeId: string | null | undefined;
  /** Accepts unknown because update payloads use Partial<ILead> / filtered records. */
  newAssigneeId: unknown;
  requestingUserType?: string;
}): Promise<void> => {
  if (normalizeUserTypeForRule(requestingUserType) === 'superadmin') return;

  if (newAssigneeId === undefined || newAssigneeId === null) return;

  const newIdStr =
    newAssigneeId instanceof Types.ObjectId
      ? newAssigneeId.toString()
      : typeof newAssigneeId === 'string'
        ? newAssigneeId
        : String(newAssigneeId);

  if (!Types.ObjectId.isValid(newIdStr))
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Invalid assignee id',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );

  if (!oldAssigneeId || oldAssigneeId === newIdStr) return;

  const rules = (await rulesService.getRules(
    getObjectId(companyId).toString(),
    'transfer_leads',
  )) as Record<string, boolean>;

  const canTransferAcrossTeams = rules.transfer_leads ?? true;

  if (canTransferAcrossTeams) return;

  const companyOid = getObjectId(companyId);
  const oldOid = getObjectId(oldAssigneeId);
  const newOid = getObjectId(newIdStr);

  const [teamsOld, teamsNew] = await Promise.all([
    Team.find({
      companyId: companyOid,
      $or: [{ lead: oldOid }, { members: oldOid }],
    })
      .select('_id')
      .lean(),
    Team.find({
      companyId: companyOid,
      $or: [{ lead: newOid }, { members: newOid }],
    })
      .select('_id')
      .lean(),
  ]);

  const oldTeamIds = new Set(teamsOld.map((t) => t._id.toString()));
  const hasSharedTeam = teamsNew.some((t) => oldTeamIds.has(t._id.toString()));

  if (!hasSharedTeam)
    throw new ApiError(
      defaultStatus.FORBIDDEN,
      'Cannot reassign lead to a user from another team. Cross-team lead transfers are disabled for your company.',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
};

export const getLeadActivityCounts = async (leadId: string) => {
  const lead = await Lead.findById(getObjectId(leadId))
    .select('activityCounts')
    .lean();

  if (!lead)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Lead not found',
      true,
      '',
      LeadResponseCodes.LEAD_NOT_FOUND,
    );

  const counts = (lead as any).activityCounts ?? {
    callAttempts: 0,
    meetings: 0,
    siteVisits: 0,
    totalActivity: 0,
  };

  return counts;
};

export const recalculateLeadActivityCounts = async (leadId: string) => {
  const leadObjectId = getObjectId(leadId);

  const completed = await Activity.find({
    lead: leadObjectId,
    status: ActivityStatus.COMPLETED,
  })
    .select('type task')
    .lean();

  const taskIds = completed.map((a: any) => a.task).filter(Boolean);

  const tasksById = new Map<string, any>();
  if (taskIds.length) {
    const tasks = await Tasks.find({ _id: { $in: taskIds } })
      .select('activityType')
      .lean();
    for (const t of tasks) tasksById.set((t as any)._id.toString(), t);
  }

  const counts = {
    callAttempts: 0,
    meetings: 0,
    siteVisits: 0,
    totalActivity: 0,
  };

  for (const a of completed as any[])
    if (a.type === 'call') {
      counts.callAttempts += 1;
    } else if (a.type === 'meeting') {
      counts.meetings += 1;
    } else if (a.type === 'site-visit') {
      counts.siteVisits += 1;
    } else if (a.type === 'schedule' && a.task) {
      const t = tasksById.get(a.task.toString());
      if (t?.activityType === TaskActivityType.MEETING) counts.meetings += 1;
      else if (t?.activityType === TaskActivityType.SITE_VISIT)
        counts.siteVisits += 1;
      else if (t?.activityType === TaskActivityType.CALL)
        counts.callAttempts += 1;
    }

  counts.totalActivity =
    counts.callAttempts + counts.meetings + counts.siteVisits;

  await Lead.updateOne(
    { _id: leadObjectId },
    {
      $set: {
        activityCounts: counts,
        activityCountsUpdatedAt: new Date(),
      },
    },
  );

  return counts;
};

interface TransferLeadInput {
  company: string;
  sourceMember: string;
  destMember?: string;
  destTeam?: string;
  distributionMethod?: 'individual' | 'equal' | 'roundrobin';
  transferType: 'temporary' | 'permanent';
  leadTypes: string[];
  transferStartDate?: Date;
  transferEndDate?: Date;
}

export const createLead = async (
  data: NewCreatedLead,
  isCheckCustomFields = true,
): Promise<ILeadDoc> => {
  try {
    const contact = await Contact.findById(data.contact).lean();
    const contactDetails: any = {
      name: [contact.firstName, contact.lastName].filter(Boolean).join(' '),
      email: contact.email,
      companyName: contact.companyName,
      phone: String(contact.phone[0].number),
    };

    if (contact)
      data = {
        ...data,
        contactDetails,
      };

    let customFields;
    if (isCheckCustomFields)
      customFields = await validateCustomFields({
        customFields: data.customFields,
        companyId: data.company,
        formName: CustomFormNames.LEAD,
        errorCode: LeadResponseCodes.LEAD_INVALID_CUSTOM_FIELDS,
        ...(data.interestType && {
          section: data.interestType,
        }),
      });

    if (customFields)
      data = {
        ...data,
        customFields: customFields,
      };

    // Normalize payload shapes so UI can send single-select values
    // while backend persists arrays where the schema expects arrays.
    if (
      data &&
      (data as any).configuration &&
      !Array.isArray((data as any).configuration)
    )
      (data as any).configuration = [(data as any).configuration];
    if (
      data &&
      (data as any).propertyType &&
      !Array.isArray((data as any).propertyType)
    )
      (data as any).propertyType = [(data as any).propertyType];
    if (
      data &&
      (data as any).preferredLocalities &&
      !Array.isArray((data as any).preferredLocalities)
    )
      (data as any).preferredLocalities = [(data as any).preferredLocalities];

    const maxPositionLead = await Lead.findOne({
      leadStage: data.leadStage,
    })
      .sort({ position: -1 })
      .select('position')
      .lean();

    const newPosition = maxPositionLead?.position
      ? (maxPositionLead.position as number) + 1000
      : 1000;

    let createdDoc;

    const { interestType } = data;

    switch (interestType) {
      case 'buy': {
        createdDoc = await BuyLead.create({
          ...(data as IBuyLead),
          position: newPosition,
        });
        break;
      }
      case 'sell':
        createdDoc = await SellLead.create({
          ...(data as ISellLead),
          position: newPosition,
        });
        break;
      case 'rent':
        createdDoc = await RentLead.create({
          ...(data as IRentLead),
          position: newPosition,
        });
        break;
      case 'lease':
        createdDoc = await LeaseLead.create({
          ...(data as ILeaseLead),
          position: newPosition,
        });
        break;
      default:
        throw new Error(`Unsupported interestType: ${interestType}`);
    }

    const lead = await Lead.findById(createdDoc._id)
      .populate([
        { path: 'contact', select: 'firstName lastName email phone' },
        { path: 'createdBy', select: 'firstName lastName email profileImage' },
        { path: 'assignedTo', select: 'firstName lastName email profileImage' },
        {
          path: 'cpContact',
          select: 'firstName lastName phone email cpCompany',
          populate: { path: 'cpCompany', select: 'companyName' },
        },
        { path: 'company', select: 'name' },
        { path: 'source', select: 'name' },
      ])
      .lean<PopulatedLead>();

    const first = lead?.contact?.firstName?.trim() || '';
    const last = lead?.contact?.lastName?.trim() || '';

    const nameSlug = [first, last]
      .filter(Boolean)
      .join('-')
      .replace(/\s+/g, '-')
      .toLowerCase();

    const slug = nameSlug || 'lead';
    const leadUrl = `${config.clientUrl}/leads/${encodeURIComponent(slug)}?id=${lead._id}`;

    const createdBy = lead?.createdBy;
    const assignedTo = lead?.assignedTo;

    const source = lead?.source
      ? await Source.findById(lead.source).lean()
      : null;

    const userName = [createdBy?.firstName, createdBy?.lastName]
      .filter(Boolean)
      .join(' ');

    const hasBudget = // check if budget exists
      lead?.budget !== undefined && lead?.budget !== null; // works also if budget is 0

    const description = `${lead?.contactDetails?.name || 'A lead'} has been assigned to you${userName ? ` by ${userName}` : ''
    }. Source: ${source?.name || 'N/A'}${hasBudget ? `. Budget: ${lead?.budget}` : ''}`; // append Budget if it exists

    const appRedirect = {
      screenType: 'Lead_Details',
      id: lead._id,
    };

    if (
      createdBy?._id &&
      assignedTo?._id &&
      !getObjectId(assignedTo._id).equals(getObjectId(createdBy._id))
    )
      await createNotification(
        {
          title: 'New Lead Assigned',
          description,
          userType: UserType.SELF,
          user: getObjectId(data.assignedTo as Types.ObjectId),
          status: NotificationStatus.SEND,
          notificationType: NotificationType.ALERT,
        },
        appRedirect,
      );

    const email = createdBy?.email?.trim();

    if (email)
      await sendEmailWithActiveTemplate({
        to: email,
        companyId: data.company,
        scenario: TemplateName.LeadAssigned,
        templateParams: {
          fullName: userName || 'User',
          companyName: lead?.company?.name || '',
          leadName:
            `${lead?.contact?.firstName || ''} ${lead?.contact?.lastName || ''}`.trim(),
          contact: lead?.contact?.phone?.[0]
            ? lead.contact.phone[0].countryCode + lead.contact.phone[0].number
            : '',
          source: source?.name || '',
          leadUrl,
        },
      });

    return createdDoc;
  } catch (error) {
    console.log(error);
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to create lead',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

/**
 * Update lead score based on activity or lead stage
 * @param leadId
 * @param activityId Optional activity to add points
 */
export const updateLeadScore = async (
  leadId: Types.ObjectId | string,
): Promise<void> => {
  // Step 1: Fetch lead and populate stage
  const lead = (await Lead.findById(leadId)
    .populate('leadStage', 'stageName')
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    .lean()) as any;

  if (!lead) throw new Error('Lead not found');
  if (!lead.company) throw new Error('Lead does not have a companyId');

  // Step 2: Fetch lead score config
  const config = await LeadScoreConfig.findOne({
    companyId: lead.company,
  }).lean();
  if (!config) throw new Error('LeadScoreConfig not found for the company');

  let newScore = lead.leadScore || 0;

  // Step 3: Handle lead stage overrides
  const stageName = lead.leadStage?.stageName;

  if (config.fullScoreIfLeadWon && stageName?.toLowerCase() === 'lead won')
    // If lead is won and full score is true, set to max points
    newScore = 100;
  else if (
    config.zeroScoreIfLeadLost &&
    stageName?.toLowerCase() === 'lead lost'
  )
    // If lead is lost and zero score is true
    newScore = 0;

  // Step 5: Update lead score atomically
  await Lead.findByIdAndUpdate(leadId, { leadScore: newScore });
};

export const queryLeadsKanbanGrouped = async (filter, options) => {
  try {
    const {
      companyName,
      search,
      company,
      preferredCity,
      preferredLocalities,
      budget,
      category,
      propertyType,
      assignedTo,
      createdBy,
      subcategory,
      project,
      configuration,
      orphanLeads,
      ids,
      minActivity,
      maxActivity,
      ...restFilter
    } = filter;

    const finalFilter = { ...restFilter };
    if (finalFilter.view) delete finalFilter.view;
    if (finalFilter.stage) delete finalFilter.stage;

    if (search && typeof search === 'string') {
      const contacts = await Contact.aggregate([
        { $unwind: '$phone' },
        {
          $match: {
            $expr: {
              $regexMatch: {
                input: { $toString: '$phone.number' },
                regex: search,
                options: 'i',
              },
            },
          },
        },
        { $group: { _id: '$_id' } },
      ]);
      const contactIds = contacts.map((c) => c._id);

      finalFilter.$or = [
        { 'contactDetails.name': { $regex: search, $options: 'i' } },
        { contact: { $in: contactIds } },
      ];
    }

    if (company && company !== 'all')
      finalFilter.company = getObjectId(company);
    if (companyName && companyName !== 'all')
      finalFilter['contactDetails.companyName'] = companyName;

    if (preferredCity) finalFilter.preferredCity = parseSafeIds(preferredCity);
    if (preferredLocalities)
      finalFilter.preferredLocalities = parseSafeIds(preferredLocalities);
    if (propertyType) finalFilter.propertyType = parseSafeIds(propertyType);
    if (category) finalFilter.category = parseSafeIds(category);
    if (subcategory) finalFilter.subcategory = parseSafeIds(subcategory);
    if (project) finalFilter.project = parseSafeIds(project);
    if (configuration) finalFilter.configuration = parseSafeIds(configuration);

    applyAssignedToFilter(finalFilter, assignedTo);
    if (createdBy) finalFilter.createdBy = parseSafeIds(createdBy);
    if (ids) {
      const parsedIds = parseSafeIds(ids);
      if (parsedIds) finalFilter._id = parsedIds;
    }

    if (budget) {
      const [minBudgetStr, maxBudgetStr] = budget
        .split(',')
        .map((b) => b.trim());
      const minBudget = parseFloat(minBudgetStr);
      const maxBudget = parseFloat(maxBudgetStr);
      if (!isNaN(minBudget) && !isNaN(maxBudget))
        finalFilter.budget = { $gte: minBudget, $lte: maxBudget };
    }

    if (orphanLeads) {
      const stageExcludeFilter: {
        company?: Types.ObjectId;
        stageName?: object;
      } = {
        stageName: { $nin: ['Lead Won', 'Lead Lost'] },
      };
      if (finalFilter.company) 
        stageExcludeFilter.company = getObjectId(
          finalFilter.company as Types.ObjectId,
        );
      
      const allowedStages = await LeadStage.find(stageExcludeFilter)
        .select('_id')
        .lean();
      const allowedStageIds = allowedStages.map((s) => s._id);

      const orphanPipeline = [
        { $match: { ...finalFilter, leadStage: { $in: allowedStageIds } } },
        {
          $lookup: {
            from: 'tasks',
            let: { leadId: '$_id' },
            pipeline: [
              {
                $match: {
                  $expr: { $eq: ['$leadId', '$$leadId'] },
                  status: { $ne: 'completed' },
                },
              },
              { $limit: 1 },
            ],
            as: 'incompleteTasks',
          },
        },
        {
          $match: {
            $expr: { $eq: [{ $size: '$incompleteTasks' }, 0] },
          },
        },
        { $project: { _id: 1 } },
      ];

      const orphanLeadsResult = await Lead.aggregate(orphanPipeline);
      const orphanLeadIds = orphanLeadsResult.map((l) => l._id);
      finalFilter._id = { $in: orphanLeadIds };
    }

    // Total activity filter
    if (minActivity !== undefined || maxActivity !== undefined) {
      const activityFilter: any = {};

      if (minActivity !== undefined) {
        const minTotalActivity = parseInt(minActivity as string, 10);
        if (!isNaN(minTotalActivity)) 
          activityFilter.$gte = minTotalActivity;
        
      }

      if (maxActivity !== undefined) {
        const maxTotalActivity = parseInt(maxActivity as string, 10);
        if (!isNaN(maxTotalActivity)) 
          activityFilter.$lte = maxTotalActivity;
        
      }

      if (Object.keys(activityFilter).length > 0) 
        finalFilter['activityCounts.totalActivity'] = activityFilter;
      
    }

    const limit = Math.min(options.limit || 20, 50);

    const stages = await LeadStage.find({
      company: finalFilter.company,
      isActive: true,
    })
      .sort({ position: 1 })
      .select('_id stageName color position')
      .lean();

    const populateOptions = options.populate
      ? options.populate.split(';').map((p) => {
          const [path, select] = p.split(':');
          return { path, select: select?.replace(/,/g, ' ') };
        })
      : [];

    const stageQueries = stages.map(async (stage) => {
      const stageFilter = { ...finalFilter, leadStage: stage._id };
      delete stageFilter.view;

      const [items, total] = await Promise.all([
        Lead.find(stageFilter)
          .sort({ position: 1 })
          .limit(limit)
          .populate(populateOptions)
          .lean(),
        Lead.countDocuments(stageFilter),
      ]);

      return {
        stage: {
          _id: stage._id,
          stageName: stage.stageName,
          color: stage.color,
          position: stage.position,
        },
        items,
        total,
        hasMore: total > limit,
      };
    });

    const columns = await Promise.all(stageQueries);

    return {
      columns,
      limit,
    };
  } catch (error) {
    console.error('Error querying leads kanban grouped:', error);
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to query leads kanban',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const queryLeadsKanban = async (filter, options) => {
  try {
    const {
      companyName,
      search,
      company,
      preferredCity,
      preferredLocalities,
      budget,
      category,
      propertyType,
      assignedTo,
      createdBy,
      subcategory,
      project,
      configuration,
      orphanLeads,
      ids,
      stage,
      minActivity,
      maxActivity,
      ...restFilter
    } = filter;

    const finalFilter = { ...restFilter };
    if (finalFilter.view) delete finalFilter.view;
    if (finalFilter.stage) delete finalFilter.stage;

    if (search && typeof search === 'string') {
      const contacts = await Contact.aggregate([
        { $unwind: '$phone' },
        {
          $match: {
            $expr: {
              $regexMatch: {
                input: { $toString: '$phone.number' },
                regex: search,
                options: 'i',
              },
            },
          },
        },
        { $group: { _id: '$_id' } },
      ]);
      const contactIds = contacts.map((c) => c._id);

      finalFilter.$or = [
        { 'contactDetails.name': { $regex: search, $options: 'i' } },
        { contact: { $in: contactIds } },
      ];
    }

    if (company && company !== 'all')
      finalFilter.company = getObjectId(company);
    if (companyName && companyName !== 'all')
      finalFilter['contactDetails.companyName'] = companyName;

    if (preferredCity) finalFilter.preferredCity = parseSafeIds(preferredCity);
    if (preferredLocalities)
      finalFilter.preferredLocalities = parseSafeIds(preferredLocalities);
    if (propertyType) finalFilter.propertyType = parseSafeIds(propertyType);
    if (category) finalFilter.category = parseSafeIds(category);
    if (subcategory) finalFilter.subcategory = parseSafeIds(subcategory);
    if (project) finalFilter.project = parseSafeIds(project);
    if (configuration) finalFilter.configuration = parseSafeIds(configuration);

    applyAssignedToFilter(finalFilter, assignedTo);
    if (createdBy) finalFilter.createdBy = parseSafeIds(createdBy);
    if (ids) {
      const parsedIds = parseSafeIds(ids);
      if (parsedIds) finalFilter._id = parsedIds;
    }

    if (budget) {
      const [minBudgetStr, maxBudgetStr] = budget
        .split(',')
        .map((b) => b.trim());
      const minBudget = parseFloat(minBudgetStr);
      const maxBudget = parseFloat(maxBudgetStr);
      if (!isNaN(minBudget) && !isNaN(maxBudget))
        finalFilter.budget = { $gte: minBudget, $lte: maxBudget };
    }

    if (stage) {
      const parsedStage = parseSafeIds(stage);
      if (parsedStage) finalFilter.leadStage = parsedStage;
    }

    // Total activity filter
    if (minActivity !== undefined || maxActivity !== undefined) {
      const activityFilter: any = {};

      if (minActivity !== undefined) {
        const minTotalActivity = parseInt(minActivity as string, 10);
        if (!isNaN(minTotalActivity)) 
          activityFilter.$gte = minTotalActivity;
        
      }

      if (maxActivity !== undefined) {
        const maxTotalActivity = parseInt(maxActivity as string, 10);
        if (!isNaN(maxTotalActivity)) 
          activityFilter.$lte = maxTotalActivity;
        
      }

      if (Object.keys(activityFilter).length > 0) 
        finalFilter['activityCounts.totalActivity'] = activityFilter;
      
    }

    const limit = options.limit || 20;
    const page = options.page || 1;
    const skip = (page - 1) * limit;

    const [items, total] = await Promise.all([
      Lead.find(finalFilter)
        .sort({ position: 1 })
        .skip(skip)
        .limit(limit)
        .populate(
          options.populate
            ? options.populate.split(';').map((p) => {
                const [path, select] = p.split(':');
                return { path, select: select?.replace(/,/g, ' ') };
              })
            : [],
        )
        .lean(),
      Lead.countDocuments(finalFilter),
    ]);

    return {
      results: items,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
      totalResults: total,
      hasMore: total > skip,
    };
  } catch (error) {
    console.error('Error querying leads kanban:', error);
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to query leads kanban',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const queryLeads = async (filter, options) => {
  try {
    const {
      companyName,
      search,
      company,
      preferredCity,
      preferredLocalities,
      budget,
      category,
      propertyType,
      assignedTo,
      createdBy,
      subcategory,
      project,
      configuration,
      orphanLeads,
      ids,
      minActivity,
      maxActivity,
      source,
      leadStage,
      interestType,
      ...restFilter
    } = filter;

    // console.log('RAW FILTER queryLeads:', filter);

    const finalFilter = { ...restFilter };

    // Search by name and phone
    if (search && typeof search === 'string') {
      const contacts = await Contact.aggregate([
        { $unwind: '$phone' },
        {
          $match: {
            $expr: {
              $regexMatch: {
                input: { $toString: '$phone.number' },
                regex: search,
                options: 'i',
              },
            },
          },
        },
        { $group: { _id: '$_id' } },
      ]);
      const contactIds = contacts.map((c) => c._id);

      finalFilter.$or = [
        { 'contactDetails.name': { $regex: search, $options: 'i' } },
        { contact: { $in: contactIds } },
      ];
    }

    // Company filters
    if (company && company !== 'all')
      finalFilter.company = getObjectId(company);
    if (companyName && companyName !== 'all')
      finalFilter['contactDetails.companyName'] = companyName;

    if (preferredCity) finalFilter.preferredCity = parseSafeIds(preferredCity);
    if (preferredLocalities)
      finalFilter.preferredLocalities = parseSafeIds(preferredLocalities);
    if (propertyType) finalFilter.propertyType = parseSafeIds(propertyType);
    if (category) finalFilter.category = parseSafeIds(category);
    if (subcategory) finalFilter.subcategory = parseSafeIds(subcategory);
    if (project) finalFilter.project = parseSafeIds(project);
    if (configuration) finalFilter.configuration = parseSafeIds(configuration);
    applyAssignedToFilter(finalFilter, assignedTo);
    if (createdBy) finalFilter.createdBy = parseSafeIds(createdBy);
    if (source) finalFilter.source = parseSafeIds(source);
    
    // leadstage ==> new,old,
    if (leadStage && leadStage !== 'all') {
      const parsedStage = parseSafeIds(leadStage);
      if (parsedStage) finalFilter.leadStage = parsedStage;
    }
    // Interest Type
    if (interestType) {
      const arr = interestType.split(',').map((i) => i.trim()).filter(Boolean);
      if (arr.length > 0) {
        finalFilter.interestType = arr.length === 1 ? arr[0] : { $in: arr };
      }
    }
  
    if (ids) {
      const parsedIds = parseSafeIds(ids);
      if (parsedIds) finalFilter._id = parsedIds;
    }
    // console.log('Incoming filter:', filter);
    // Budget range
    if (budget) {
      const [minBudgetStr, maxBudgetStr] = budget
        .split(',')
        .map((b) => b.trim());
      const minBudget = parseFloat(minBudgetStr);
      const maxBudget = parseFloat(maxBudgetStr);
      if (!isNaN(minBudget) && !isNaN(maxBudget))
        finalFilter.budget = { $gte: minBudget, $lte: maxBudget };
    }

    // Orphan leads filter
    if (orphanLeads) {
      const orphanPipeline = [
        { $match: finalFilter },
        {
          $lookup: {
            from: 'tasks',
            localField: '_id',
            foreignField: 'leadId',
            as: 'tasks',
          },
        },
        {
          $addFields: {
            allTasksCompleted: {
              $cond: {
                if: { $eq: [{ $size: '$tasks' }, 0] },
                then: true,
                else: {
                  $allElementsTrue: {
                    $map: {
                      input: '$tasks',
                      as: 't',
                      in: { $eq: ['$$t.status', 'completed'] },
                    },
                  },
                },
              },
            },
          },
        },
        {
          $lookup: {
            from: 'leadstages',
            localField: 'leadStage',
            foreignField: '_id',
            as: 'stage',
          },
        },
        {
          $unwind: {
            path: '$stage',
            preserveNullAndEmptyArrays: true,
          },
        },
        {
          $match: {
            allTasksCompleted: true,
            'stage.stageName': { $nin: ['Lead Won', 'Lead Lost'] },
          },
        },
        { $project: { _id: 1 } },
      ];

      const orphanLeadsResult = await Lead.aggregate(orphanPipeline);
      const orphanLeadIds = orphanLeadsResult.map((l) => l._id);
      finalFilter._id = { $in: orphanLeadIds };
    }

    // Total activity filter
    if (minActivity !== undefined || maxActivity !== undefined) {
      const activityFilter: any = {};

      if (minActivity !== undefined) {
        const minTotalActivity = parseInt(minActivity as string, 10);
        if (!isNaN(minTotalActivity)) 
          activityFilter.$gte = minTotalActivity;
        
      }

      if (maxActivity !== undefined) {
        const maxTotalActivity = parseInt(maxActivity as string, 10);
        if (!isNaN(maxTotalActivity)) 
          activityFilter.$lte = maxTotalActivity;
        
      }

      if (Object.keys(activityFilter).length > 0) 
        finalFilter['activityCounts.totalActivity'] = activityFilter;
      
    }

    // Execute paginated query
    const leads = await Lead.paginate(finalFilter, options);

    return leads;
  } catch (error) {
    console.error('Error querying leads123:', error);
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to query leads',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const bulkDeleteLeads = async (
  ids: string[],
): Promise<{ deletedCount: number }> => {
  if (!ids || ids.length < 1)
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'At least one lead ID is required',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );

  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    // Delete all leads with cascade (deletes referencing Tasks as well)
    for (const id of ids) await deleteWithReferences(Lead, id);

    await session.commitTransaction();
    return { deletedCount: ids.length };
  } catch (error) {
    await session.abortTransaction();
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to bulk delete leads',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  } finally {
    session.endSession();
  }
};

export const updateLead = async (
  id: string,
  updateData: UpdateLeadBody,
  assignedBody: { firstName: string; lastName: string },
  checkCustomFieldValidation: boolean = true,
  requestingUserType?: string,
): Promise<boolean> => {
  try {
    const filteredUpdateData = Object.fromEntries(
      Object.entries(updateData).filter(([_, value]) => value !== ''),
    );

    const existing = await Lead.findById(id)
      .select('assignedTo source leadStage company')
      .populate({ path: 'leadStage', select: 'stageName' })
      .lean();
    if (!existing)
      throw new ApiError(
        defaultStatus.OK,
        'Lead not found',
        true,
        '',
        LeadResponseCodes.LEAD_NOT_FOUND,
      );

    // Check if lead is won and if editing closed leads is allowed
    if (existing.leadStage) {
      const leadStageName = (existing?.leadStage as any)?.stageName;
      const isClosedLead = isClosedLeadStage(leadStageName);

      if (isClosedLead) {
        const rules = await rulesService.getRules(
          existing.company?.toString() ?? '',
          'edit_closed_leads',
        );
        const canEditClosedLeads =
          (rules as Record<string, boolean>).edit_closed_leads ?? false;

        if (!canEditClosedLeads) 
          throw new ApiError(
            defaultStatus.FORBIDDEN,
            'Editing closed leads is not allowed',
            true,
            '',
            LeadResponseCodes.LEAD_ERROR,
          );
        
      }
    }

    const oldAssigneeId = existing.assignedTo?.toString() ?? null;

    const oldLeadStageId = existing.leadStage?.toString() ?? null;
    let customFields = null;

    if (filteredUpdateData.assignedTo !== undefined) {
      const rawNew = filteredUpdateData.assignedTo;
      const newAssigneeStr =
        rawNew instanceof Types.ObjectId
          ? rawNew.toString()
          : typeof rawNew === 'string'
            ? rawNew
            : String(rawNew);
      if (oldAssigneeId !== newAssigneeStr)
        await assertLeadReassignmentAllowedByTeamRule({
          companyId: existing.company ?? updateData.company,
          oldAssigneeId,
          newAssigneeId: rawNew,
          requestingUserType,
        });
    }

    if (!checkCustomFieldValidation)
      customFields = await validateCustomFields({
        customFields: filteredUpdateData.customFields as Record<
          string,
          unknown
        >,
        companyId: filteredUpdateData.company as Types.ObjectId,
        formName: CustomFormNames.LEAD,
        errorCode: LeadResponseCodes.LEAD_INVALID_CUSTOM_FIELDS,
        ...(existing?.interestType && {
          section: existing?.interestType as string,
        }),
      });

    const result = await Lead.findByIdAndUpdate(
      id,
      {
        ...filteredUpdateData,
        ...(customFields && { customFields }),
      },
      {
        new: true,
        runValidators: true,
        strict: false,
      },
    )
      .populate({ path: 'leadStage', select: 'stageName' })
      .exec();

    if (!result)
      throw new ApiError(
        defaultStatus.OK,
        'Lead not found',
        true,
        '',
        LeadResponseCodes.LEAD_NOT_FOUND,
      );

    const source = await Source.findById(existing.source).lean();
    const description = `${result.contactDetails.name} has been assigned to you by ${assignedBody.firstName} ${assignedBody.lastName}. Source: ${source.name}. Budget: ${(result as any)?.budget || ''}`;

    const leadStageDescription = `${result.contactDetails.name}  moved to ${(result.leadStage as any).stageName} `;

    const appRedirect = {
      screenType: 'Lead_Details',
      id: id,
    };

    // Trigger notification to assigned userId if lead is assigned
    if (
      updateData?.assignedTo &&
      oldAssigneeId !== updateData?.assignedTo?.toString()
    )
      await createNotification(
        {
          title: 'New Lead Assigned',
          description: description,
          userType: UserType.SELF,
          user: getObjectId(result.assignedTo as Types.ObjectId),
          status: NotificationStatus.SEND,
        },
        appRedirect,
      );

    if (oldLeadStageId !== updateData.leadStage.toString())
      await createNotification(
        {
          title: 'Lead Stage Updated',
          description: leadStageDescription,
          userType: UserType.SELF,
          user: getObjectId(result.createdBy as Types.ObjectId),
          status: NotificationStatus.SEND,
        },
        appRedirect,
      );

    const providedAssigneeId = updateData.assignedTo
      ? updateData.assignedTo.toString()
      : null;

    const assignedChanged =
      !!providedAssigneeId && providedAssigneeId !== oldAssigneeId;

    let leadData;

    if (assignedChanged) {
      leadData = await Lead.findById(id)
        .populate([
          { path: 'contactId', select: 'name email phone' },
          {
            path: 'createdBy',
            select: 'firstName lastName email profileImage',
          },
          {
            path: 'assignedTo',
            select: 'firstName lastName email profileImage',
          },
          { path: 'company', select: 'name' },
          { path: 'source', select: 'name' },
        ])
        .lean<PopulatedLead>();

      await sendEmailWithActiveTemplate({
        to: leadData.assignedTo.email,
        companyId: updateData.company,
        scenario: TemplateName.LeadAssigned,
        templateParams: {
          fullName: `${leadData.assignedTo?.firstName} ${leadData.assignedTo?.lastName}`,
          companyName: leadData.company?.name,
          leadName: leadData.contactId?.name,
          contact: leadData.contactId?.phone,
          source: leadData.source?.name,
          leadUrl: `${config.clientUrl}/lead/${leadData._id}`,
        },
      });
    }

    await updateLeadScore(id);

    return true;
  } catch (err) {
    console.log('🚀 ~ updateLead ~ err:', err);
    if (err instanceof ApiError) throw err;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update lead',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const bulkUpdateLeads = async (
  ids: string[],
  updateData: UpdateLeadBody,
  assignedBody: { firstName: string; lastName: string },
  requestingUserType?: string,
): Promise<ILeadDoc[]> => {
  try {
    const filteredUpdateData = Object.fromEntries(
      Object.entries(updateData).filter(([_, value]) => value !== ''),
    );

    // Validate that all leads exist and belong to the company
    const existingLeads = await Lead.find({
      _id: { $in: ids },
      company: updateData.company,
    })
      .select('assignedTo source leadStage company createdBy')
      .populate({ path: 'leadStage', select: 'stageName' })
      .lean<ILeadDoc[]>();

    if (existingLeads.length !== ids.length) 
      throw new ApiError(
        defaultStatus.NOT_FOUND,
        'One or more leads not found',
        true,
        '',
        LeadResponseCodes.LEAD_NOT_FOUND,
      );
    

    // Check if any lead is won and if editing closed leads is allowed
    const rules = await rulesService.getRules(
      updateData.company,
      'edit_closed_leads',
    );
    const canEditClosedLeads =
      (rules as Record<string, boolean>).edit_closed_leads ?? false;

    if (!canEditClosedLeads) {
      const hasClosedLeads = existingLeads.some((lead) => {
        if (!lead.leadStage) return false;
        const leadStageName = (lead.leadStage as any)?.stageName;
        return isClosedLeadStage(leadStageName);
      });

      if (hasClosedLeads) 
        throw new ApiError(
          defaultStatus.FORBIDDEN,
          'Editing closed leads is not allowed',
          true,
          '',
          LeadResponseCodes.LEAD_ERROR,
        );
      
    }

    if (filteredUpdateData.assignedTo !== undefined) {
      const newAssignee = filteredUpdateData.assignedTo;
      for (const lead of existingLeads)
        await assertLeadReassignmentAllowedByTeamRule({
          companyId: updateData.company,
          oldAssigneeId: lead.assignedTo?.toString() ?? null,
          newAssigneeId: newAssignee,
          requestingUserType,
        });
    }

    // Perform bulk update
    const result = await Lead.updateMany(
      { _id: { $in: ids }, company: updateData.company },
      {
        ...filteredUpdateData,
        updatedAt: new Date(),
      },
      {
        runValidators: true,
        strict: false,
      },
    );

    if (result.modifiedCount === 0) 
      throw new ApiError(
        defaultStatus.INTERNAL_SERVER_ERROR,
        'Failed to update leads',
        true,
        '',
        LeadResponseCodes.LEAD_ERROR,
      );
    

    // Get updated leads for return
    const updatedLeads = await Lead.find({
      _id: { $in: ids },
      company: updateData.company,
    })
      .populate({ path: 'leadStage', select: 'stageName' })
      .populate({ path: 'assignedTo', select: 'firstName lastName email' })
      .populate({ path: 'contact', select: 'firstName lastName email phone' })
      .lean<ILeadDoc[]>();

    // Handle notifications for assignedTo changes
    if (updateData.assignedTo) {
      const source = await Source.findById(updateData.company).lean();
      const description = `Multiple leads have been assigned to you by ${assignedBody.firstName} ${assignedBody.lastName}. Source: ${source?.name || 'Unknown'}`;

      await createNotification(
        {
          title: 'Multiple Leads Assigned',
          description: description,
          userType: UserType.SELF,
          user: getObjectId(updateData.assignedTo),
          status: NotificationStatus.SEND,
        },
        {
          screenType: 'Lead_List',
          id: null,
        },
      );
    }

    // Handle notifications for leadStage changes
    if (updateData.leadStage) {
      const leadStage = await LeadStage.findById(updateData.leadStage).lean();
      if (leadStage) {
        const leadStageDescription = `Multiple leads moved to ${leadStage.stageName}`;

        const firstLeadCreatedBy = existingLeads[0]?.createdBy;

        await createNotification(
          {
            title: 'Multiple Leads Stage Updated',
            description: leadStageDescription,
            userType: UserType.SELF,
            user: firstLeadCreatedBy
              ? getObjectId(firstLeadCreatedBy)
              : getObjectId(updateData.createdBy as Types.ObjectId),
            status: NotificationStatus.SEND,
          },
          {
            screenType: 'Lead_List',
            id: null,
          },
        );
      }
    }

    // Update lead scores for all updated leads
    await Promise.all(ids.map((id) => updateLeadScore(id)));

    return updatedLeads;
  } catch (err) {
    console.log('🚀 ~ bulkUpdateLeads ~ err:', err);
    if (err instanceof ApiError) throw err;
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to bulk update leads',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const countLeadsByContactId = async (
  contactId: string,
): Promise<number> => {
  try {
    const query: Record<string, unknown> = {
      contact: getObjectId(contactId),
    };

    const count = await Lead.countDocuments(query);
    return count;
  } catch (error) {
    console.error('Error counting leads by contact ID:', error);
    throw new Error('Failed to count leads by contact ID');
  }
};

export const getLeadById = async (id: string): Promise<ILeadDoc | null> => {
  try {
    const lead = await Lead.findById(id).populate([
      {
        path: 'contact',
        select:
          'firstName lastName email companyName workPhoneNumber workEmail addressLine1 addressLine2 position city state pinCode country phone ',
        populate: [
          {
            path: 'city',
            select: 'name',
          },
          {
            path: 'country',
            select: 'name',
          },
          {
            path: 'state',
            select: 'name',
          },
        ],
      },
      {
        path: 'source',
        select: 'name',
      },
      {
        path: 'project',
        select: 'projectName',
      },
      {
        path: 'unit',
        select: 'unitNumber',
      },
      {
        path: 'preferredLocalities',
        select: 'name',
      },
      {
        path: 'preferredCity',
        select: 'name',
      },
      {
        path: 'preferredState',
        select: 'name',
      },
      {
        path: 'leadStage',
        select: 'stageName position',
      },
      {
        path: 'propertyType',
        select: 'name',
      },
      {
        path: 'createdBy',
        select: 'firstName lastName',
      },
      {
        path: 'shortlisted',
        select:
          'id title ownerName ownerContact price monthlyRent status availability createdBy mediaUrls',
        populate: [
          { path: 'city', select: 'name' },
          { path: 'state', select: 'name' },
          { path: 'locality', select: 'name' },
          { path: 'configuration', select: 'name' },
          { path: 'subcategory', select: 'name' },
          { path: 'createdBy', select: 'firstName lastName' },
        ],
      },
      {
        path: 'removed',
        select:
          'id title ownerName ownerContact price monthlyRent status availability createdBy mediaUrls',
        populate: [
          { path: 'city', select: 'name' },
          { path: 'state', select: 'name' },
          { path: 'locality', select: 'name' },
          { path: 'configuration', select: 'name' },
          { path: 'subcategory', select: 'name' },
          { path: 'createdBy', select: 'firstName lastName' },
        ],
      },
      {
        path: 'shortlistedProject',
        select:
          '_id projectName status city state locality pricePerSqYard mediaUrls createdBy companyId',

        populate: [
          { path: 'city', select: 'name' },
          { path: 'state', select: 'name' },
          { path: 'locality', select: 'name' },
          { path: 'createdBy', select: 'firstName lastName' },
        ],
      },
      {
        path: 'removedProject',
        select:
          '_id projectName status city state locality pricePerSqYard mediaUrls createdBy companyId',

        populate: [
          { path: 'city', select: 'name' },
          { path: 'state', select: 'name' },
          { path: 'locality', select: 'name' },
          { path: 'createdBy', select: 'firstName lastName' },
        ],
      },
      {
        path: 'property',
        select:
          'id title ownerName ownerContact price monthlyRent status availability createdBy mediaUrls',
      },
      {
        path: 'company',
        select: 'name',
      },
      {
        path: 'assignedTo',
        select: 'firstName lastName phone email',
      },
      {
        path: 'cpContact',
        select: 'firstName lastName phone email cpCompany',
        populate: {
          path: 'cpCompany',
          select: 'companyName',
        },
      },
    ]);

    if (!lead)
      throw new ApiError(
        defaultStatus.OK,
        'Lead not found',
        true,
        '',
        LeadResponseCodes.LEAD_NOT_FOUND,
      );

    return lead;
  } catch (error) {
    console.error('Error fetching lead by ID:', error);
    throw new Error('Failed to fetch lead by ID');
  }
};

export const deleteLeadById = async (id: string): Promise<boolean> => {
  try {
    await deleteWithReferences(Lead, id);
    // await safeDeleteById(Lead, id, LeadResponseCodes.LEAD_IN_USE);
    return true;
  } catch (error) {
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to delete lead',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const checkIfRefLead = async (id: string) => {
  try {
    await safeDeleteById(Lead, id, LeadResponseCodes.LEAD_IN_USE);
  } catch (error) {
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to check lead protection',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const getLeadAnalytics = async (
  companyId: string | Types.ObjectId,
  createdBy?: string | Types.ObjectId, // make optional
) => {
  const objectCompanyId = getObjectId(companyId);
  const objectCreatedBy = createdBy ? getObjectId(createdBy) : null;

  const baseMatch: Record<string, Types.ObjectId> = {
    company: objectCompanyId,
  };

  if (objectCreatedBy) baseMatch.assignedTo = objectCreatedBy;

  const [leadTrendData, leadScores, stageCounts, conversionTrendData] =
    await Promise.all([
      Lead.aggregate(getMonthlyLeadTotalsPipeline(baseMatch)),
      Lead.aggregate(getLeadScoreBucketPipeline(baseMatch)),
      Lead.aggregate(getLeadStageBreakdownPipeline(baseMatch)),
      Lead.aggregate(getConversionRatePipeline(baseMatch)),
    ]);

  const leadTrendsWithChange = computeLeadTrendsWithChange(leadTrendData);
  const conversionTrendsWithChange =
    computeLeadTrendsWithChange(conversionTrendData);

  return {
    leadScores,
    stageCounts,
    conversionTrendData: conversionTrendsWithChange,
    leadTrendData: leadTrendsWithChange,
  };
};

export const handleShortlistUpdate = async <
  T extends {
    shortlisted?: Types.ObjectId[];
    removed?: Types.ObjectId[];
    shortlistedProject?: Types.ObjectId[];
    removedProject?: Types.ObjectId[];
    save: () => Promise<T>;
  },
>(
  entity: T,
  {
    propertyId,
    projectId,
    type,
  }: {
    propertyId?: string;
    projectId?: string;
    type: 'add' | 'remove' | 'delete';
  },
): Promise<T> => {
  let fieldShortlisted: Types.ObjectId[];
  let fieldRemoved: Types.ObjectId[];
  let objectId: Types.ObjectId;

  if (propertyId) {
    objectId = new Types.ObjectId(propertyId);
    if (!entity.shortlisted) entity.shortlisted = [];
    if (!entity.removed) entity.removed = [];
    fieldShortlisted = entity.shortlisted;
    fieldRemoved = entity.removed;
  } else if (projectId) {
    objectId = new Types.ObjectId(projectId);
    if (!entity.shortlistedProject) entity.shortlistedProject = [];
    if (!entity.removedProject) entity.removedProject = [];
    fieldShortlisted = entity.shortlistedProject;
    fieldRemoved = entity.removedProject;
  } else {
    throw new Error('Either propertyId or projectId must be provided');
  }

  if (type === 'add') {
    if (!fieldShortlisted.some((p) => p.equals(objectId)))
      fieldShortlisted.push(objectId);

    fieldRemoved = fieldRemoved.filter((p) => !p.equals(objectId));
  } else if (type === 'remove') {
    fieldShortlisted = fieldShortlisted.filter((p) => !p.equals(objectId));
    if (!fieldRemoved.some((p) => p.equals(objectId)))
      fieldRemoved.push(objectId);
  } else if (type === 'delete') {
    fieldRemoved = fieldRemoved.filter((p) => !p.equals(objectId));
  }

  // reassign back since we mutated filtered arrays
  if (propertyId) {
    entity.shortlisted = fieldShortlisted;
    entity.removed = fieldRemoved;
  } else if (projectId) {
    entity.shortlistedProject = fieldShortlisted;
    entity.removedProject = fieldRemoved;
  }

  return await entity.save();
};

export const getSuggestedPropertiesByLeadOrContact = async (
  id: string,
  model: 'lead' | 'contact',
  filters?: {
    subcategory?: string;
    propertyType?: string;
    budgetMin?: string;
    budgetMax?: string;
    furnishingType?: string;
  },
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any[]> => {
  try {
    let query: Record<string, unknown> = { status: 'active' };
    let excludedIds: string[] = [];

    if (model === 'lead') {
      // 🔹 Fetch Lead
      const lead = await Lead.findById(id).lean();
      if (!lead)
        throw new ApiError(
          defaultStatus.OK,
          'Lead not found',
          true,
          '',
          LeadResponseCodes.LEAD_NOT_FOUND,
        );

      if (lead.company) query.companyId = lead.company;

      // 🔹 Handle by interest type
      // eslint-disable-next-line default-case
      switch (lead.interestType) {
        case 'buy': {
          const buyLead = await BuyLead.findById(id).lean();
          if (!buyLead) break;

          if (buyLead.category) query.propertyType = buyLead.category;

          if (buyLead.preferredCity) query.city = buyLead.preferredCity;
          if (buyLead.preferredLocalities?.length)
            query.locality = { $in: buyLead.preferredLocalities };
          if (buyLead.subCategoryId) query.subcategory = buyLead.subCategoryId;
          if (buyLead.configuration?.length)
            query.configuration = { $in: buyLead.configuration };
          if (buyLead.budget) query.price = { $lte: Number(buyLead.budget) };
          break;
        }

        case 'rent': {
          const rentLead = await RentLead.findById(id).lean();
          if (!rentLead) break;

          if (rentLead.category) query.propertyType = rentLead.category;

          if (rentLead.propertyCity) query.city = rentLead.propertyCity;
          if (rentLead.propertyLocality)
            query.locality = rentLead.propertyLocality;
          if (rentLead.propertyCategory)
            query.category = rentLead.propertyCategory;
          if (rentLead.configuration?.length)
            query.configuration = { $in: rentLead.configuration };
          if (rentLead.rentAmount)
            query.monthlyRent = { $lte: Number(rentLead.rentAmount) };
          break;
        }

        case 'sell': {
          const sellLead = await SellLead.findById(id).lean();
          if (!sellLead) break;

          if (sellLead.category) query.propertyType = sellLead.category;

          if (sellLead.propertyCategory)
            query.category = sellLead.propertyCategory;
          if (sellLead.configuration?.length)
            query.configuration = { $in: sellLead.configuration };
          if (sellLead.askingPrice)
            query.price = { $gte: Number(sellLead.askingPrice) };
          break;
        }

        case 'lease': {
          const leaseLead = await LeaseLead.findById(id).lean();
          if (!leaseLead) break;

          if (leaseLead.category) query.propertyType = leaseLead.category;

          if (leaseLead.propertyCity) query.city = leaseLead.propertyCity;
          if (leaseLead.propertyLocality)
            query.locality = leaseLead.propertyLocality;
          if (leaseLead.propertyCategory)
            query.category = leaseLead.propertyCategory;
          if (leaseLead.configuration?.length)
            query.configuration = { $in: leaseLead.configuration };
          if (leaseLead.leaseAmount)
            query.monthlyRent = { $lte: Number(leaseLead.leaseAmount) };
          break;
        }
      }

      excludedIds = [
        ...(lead.shortlisted ?? []).map((id: Types.ObjectId) => id.toString()),
        ...(lead.removed ?? []).map((id: Types.ObjectId) => id.toString()),
      ];
    }

    if (model === 'contact') {
      // 🔹 Fetch Contact
      const contact = await Contact.findById(id).lean();
      if (!contact)
        throw new ApiError(
          defaultStatus.OK,
          'Contact not found',
          true,
          '',
          LeadResponseCodes.LEAD_NOT_FOUND,
        );

      if (contact.company) query.companyId = contact.company;
      if (contact.city) query.city = contact.city;
      if (contact.state) query.state = contact.state;
      // if (contact.country) query.country = contact.country;
      // note: no exclusions here, only location-based

      excludedIds = [
        ...(contact.shortlisted ?? []).map((id: Types.ObjectId) =>
          id.toString(),
        ),
        ...(contact.removed ?? []).map((id: Types.ObjectId) => id.toString()),
      ];
    }

    if (excludedIds.length) query._id = { $nin: excludedIds };

    // 🔹 Apply additional filters from request
    if (filters) {
      // Subcategory filter
      if (filters.subcategory) {
        const subcategoryIds = filters.subcategory
          .split(',')
          .map((id) => getObjectId(id.trim()));
        query.subcategory =
          subcategoryIds.length === 1
            ? subcategoryIds[0]
            : { $in: subcategoryIds };
      }

      // PropertyType filter (overrides lead's property type)
      if (filters.propertyType) 
        query.propertyType = getObjectId(filters.propertyType);
      

      // Budget range filter (price for sell, monthlyRent for rent/lease)
      if (filters.budgetMin || filters.budgetMax) {
        const budgetFilter: Record<string, number> = {};
        if (filters.budgetMin) budgetFilter.$gte = Number(filters.budgetMin);
        if (filters.budgetMax) budgetFilter.$lte = Number(filters.budgetMax);

        // Apply to both price and monthlyRent fields
        query.$or = [{ price: budgetFilter }, { monthlyRent: budgetFilter }];
      }

      // Furnishing type filter
      if (filters.furnishingType) 
        query.furnishingType = filters.furnishingType;
      
    }

    // 🔹 Query properties
    const suggestedProperties = await individualProperties
      .find(query)
      .select(
        '_id title ownerName ownerContact price monthlyRent status availability createdBy mediaUrls',
      )
      .populate([
        { path: 'city', select: 'name' },
        { path: 'state', select: 'name' },
        { path: 'locality', select: 'name' },
        { path: 'configuration', select: 'name' },
        { path: 'subcategory', select: 'name' },
        { path: 'createdBy', select: 'firstName lastName' },
      ])
      .lean();

    // 🔹 Map _id → id
    return suggestedProperties.map((prop) => {
      const { _id, ...rest } = prop;
      return { id: _id.toString(), ...rest };
    });
  } catch (error) {
    console.log(error);
    throw new ApiError(
      defaultStatus.OK,
      'Failed to query suggested properties',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const addPropertiesToSuggested = async (
  id: string,
  propertyIds: string[],
  companyId?: string,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> => {
  try {
    if (!propertyIds || !Array.isArray(propertyIds) || propertyIds.length === 0)
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'propertyIds must be a non-empty array',
        true,
        '',
        LeadResponseCodes.LEAD_ERROR,
      );

    const objectIds = propertyIds.map((pid) => new Types.ObjectId(pid));

    const lead = await Lead.findOne({
      _id: new Types.ObjectId(id),
      company: companyId ? new Types.ObjectId(companyId) : undefined,
    });

    if (!lead)
      throw new ApiError(
        defaultStatus.NOT_FOUND,
        'Lead not found',
        true,
        '',
        LeadResponseCodes.LEAD_NOT_FOUND,
      );

    // Add propertyIds to shortlisted if not already present
    const currentShortlisted = lead.shortlisted || [];
    const currentRemoved = lead.removed || [];

    for (const objectId of objectIds) {
      // Add to shortlisted if not already there
      if (!currentShortlisted.some((pid) => pid.equals(objectId))) 
        currentShortlisted.push(objectId);
      
      // Remove from removed if present
      const removedIndex = currentRemoved.findIndex((pid) =>
        pid.equals(objectId),
      );
      if (removedIndex > -1) 
        currentRemoved.splice(removedIndex, 1);
      
    }

    lead.shortlisted = currentShortlisted;
    lead.removed = currentRemoved;
    await lead.save();

    return {
      id: lead._id,
      shortlisted: lead.shortlisted,
      removed: lead.removed,
    };
  } catch (error) {
    console.log('🚀 ~ addPropertiesToSuggested ~ error:', error);
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to add properties to suggested list',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const getSuggestedProjectsByLeadOrContact = async (
  id: string,
  model: 'lead' | 'contact',
  userTeams?: Types.ObjectId[],
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any[]> => {
  try {
    let query: Record<string, unknown> = { isDraft: false };
    let excludedIds: string[] = [];

    if (model === 'lead') {
      // 🔹 Fetch Lead
      const lead = await Lead.findById(id).lean();
      if (!lead)
        throw new ApiError(
          defaultStatus.OK,
          'Lead not found',
          true,
          '',
          LeadResponseCodes.LEAD_NOT_FOUND,
        );

      if (lead.company) query.companyId = lead.company;
      if (lead.interestType === 'buy') {
        // lead is IBuyLead here
        if (lead.preferredCity) query.city = lead.preferredCity;
      } else if (
        lead.interestType === 'rent' ||
        lead.interestType === 'sell' ||
        lead.interestType === 'lease'
      ) {
        // lead is IRentLead | ISellLead | ILeaseLead
        if (lead.propertyCity) query.city = lead.propertyCity;
        if (lead.state) query.state = lead.state;
      }

      excludedIds = [
        ...(lead.shortlistedProject ?? []).map((id: Types.ObjectId) =>
          id.toString(),
        ),
        ...(lead.removedProject ?? []).map((id: Types.ObjectId) =>
          id.toString(),
        ),
      ];
    }

    if (model === 'contact') {
      // 🔹 Fetch Contact
      const contact = await Contact.findById(id).lean();
      if (!contact)
        throw new ApiError(
          defaultStatus.OK,
          'Contact not found',
          true,
          '',
          LeadResponseCodes.LEAD_NOT_FOUND,
        );

      if (contact.companyId) query.companyId = contact.companyId;
      if (contact.city) query.city = contact.city;
      if (contact.state) query.state = contact.state;

      excludedIds = [
        ...(contact.shortlisted ?? []).map((id: Types.ObjectId) =>
          id.toString(),
        ),
        ...(contact.removed ?? []).map((id: Types.ObjectId) => id.toString()),
      ];
    }

    if (excludedIds.length) query._id = { $nin: excludedIds };

    // Add team filter if user has teams
    if (userTeams && userTeams.length > 0)
      query.$or = [
        { team: { $in: userTeams } },
        { team: { $exists: false } },
        { team: null },
      ];
    else query.$or = [{ team: { $exists: false } }, { team: null }];

    // 🔹 Query Projects
    const suggestedProjects = await Project.find(query)
      .select(
        '_id projectName status city state locality pricePerSqYard mediaUrls createdBy companyId',
      )
      .populate([
        { path: 'city', select: 'name' },
        { path: 'state', select: 'name' },
        { path: 'locality', select: 'name' },
        { path: 'createdBy', select: 'firstName lastName' },
      ])
      .lean();

    // 🔹 Map _id → id
    return suggestedProjects.map((proj) => {
      const { _id, ...rest } = proj;
      return { id: _id.toString(), ...rest };
    });
  } catch (error) {
    console.log(error);
    throw new ApiError(
      defaultStatus.OK,
      'Failed to query suggested projects',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

export const getOrphanLeadsCount = async (filter) => {
  const {
    companyName,
    search,
    company,
    preferredCity,
    preferredLocalities,
    budget,
    category,
    propertyType,
    assignedTo,
    createdBy,
    subcategory,
    project,
    configuration,
    ...restFilter
  } = filter;
  
  const finalFilter = { ...restFilter };
  if (finalFilter.view) delete finalFilter.view;
  if (finalFilter.stage) delete finalFilter.stage;

  // Search by name and phone
  if (search && typeof search === 'string') {
    const contacts = await Contact.aggregate([
      { $unwind: '$phone' },
      {
        $match: {
          $expr: {
            $regexMatch: {
              input: { $toString: '$phone.number' },
              regex: search,
              options: 'i',
            },
          },
        },
      },
      { $group: { _id: '$_id' } },
    ]);
    const contactIds = contacts.map((c) => c._id);

    finalFilter.$or = [
      { 'contactDetails.name': { $regex: search, $options: 'i' } },
      { contact: { $in: contactIds } },
    ];
  }

  // Company filters
  if (company && company !== 'all') finalFilter.company = getObjectId(company);
  if (companyName && companyName !== 'all')
    finalFilter['contactDetails.companyName'] = companyName;

  // Cities, localities, propertyType, category
  if (preferredCity) finalFilter.preferredCity = parseSafeIds(preferredCity);
  if (preferredLocalities)
    finalFilter.preferredLocalities = parseSafeIds(preferredLocalities);
  if (propertyType) finalFilter.propertyType = parseSafeIds(propertyType);
  if (category) finalFilter.category = parseSafeIds(category);
  if (subcategory) finalFilter.subcategory = parseSafeIds(subcategory);
  if (project) finalFilter.project = parseSafeIds(project);
  if (configuration) finalFilter.configuration = parseSafeIds(configuration);

  applyAssignedToFilter(finalFilter, assignedTo);
  if (createdBy) finalFilter.createdBy = parseSafeIds(createdBy);

  // Budget range
  if (budget) {
    const [minBudgetStr, maxBudgetStr] = budget.split(',').map((b) => b.trim());
    const minBudget = parseFloat(minBudgetStr);
    const maxBudget = parseFloat(maxBudgetStr);
    if (!isNaN(minBudget) && !isNaN(maxBudget))
      finalFilter.budget = { $gte: minBudget, $lte: maxBudget };
  }

  const stageFilter: { company?: Types.ObjectId; stageName?: object } = {
    stageName: { $nin: ['Lead Won', 'Lead Lost'] },
  };
  if (finalFilter.company) 
    stageFilter.company = getObjectId(finalFilter.company as Types.ObjectId);
  
  const allowedStages = await LeadStage.find(stageFilter).select('_id').lean();
  const allowedStageIds = allowedStages.map((s) => s._id);
  if (allowedStageIds.length === 0) return 0;

  finalFilter.leadStage = { $in: allowedStageIds };

  const orphanPipeline = [
    { $match: finalFilter },
    {
      $lookup: {
        from: 'tasks',
        let: { leadId: '$_id' },
        pipeline: [
          {
            $match: {
              $expr: { $eq: ['$leadId', '$$leadId'] },
              status: { $ne: 'completed' },
            },
          },
          { $limit: 1 },
        ],
        as: 'incompleteTasks',
      },
    },
    {
      $match: {
        $expr: { $eq: [{ $size: '$incompleteTasks' }, 0] },
      },
    },
    { $count: 'total' },
  ];

  const result = await Lead.aggregate(orphanPipeline);
  return result.length ? result[0].total : 0;
};

export const moveLead = async (
  leadId: string,
  targetStage: string,
  beforeId?: string,
  afterId?: string,
): Promise<ILeadDoc> => {
  try {
    const lead = await Lead.findById(leadId).select('leadStage company').populate({ path: 'leadStage', select: 'stageName' }).lean();
    if (!lead)
      throw new ApiError(
        defaultStatus.NOT_FOUND,
        'Lead not found',
        true,
        '',
        LeadResponseCodes.LEAD_NOT_FOUND,
      );

    // Check if lead is won and if editing closed leads is allowed
    if (lead.leadStage) {
      const leadStageName = (lead.leadStage as any)?.stageName;
      const isClosedLead = isClosedLeadStage(leadStageName);

      if (isClosedLead) {
        const rules = await rulesService.getRules(
          lead.company?.toString() ?? '',
          'edit_closed_leads',
        );
        const canEditClosedLeads =
          (rules as Record<string, boolean>).edit_closed_leads ?? false;

        if (!canEditClosedLeads) 
          throw new ApiError(
            defaultStatus.FORBIDDEN,
            'Editing closed leads is not allowed',
            true,
            '',
            LeadResponseCodes.LEAD_ERROR,
          );
        
      }
    }

    const targetStageDoc = await LeadStage.findOne({
      _id: getObjectId(targetStage),
      company: lead.company,
    })
      .select('_id')
      .lean();

    if (!targetStageDoc)
      throw new ApiError(
        defaultStatus.NOT_FOUND,
        'Target stage not found',
        true,
        '',
        LeadResponseCodes.LEAD_ERROR,
      );

    const targetStageId = targetStageDoc._id;
    const currentStageId = lead.leadStage?._id;
    const isSameStage = currentStageId.equals(targetStageId);

    let newPosition: number;

    if (beforeId && afterId) {
      const [beforeLead, afterLead] = await Promise.all([
        Lead.findById(beforeId).select('position').lean(),
        Lead.findById(afterId).select('position').lean(),
      ]);

      if (!beforeLead || !afterLead)
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'Invalid beforeId or afterId',
          true,
          '',
          LeadResponseCodes.LEAD_ERROR,
        );

      const beforePos = beforeLead.position as number;
      const afterPos = afterLead.position as number;

      if (Math.abs(afterPos - beforePos) < 2) {
        await reindexStagePositions(targetStageId);

        const [updatedBefore, updatedAfter] = await Promise.all([
          Lead.findById(beforeId).select('position').lean(),
          Lead.findById(afterId).select('position').lean(),
        ]);

        newPosition = Math.floor(
          ((updatedBefore.position as number) +
            (updatedAfter.position as number)) /
            2,
        );
      } else {
        newPosition = Math.floor((beforePos + afterPos) / 2);
      }
    } else if (beforeId) {
      const beforeLead = await Lead.findById(beforeId)
        .select('position')
        .lean();
      if (!beforeLead)
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'Invalid beforeId',
          true,
          '',
          LeadResponseCodes.LEAD_ERROR,
        );

      newPosition = (beforeLead.position as number) - 500;

      if (newPosition <= 0) {
        await reindexStagePositions(targetStageId);
        const updatedBefore = await Lead.findById(beforeId)
          .select('position')
          .lean();
        newPosition = Math.floor((updatedBefore.position as number) / 2);
      }
    } else if (afterId) {
      const afterLead = await Lead.findById(afterId).select('position').lean();
      if (!afterLead)
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'Invalid afterId',
          true,
          '',
          LeadResponseCodes.LEAD_ERROR,
        );

      const minPositionLead = await Lead.findOne({
        leadStage: targetStageId,
      })
        .sort({ position: 1 })
        .select('position')
        .lean();

      const afterPos = afterLead.position as number;
      const minPos = (minPositionLead?.position as number) || 0;

      if (afterPos <= minPos || Math.abs(afterPos - minPos) < 2) {
        await reindexStagePositions(targetStageId);
        const updatedAfter = await Lead.findById(afterId)
          .select('position')
          .lean();
        newPosition = (updatedAfter.position as number) - 500;
      } else {
        newPosition = Math.floor((minPos + afterPos) / 2);
      }
    } else {
      const maxPositionLead = await Lead.findOne({
        leadStage: targetStageId,
      })
        .sort({ position: -1 })
        .select('position')
        .lean();

      newPosition = maxPositionLead?.position
        ? (maxPositionLead.position as number) + 1000
        : 1000;
    }

    const updateData: any = { position: newPosition };
    if (!isSameStage) {
      updateData.leadStage = targetStageId;
    }

    const updatedLead = await Lead.findByIdAndUpdate(
      leadId,
      updateData,
      { new: true }
    ).select('leadStage company').populate({ path: 'leadStage', select: 'stageName' });

    await updateLeadScore(leadId);

    return updatedLead;
  } catch (error) {
    console.error('Error moving lead:', error);
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to move lead',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );
  }
};

const reindexStagePositions = async (
  stageId: Types.ObjectId,
): Promise<void> => {
  const POSITION_SPACING = 1000;

  const leads = await Lead.find({ leadStage: stageId })
    .sort({ position: 1, createdAt: 1 })
    .select('_id')
    .lean();

  const bulkOps = leads.map((lead, index) => ({
    updateOne: {
      filter: { _id: lead._id },
      update: { $set: { position: (index + 1) * POSITION_SPACING } },
    },
  }));

  if (bulkOps.length > 0) 
    await Lead.bulkWrite(bulkOps);
  
};

export const transferLead = async (payload: TransferLeadInput) => {
  const {
    company,
    sourceMember,
    destMember,
    destTeam,
    distributionMethod,
    transferType,
    leadTypes,
    transferStartDate,
    transferEndDate,
  } = payload;

  if (!company || !sourceMember)
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Missing required fields',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );

  if (!Array.isArray(leadTypes) || !leadTypes.length)
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'leadTypes must be a non-empty array of stage IDs',
      true,
      '',
      LeadResponseCodes.LEAD_ERROR,
    );

  const stageObjectIds = leadTypes.map((id) => new Types.ObjectId(id));

  const leads = await Lead.find({
    company: new Types.ObjectId(company),
    assignedTo: new Types.ObjectId(sourceMember),
    leadStage: { $in: stageObjectIds },
  });

  if (!leads.length)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'No leads found for transfer with given stages',
      true,
      '',
      LeadResponseCodes.LEAD_NOT_FOUND,
    );

  if (transferType === 'temporary') {
    // 🔹 TEMPORARY TRANSFER
    await Lead.updateMany(
      { _id: { $in: leads.map((l) => l._id) } },
      {
        $set: {
          assignedTo: new Types.ObjectId(destMember),
          transferFrom: new Types.ObjectId(sourceMember),
          transferType,
          transferStartDate,
          transferEndDate,
        },
      },
    );

    return { count: leads.length, transferType, destMember };
  }

  if (transferType === 'permanent') {
    if (distributionMethod === 'individual') {
      // 🔹 PERMANENT INDIVIDUAL → direct member transfer
      await Lead.updateMany(
        { _id: { $in: leads.map((l) => l._id) } },
        {
          $set: {
            assignedTo: new Types.ObjectId(destMember),
            transferFrom: new Types.ObjectId(sourceMember),
            transferType,
          },
        },
      );

      return { count: leads.length, transferType, destMember };
    }

    if (distributionMethod === 'equal' || distributionMethod === 'roundrobin') {
      if (!destTeam)
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'destTeam required for team distribution',
          true,
          '',
          LeadResponseCodes.LEAD_ERROR,
        );

      const team = await Team.findById(destTeam).lean();
      if (!team || !team.members?.length)
        throw new ApiError(
          defaultStatus.NOT_FOUND,
          'Team not found or has no members',
          true,
          '',
          LeadResponseCodes.LEAD_NOT_FOUND,
        );

      // combine team lead + members
      let assignees = [
        ...(team.lead ? [team.lead.toString()] : []),
        ...team.members.map((m) => m.toString()),
      ];

      assignees = assignees.filter(
        (id) => id.toString() !== sourceMember.toString(),
      );

      if (!assignees.length)
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'No assignees available in team',
          true,
          '',
          LeadResponseCodes.LEAD_ERROR,
        );

      // distribute leads
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const updates: any[] = [];
      if (distributionMethod === 'equal') {
        const perMember = Math.ceil(leads.length / assignees.length);
        let index = 0;
        for (const assignee of assignees) {
          const batch = leads.slice(index, index + perMember);
          index += perMember;
          if (!batch.length) continue;
          updates.push(
            Lead.updateMany(
              { _id: { $in: batch.map((l) => l._id) } },
              {
                $set: {
                  assignedTo: new Types.ObjectId(assignee),
                  transferFrom: new Types.ObjectId(sourceMember),
                  transferType,
                },
              },
            ),
          );
        }
      } else if (distributionMethod === 'roundrobin') {
        for (let i = 0; i < leads.length; i++) {
          const assignee = assignees[i % assignees.length];
          updates.push(
            Lead.updateOne(
              { _id: leads[i]._id },
              {
                $set: {
                  assignedTo: new Types.ObjectId(assignee),
                  transferFrom: new Types.ObjectId(sourceMember),
                  transferType,
                },
              },
            ),
          );
        }
      }

      await Promise.all(updates);
      return {
        count: leads.length,
        transferType,
        destTeam,
        distributionMethod,
      };
    }
  }

  throw new ApiError(
    defaultStatus.BAD_REQUEST,
    'Invalid transfer configuration',
    true,
    '',
    LeadResponseCodes.LEAD_ERROR,
  );
};

/**
 * Get MyOperator status for a lead
 * @param leadId - Lead ID
 * @param userId - User ID
 * @param companyId - Company ID
 * @returns MyOperator status information
 */
export const getLeadMyOperatorStatus = async (
  leadId: string | Types.ObjectId,
  userId: string | Types.ObjectId,
  companyId: string | Types.ObjectId,
) => {
  const { myOperatorService } =
    await import('@/modules/myoperator/myoperator.service');

  // Check if MyOperator is configured
  const config = await myOperatorService.isConfigured(companyId);

  // Get lead with contact
  const lead = await Lead.findById(leadId).populate('contact').lean();

  if (!lead) 
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Lead not found',
      true,
      '',
      LeadResponseCodes.LEAD_NOT_FOUND,
    );
  

  // Get user's MyOperator sync status
  const user = await User.findById(userId).select('myoperator').lean();

  // Get last call activity for this lead
  const lastCallActivity = await Activity.findOne({
    lead: leadId,
    type: 'call',
  })
    .sort({ createdAt: -1 })
    .select('_id title status createdAt')
    .lean();

  return {
    isConfigured: config.configured,
    isActive: config.active,
    userSynced: user?.myoperator?.syncStatus === 'synced',
    userExtension: user?.myoperator?.extension,
    leadPhone: (lead.contact as any)?.phone?.[0]?.number,
    lastCallActivity: lastCallActivity
      ? {
          id: lastCallActivity._id.toString(),
          title: lastCallActivity.title,
          status: lastCallActivity.status,
          createdAt: lastCallActivity.createdAt,
        }
      : undefined,
  };
};

/**
 * Get call history for a lead from MyOperator
 * @param leadId - Lead ID
 * @param companyId - Company ID
 * @param page - Page number
 * @param pageSize - Page size
 * @returns Call history
 */
export const getLeadCallHistory = async (
  leadId: string | Types.ObjectId,
  companyId: string | Types.ObjectId,
  page: number = 1,
  pageSize: number = 20,
) => {
  const { myOperatorService } =
    await import('@/modules/myoperator/myoperator.service');

  // Get lead with contact
  const lead = await Lead.findById(leadId).populate('contact').lean();

  if (!lead) 
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Lead not found',
      true,
      '',
      LeadResponseCodes.LEAD_NOT_FOUND,
    );
  

  const contact = lead.contact as any;
  if (!contact?.phone?.[0]?.number) 
    return {
      calls: [],
      totalCalls: 0,
      page,
      pageSize,
    };
  

  // Search call logs by phone number
  const phoneNumber = contact.phone[0].number;
  const calls = await myOperatorService.searchCallLogs(companyId, {
    searchKey: phoneNumber,
    pageSize,
    logFrom: (page - 1) * pageSize,
  });

  return {
    calls,
    totalCalls: calls.length,
    page,
    pageSize,
  };
};
