import { handleRequestError } from "@/utils/errorUtils.js";

import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
import RecaseError from "@/utils/errorUtils.js";
import { ErrCode, FullCustomerEntitlement } from "@autumn/shared";
import { Decimal } from "decimal.js";
import { StatusCodes } from "http-status-codes";
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
import { CusPriceService } from "@/internal/customers/cusProducts/cusPrices/CusPriceService.js";
import { CusService } from "@/internal/customers/CusService.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { getCusEntBalance } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
import { performDeductionOnCusEnt } from "@/trigger/updateBalanceTask.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { CusProductService } from "../cusProducts/CusProductService.js";

const getCusOrgAndCusPrice = async ({
  db,
  req,
  cusEnt,
}: {
  db: DrizzleCli;
  req: ExtendedRequest;
  cusEnt: FullCustomerEntitlement;
}) => {
  const [cusPrice, customer, org] = await Promise.all([
    CusPriceService.getRelatedToCusEnt({
      db,
      cusEnt,
    }),
    CusService.getByInternalId({
      db,
      internalId: cusEnt.internal_customer_id,
    }),
    OrgService.getFromReq(req),
  ]);

  return { cusPrice, customer, org };
};

export const handleUpdateEntitlement = async (req: any, res: any) => {
  try {
    const { db, logtail: logger } = req;
    const { customer_entitlement_id } = req.params;
    const { balance, next_reset_at, entity_id } = req.body;

    if (isNaN(parseFloat(balance))) {
      throw new RecaseError({
        message: "Invalid balance",
        code: ErrCode.InvalidRequest,
        statusCode: StatusCodes.BAD_REQUEST,
      });
    }

    if (
      next_reset_at !== null &&
      (!Number.isInteger(next_reset_at) || next_reset_at < 0)
    ) {
      throw new RecaseError({
        message: "Next reset at must be a valid unix timestamp or null",
        code: ErrCode.InvalidRequest,
        statusCode: StatusCodes.BAD_REQUEST,
      });
    }

    // Check if org owns the entitlement
    const cusEnt = await CusEntService.getStrict({
      db,
      id: customer_entitlement_id,
      orgId: req.orgId,
      env: req.env,
      withCusProduct: true,
    });

    if (balance < 0 && !cusEnt.usage_allowed) {
      throw new RecaseError({
        message: "Entitlement does not allow usage",
        code: ErrCode.InvalidRequest,
        statusCode: StatusCodes.BAD_REQUEST,
      });
    }

    if (cusEnt.unlimited) {
      throw new RecaseError({
        message: "Entitlement is unlimited",
        code: ErrCode.InvalidRequest,
        statusCode: StatusCodes.BAD_REQUEST,
      });
    }

    let { balance: masterBalance } = getCusEntBalance({
      cusEnt,
      entityId: entity_id,
    });

    const deducted = new Decimal(masterBalance!).minus(balance).toNumber();

    let originalBalance = structuredClone(masterBalance);

    let { newBalance, newEntities, newAdjustment } = performDeductionOnCusEnt({
      cusEnt,
      toDeduct: deducted,
      addAdjustment: true,
      allowNegativeBalance: cusEnt.usage_allowed || false,
      entityId: entity_id,
    });

    let updates = {
      balance: newBalance,
      next_reset_at,
      entities: newEntities,
      adjustment: newAdjustment,
    };

    const { cusPrice, customer, org } = await getCusOrgAndCusPrice({
      db,
      req,
      cusEnt,
    });

    if (cusPrice && customer) {
      let fullCusProduct = await CusProductService.get({
        db,
        id: cusEnt.customer_product_id,
        orgId: req.orgId,
        env: req.env,
      });

      const { newReplaceables, deletedReplaceables } = await adjustAllowance({
        db,
        env: req.env,
        org: org,
        affectedFeature: cusEnt.entitlement.feature,
        cusEnt: {
          ...cusEnt,
          customer_product: fullCusProduct!,
        },
        cusPrices: [cusPrice],
        customer: customer,
        originalBalance: originalBalance!,
        newBalance: balance,
        logger: req.logtail,
      });

      if (newReplaceables && newReplaceables.length > 0) {
        updates.balance = newBalance! - newReplaceables.length;
      }

      if (deletedReplaceables && deletedReplaceables.length > 0) {
        updates.balance = newBalance! + deletedReplaceables.length;
      }
    }

    await CusEntService.update({
      db,
      id: customer_entitlement_id,
      updates,
    });

    res.status(200).json({ success: true });
  } catch (error) {
    handleRequestError({
      req,
      error,
      res,
      action: "update customer entitlement",
    });
  }
};
