import { ApiError } from '@/shared/utils/errors';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';
import { Invoice } from '@/modules/invoice/invoice.model';

import responseCodes from '@/shared/utils/responseCode/responseCode';
import {
  IInvoiceBase,
  UpdateInvoiceBody,
} from '@/modules/invoice/invoice.interface';
import { getObjectId } from '@/shared/utils/commonHelper';
import { Company } from '../company/company.model';
import { calculatePlanExpiryDate } from '@/shared/utils/plans.utils';
import { Status } from '@/shared/constants/enum.constant';
import User from '../user/user.model';

type MongoDateRange = { $gte: Date; $lte: Date };
type InvoiceFilter = Record<string, string | MongoDateRange>;

const { InvoiceResponseCodes } = responseCodes;

export const createInvoice = async (
  data: IInvoiceBase,
  shouldMerge: boolean,
): Promise<boolean> => {
  const invoice = await Invoice.create(data);

  if (!invoice)
    throw new ApiError(
      defaultStatus.OK,
      'Failed to create invoice',
      true,
      '',
      InvoiceResponseCodes.INVOICE_ERROR,
    );

  if (shouldMerge && data.company) {
    const company = await Company.findById(data.company);

    if (company) {
      const updatedMaxUserCount =
        data.maxUserCount ?? company.maxUserCount ?? 0;

      const updatedFinalPrice = data.finalPrice ?? company.finalPrice ?? 0;

      company.maxUserCount = updatedMaxUserCount;
      company.finalPrice = updatedFinalPrice;
      company.planValidity = data.planValidity;
      company.price = data.price;
      company.tax = data.tax;
      company.discount = data.discount;
      (company.planExpiryDate = calculatePlanExpiryDate(data.planValidity)),
        (company.status = Status.ACTIVE);
      await company.save();

      await User.updateMany(
        { 'company.id': getObjectId(company._id as string), status: Status.INACTIVE },
        { $set: { status: Status.ACTIVE } },
      );
    }
  }

  return true;
};

export const getInvoiceById = async (id: string) => {
  const invoice = await Invoice.findById(id);
  if (!invoice)
    throw new ApiError(
      defaultStatus.OK,
      'Invoice not found',
      true,
      '',
      InvoiceResponseCodes.INVOICE_NOT_FOUND,
    );

  return invoice;
};

export const updateInvoice = async (
  id: string,
  updateData: UpdateInvoiceBody,
): Promise<boolean | null> => {
  try {
    const result = await Invoice.updateOne(
      { _id: getObjectId(id) },
      { $set: updateData },
    );

    if (result.matchedCount === 0)
      throw new ApiError(
        defaultStatus.OK,
        'Invoice not found',
        false,
        '',
        InvoiceResponseCodes.INVOICE_NOT_FOUND,
      );

    return true;
  } catch (err: unknown) {
    if (err instanceof ApiError) throw err;
    throw new ApiError(
      defaultStatus.OK,
      'Failed to update invoice',
      true,
      '',
      InvoiceResponseCodes.INVOICE_ERROR,
    );
  }
};

export const deleteInvoice = async (id: string): Promise<void> => {
  try {
    const deletedInvoice = await Invoice.findByIdAndDelete(getObjectId(id));

    if (!deletedInvoice)
      throw new ApiError(
        defaultStatus.NOT_FOUND,
        'Invoice not found',
        false,
        '',
        InvoiceResponseCodes.INVOICE_NOT_FOUND,
      );
  } catch (err: unknown) {
    if (err instanceof ApiError) throw err;

    throw new ApiError(
      defaultStatus.INTERNAL_SERVER_ERROR,
      'Failed to delete invoice',
      true,
      '',
      InvoiceResponseCodes.INVOICE_ERROR,
    );
  }
};

export const queryInvoices = async (
  filter: InvoiceFilter = {},
  options = {},
) => {
  const { search, from, to, ...rest } = filter;
  const query: InvoiceFilter = { ...rest };

  if (typeof from === 'string' && typeof to === 'string')
    query.issuedDate = {
      $gte: new Date(from),
      $lte: new Date(to),
    };

  if (typeof search === 'string' && search.trim()) {
    const aggregation = [
      {
        $lookup: {
          from: 'companies',
          localField: 'company',
          foreignField: '_id',
          as: 'company',
        },
      },
      { $unwind: { path: '$company', preserveNullAndEmptyArrays: true } },
      {
        $match: {
          $or: [{ 'company.name': { $regex: search, $options: 'i' } }],
          ...(typeof from === 'string' &&
            typeof to === 'string' && {
              issuedDate: {
                $gte: new Date(from),
                $lte: new Date(to),
              },
            }),
        },
      },
    ];

    return Invoice.paginate(
      {},
      {
        ...options,
        aggregation,
      },
    );
  }

  return Invoice.paginate(query, options);
};
