import { ILeadStageDoc } from './leadStage.interface';
import { LeadStage } from './leadStage.model';
import { NewCreatedLeadStage } from '@/modules/master/leadStage/leadStage.interface';
import ApiError from '@/shared/utils/errors/ApiError';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
const { LeadStageResponseCodes } = responseCodes;

export const cloneDefaultLeadStagesForCompany = async (
  companyId: string,
  createdBy?: string,
) => {
  const defaultLeadStages = await LeadStage.find({ isDefault: true });

  if (!defaultLeadStages.length) return;

  const newStages = defaultLeadStages.map((stage) => {
    const { stageName, description, color, position, isProtected } = stage;
    return {
      stageName,
      description,
      color,
      position,
      isDefault: false,
      company: companyId,
      isActive: true,
      isProtected,
      createdBy,
      updatedBy: createdBy,
    };
  });

  await LeadStage.insertMany(newStages);
};

export const createLeadStage = async (
  leadStageBody: NewCreatedLeadStage,
) => {

  const result = await LeadStage.findOneAndUpdate(
    {
      ...(leadStageBody.company && { company: leadStageBody.company }),
      stageName: {
        $regex: new RegExp(
          `^${String(leadStageBody.stageName || '')
            .trim()
            .toLowerCase()}$`,
          'i',
        ),
      },
    },
    { $setOnInsert: leadStageBody },
    {
      new: true,
      upsert: true,
      includeResultMetadata: true,
    },
  );


  if (result.lastErrorObject?.updatedExisting)
    throw new ApiError(
      defaultStatus.CONFLICT,
      'Lead stage already exists',
      true,
      '',
      LeadStageResponseCodes.LEAD_STAGE_ALREADY_EXISTS,
    );

  return result;
};

export const queryLeadStages = async (filter: {}, options: {}) => {
  const leadStages = await LeadStage.paginate(filter, options);
  return leadStages;
};

export const updateLeadStage = async (
  id: string,
  updateData: Partial<ILeadStageDoc> & { newPosition?: number },
): Promise<boolean | null> => {
  const leadStage = await LeadStage.findById(id);
  if (!leadStage)
    throw new ApiError(
      defaultStatus.OK,
      'Lead stage not found',
      true,
      '',
      LeadStageResponseCodes.LEAD_STAGE_NOT_FOUND,
    );

  try {
    await LeadStage.findByIdAndUpdate(id, updateData, { new: true });
  } catch (_error) {
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update lead stage',
      true,
      '',
      LeadStageResponseCodes.LEAD_STAGE_UPDATE_ERROR,
    );
  }

  return true;
};

export const deleteLeadStage = async (
  id: string,
): Promise<ILeadStageDoc | null> => {
  const leadStage = await LeadStage.findByIdAndDelete(id);
  if (!leadStage)
    throw new ApiError(
      defaultStatus.OK,
      'Lead stage not found',
      true,
      '',
      LeadStageResponseCodes.LEAD_STAGE_NOT_FOUND,
    );

  // decreasing position by 1 for those with a position greater than the deleted lead stage's position
  await LeadStage.updateMany(
    {
      company: leadStage.company,
      isDefault: leadStage.isDefault,
      position: { $gt: leadStage.position },
    },
    { $inc: { position: -1 } },
  );

  return leadStage;
};

export const updateLeadStagesPosition = async (
  positions: Array<{ id: string; position: number }>,
): Promise<boolean> => {
  if (!Array.isArray(positions) || positions.length === 0)
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Invalid input. "positions" must be a non-empty array.',
    );

  // Store the current positions to check for updates
  const currentLeadStages = await LeadStage.find({
    _id: { $in: positions.map((p) => p.id) },
  });

  if (currentLeadStages.length !== positions.length)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'One or more LeadStages not found',
    );

  // Prepare an array to hold updates for bulk updates
  const updates = [];

  for (const { id, position } of positions) {
    // Check if the new position is valid and prepare the update object
    if (typeof position !== 'number' || position < 0)
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'position must be a non-negative number',
      );

    updates.push({
      updateOne: {
        filter: { _id: id },
        update: { position: position },
      },
    });
  }
  await LeadStage.bulkWrite(updates);

  return true;
};
