/* eslint-disable */

import { FilterQuery, Types } from 'mongoose';
import {
  IUnitBookingOrHold,
  IUnitBookingOrHoldDoc,
  IPersonWithDocs,
  NewCreatedUnitBookingOrHold,
  UnitBookingOrHoldQueryFilter,
  UpdateUnitBookingOrHoldBody,
} from './unitBookingOrHold.interface';
import { UnitBookingOrHold } from './unitBookingOrHold.model';
import { getObjectId } from '@/shared/utils/commonHelper';
import { PaginateOptions } from '@/shared/utils/plugins/paginate/paginate';
import { ApiError } from '@/shared/utils/errors';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import { CustomFormNames } from '@/modules/customFields/customFields.constant';
import { validateCustomFields } from '@/shared/utils/customFieldValidation';
import { sendWhatsappMessage } from '@/shared/communicationAlerts/sendMetaMsg';
import { TriggerPoint } from '@/shared/constants/enum.constant';
import { sendUnitFcmNotification } from './unitBookingOrHold.helper';
import Unit from '@/modules/project/unit/unit.model';
import IndividualProperties from '@/modules/individualProperties/individualProperties.model';
import { sendNotificationToUsers } from '@/modules/notification/sendNotificationToUsers';

const { UnitBookingOrHoldResponseCodes } = responseCodes;

const WHATSAPP_VAR_ORDER = [
  'name',
  'company_name',
  'date',
  'unit_name', // project OR property (never empty ideally)
  'support_name',
  'support_contact',
] as const;
function normalizeWhatsappVariables(
  variables: Record<string, string>,
  isNamedParams: boolean,
): Record<string, string> | string[] {
  if (isNamedParams) {
    return variables;
  }

  return WHATSAPP_VAR_ORDER.map((key) => {
    const val = variables[key];
    if (!val) {
      console.error(`Missing required variable: ${key}`);
      return;
    }
    return val;
  });
}

function buildWhatsappVariables(
  result: IUnitBookingOrHoldDoc,
  excludeHoldUntil = false,
): Record<string, string> {
  const lead = (result as any)?.lead;
  const soldBy = (result as any)?.soldBy;
  const project = (result as any)?.project;
  const property = (result as any)?.property;

  const vars: Record<string, string> = {
    name: lead?.contactDetails?.name || '',
    company_name: soldBy?.company?.id?.name || '',
    support_name: 'Support Team',
    support_contact: '1800-123-4567',
    company_name_: soldBy?.company?.id?.name || '',
  };

  vars.date = excludeHoldUntil
    ? new Date(result.createdAt as any).toLocaleDateString()
    : result.holdUntil
      ? new Date(result.holdUntil as any).toLocaleDateString()
      : new Date(result.createdAt as any).toLocaleDateString();

  // ✅ SINGLE source field
  vars.unit_name = result.project?.projectName || result.property?.title || '';

  return vars;
}

function determineTriggerPoint(
  result: IUnitBookingOrHoldDoc,
  excludeHoldUntil: boolean,
): string {
  if (result.action === 'hold') {
    return TriggerPoint['WhenUserHoldPropertyForLead'];
  }

  if (result.action === 'book') {
    const listingType = (result as any)?.property?.listingType;
    if (excludeHoldUntil && listingType === 'sell') {
      return TriggerPoint['WhenUserMarkPropertyForSellToLead'];
    } else {
      return TriggerPoint['WhenUserBookPropertyForLead'];
    }
  }

  // Default fallback
  return TriggerPoint['WhenUserHoldPropertyForLead'];
}

export const createUnitBookingOrHold = async (
  payload: NewCreatedUnitBookingOrHold,
): Promise<IUnitBookingOrHoldDoc> => {
  let { company, ...data } = payload;

  let previousHoldCreatedBy: Types.ObjectId | null = null;

  const hasUnit = !!data.unit;
  const hasProperty = !!data.property;

  // Builder flow => unit required
  // Broker flow => property required
  // Enforce exactly one
  if ((hasUnit && hasProperty) || (!hasUnit && !hasProperty)) {
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Either unit or property is required (provide exactly one)',
      true,
      '',
      UnitBookingOrHoldResponseCodes.ERROR,
    );
  }

  // Only validate & update unit status for builder flow
  if (hasUnit) {
    const unitDoc = await Unit.findById(getObjectId(data.unit as string)).select(
      'status',
    );

    if (!unitDoc) {
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'Invalid unit',
        true,
        '',
        UnitBookingOrHoldResponseCodes.ERROR,
      );
    }

    if (unitDoc.status === 'sold' || unitDoc.status === 'booked') {
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'Unit is already sold',
        true,
        '',
        UnitBookingOrHoldResponseCodes.ERROR,
      );
    }

    if (data.action === 'hold') {
      if (unitDoc.status === 'hold') {
        throw new ApiError(
          defaultStatus.BAD_REQUEST,
          'Unit is already on hold',
          true,
          '',
          UnitBookingOrHoldResponseCodes.ERROR,
        );
      }
    }

    if (data.action === 'book') {
      if (unitDoc.status === 'hold') {
        const latestHold = await UnitBookingOrHold.findOne({
          unit: getObjectId(data.unit as string),
          action: 'hold',
        })
          .sort({ createdAt: -1 })
          .select('createdBy')
          .lean();

        previousHoldCreatedBy = (latestHold as any)?.createdBy || null;
      }
    }
  }

  const customFields = await validateCustomFields({
    customFields: data.customFields,
    companyId: company,
    formName: CustomFormNames.BOOK_OR_HOLD,
    errorCode:
      UnitBookingOrHoldResponseCodes.UNITBOOKINGORHOLD_INVALID_CUSTOM_FIELDS,
  });

  data = {
    ...data,
    ...(customFields && { customFields }),
  };

  const record = await UnitBookingOrHold.create(payload);

  if (!record) {
    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to create record',
      true,
      '',
      UnitBookingOrHoldResponseCodes.ERROR,
    );
  }

  if (hasUnit) {
    if (data.action === 'hold') {
      await Unit.findByIdAndUpdate(getObjectId(data.unit as string), {
        $set: { status: 'hold' },
      });
    }

    if (data.action === 'book') {
      await Unit.findByIdAndUpdate(getObjectId(data.unit as string), {
        $set: { status: 'sold' },
      });
    }
  }

  if (hasProperty) {
    if (data.action === 'book') {
      await IndividualProperties.findByIdAndUpdate(
        getObjectId(data.property as string),
        { $set: { status: 'sold', availability: 'Sold' } },
      );
    }
  }

  const result = await UnitBookingOrHold.findById(record._id).populate([
    { path: 'lead', select: 'contactDetails' },
    { path: 'property', select: 'title listingType _id' },
    { path: 'project', select: 'projectName _id' },
    {
      path: 'soldBy',
      select: 'company',
      populate: [{ path: 'company.id', select: 'id name' }],
    },
    {
      path: 'createdBy',
      select: 'firstName lastName phone',
    },
    {
      path: 'updatedBy',
      select: 'firstName lastName phone',
    },
    {
      path: 'unit',
      select: 'unitNumber _id',
    },
  ]);

  const listingType = (result as any)?.property?.listingType;
  const excludeHoldUntil = listingType === 'sell' || listingType === 'rent';

  const whatsappVariables = buildWhatsappVariables(result, excludeHoldUntil);
  const triggerPoint = determineTriggerPoint(result, excludeHoldUntil);

  if (result.action === 'hold' || result.action === 'book') {
    await sendUnitFcmNotification(result);

    if (
      result.action === 'book' &&
      previousHoldCreatedBy &&
      String(previousHoldCreatedBy) !== String(payload.createdBy)
    ) {
      await sendNotificationToUsers(
        {
          title: 'Unit sold (was held by you)',
          description: `Unit ${(result as any)?.unit?.unitNumber || ''} has been marked sold.`,
          user: getObjectId(previousHoldCreatedBy as any),
          createdBy: getObjectId(payload.createdBy as any),
        },
        {
          screenType: 'Unit_Details',
          id: (result as any)?.project?._id,
          unitId: (result as any)?.unit?._id,
          unitNumber: (result as any)?.unit?.unitNumber,
        },
      );
    }

  
    const isNamedParams = result.action === 'hold';
    await sendWhatsappMessage({
      companyId: company as Types.ObjectId,
      triggerPoint,
      toNumber: String(result.lead?.contactDetails?.phone),
      variables: normalizeWhatsappVariables(whatsappVariables, isNamedParams),
      isNamedParams,
    });
  }

  return record;
};

export const getUnitBookingOrHoldById = async (
  id: string,
): Promise<IUnitBookingOrHoldDoc | null> => {
  const record = await UnitBookingOrHold.findById(id);
  if (!record)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Record not found',
      true,
      '',
      UnitBookingOrHoldResponseCodes.NOT_FOUND,
    );

  return record;
};

const BOOKING_DOC_FIELD_KEYS = [
  'aadharFront',
  'aadharBack',
  'panCard',
  'passportPhoto',
  'additionalDocs',
] as const;

type BookingDocFieldKey = (typeof BOOKING_DOC_FIELD_KEYS)[number];

export const updateBookingDocument = async (
  id: string,
  payload: {
    personIndex: number;
    fieldKey: BookingDocFieldKey;
    url: string;
    additionalDocIndex?: number;
    updatedBy?: string;
  },
): Promise<void> => {
  const record = await UnitBookingOrHold.findById(getObjectId(id)).select(
    'persons action',
  );
  if (!record)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Record not found',
      false,
      '',
      UnitBookingOrHoldResponseCodes.NOT_FOUND,
    );

  if (record.action !== 'book')
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Only booking records support KYC document updates',
      true,
      '',
      UnitBookingOrHoldResponseCodes.ERROR,
    );

  const persons = record.persons as IPersonWithDocs[] | undefined;
  if (!persons?.length)
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'No persons found on this booking',
      true,
      '',
      UnitBookingOrHoldResponseCodes.ERROR,
    );

  if (
    payload.personIndex < 0 ||
    payload.personIndex >= persons.length
  )
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Invalid person index',
      true,
      '',
      UnitBookingOrHoldResponseCodes.ERROR,
    );

  if (!BOOKING_DOC_FIELD_KEYS.includes(payload.fieldKey))
    throw new ApiError(
      defaultStatus.BAD_REQUEST,
      'Invalid document field',
      true,
      '',
      UnitBookingOrHoldResponseCodes.ERROR,
    );

  const setPayload: Record<string, unknown> = {};

  if (payload.fieldKey === 'additionalDocs') {
    if (payload.additionalDocIndex === undefined || payload.additionalDocIndex < 0)
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'additionalDocIndex is required for additional documents',
        true,
        '',
        UnitBookingOrHoldResponseCodes.ERROR,
      );
    const current = [...(persons[payload.personIndex].additionalDocs || [])];
    if (payload.additionalDocIndex >= current.length)
      throw new ApiError(
        defaultStatus.BAD_REQUEST,
        'Invalid additional document index',
        true,
        '',
        UnitBookingOrHoldResponseCodes.ERROR,
      );
    current[payload.additionalDocIndex] = payload.url;
    setPayload[`persons.${payload.personIndex}.additionalDocs`] = current;
  } else {
    setPayload[`persons.${payload.personIndex}.${payload.fieldKey}`] =
      payload.url;
  }

  if (payload.updatedBy) {
    setPayload.updatedBy = getObjectId(payload.updatedBy);
  }

  const result = await UnitBookingOrHold.updateOne(
    { _id: getObjectId(id) },
    { $set: setPayload },
  );

  if (result.matchedCount === 0)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Record not found',
      false,
      '',
      UnitBookingOrHoldResponseCodes.NOT_FOUND,
    );
};

export const updateUnitBookingOrHold = async (
  id: string,
  updateData: UpdateUnitBookingOrHoldBody,
): Promise<void> => {
  let { company, ...data } = updateData;
  const customFields = await validateCustomFields({
    customFields: data.customFields,
    companyId: company,
    formName: CustomFormNames.BOOK_OR_HOLD,
    errorCode:
      UnitBookingOrHoldResponseCodes.UNITBOOKINGORHOLD_INVALID_CUSTOM_FIELDS,
  });

  data = {
    ...data,
    ...(customFields && { customFields }),
  };
  const result = await UnitBookingOrHold.updateOne(
    { _id: getObjectId(id) },
    { $set: data },
  );

  if (result.matchedCount === 0)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Record not found',
      false,
      '',
      UnitBookingOrHoldResponseCodes.NOT_FOUND,
    );
};

export const deleteUnitBookingOrHold = async (id: string): Promise<void> => {
  const deleted = await UnitBookingOrHold.findByIdAndDelete(getObjectId(id));
  if (!deleted)
    throw new ApiError(
      defaultStatus.NOT_FOUND,
      'Record not found',
      false,
      '',
      UnitBookingOrHoldResponseCodes.NOT_FOUND,
    );
};

export const queryUnitBookingOrHold = async (
  rawFilter: UnitBookingOrHoldQueryFilter,
  options: PaginateOptions,
) => {
  const { lead, project, unit, action, createdBy, updatedBy, ...rest } =
    rawFilter;

  const merged = {
    ...rest,
    ...(lead && { lead: getObjectId(lead) }),
    ...(project && { project: getObjectId(project) }),
    ...(unit && { unit: getObjectId(unit) }),
    ...(action && { action }),
    ...(createdBy && { createdBy: getObjectId(createdBy) }),
    ...(updatedBy && { updatedBy: getObjectId(updatedBy) }),
  };

  const filter: FilterQuery<IUnitBookingOrHold> = Object.fromEntries(
    Object.entries(merged).filter(([, v]) => v != null),
  );

  return UnitBookingOrHold.paginate(filter, options);
};

export const getLatestHoldByUnitId = async (unitId: string) => {
  const record = await UnitBookingOrHold.findOne({
    unit: getObjectId(unitId),
    action: 'hold',
  })
    .sort({ createdAt: -1 })
    .populate([
      {
        path: 'createdBy',
        select: 'firstName lastName phone email',
      },
      {
        path: 'unit',
        select: 'unitNumber _id status',
      },
      {
        path: 'project',
        select: 'projectName _id',
      },
      {
        path: 'lead',
        select: 'contactDetails',
      },
    ])
    .lean();

  return record;
};
