import DatabaseConfig from "../DatabaseConfig";
import BearerTokenAuthorization from "../Middleware/BearerTokenAuthorization";
import BillingService from "../Services/BillingService";
import ProjectService from "../Services/ProjectService";
import PromoCodeService from "../Services/PromoCodeService";
import ResellerPlanService, {
  Service as ResellerPlanServiceType,
} from "../Services/ResellerPlanService";
import {
  ExpressRequest,
  ExpressResponse,
  NextFunction,
} from "../Utils/Express";
import Response from "../Utils/Response";
import BaseAPI from "./BaseAPI";
import StatusCode from "../../Types/API/StatusCode";
import URL from "../../Types/API/URL";
import { PlanType } from "../../Types/Billing/SubscriptionPlan";
import Email from "../../Types/Email";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import Project from "../../Models/DatabaseModels/Project";
import PromoCode from "../../Models/DatabaseModels/PromoCode";
import ResellerPlan from "../../Models/DatabaseModels/ResellerPlan";

export default class ResellerPlanAPI extends BaseAPI<
  ResellerPlan,
  ResellerPlanServiceType
> {
  public constructor() {
    super(ResellerPlan, ResellerPlanService);

    // Reseller Plan Action API
    // TODO: Refactor this API and make it partner specific.
    this.router.post(
      `${new this.entityType()
        .getCrudApiPath()
        ?.toString()}/action/:resellerId`,
      BearerTokenAuthorization.isAuthorizedBearerToken,
      async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
        try {
          const resellerId: string | undefined = req.params["resellerId"];

          if (!resellerId) {
            throw new Error("Invalid reseller id.");
          }

          if (
            resellerId.trim().toLowerCase() !==
            (req as any).bearerTokenData?.resellerId?.trim().toLowerCase()
          ) {
            throw new Error("Invalid reseller id found in access token");
          }

          const action: string = req.body.action;

          if (!action) {
            throw new BadDataException("Invalid action.");
          }

          const resellerPlanId: string | undefined = req.body.plan_id;

          if (!resellerPlanId) {
            throw new BadDataException("Invalid reseller plan id.");
          }

          // check reseller Plan.

          const resellerPlan: ResellerPlan | null =
            await ResellerPlanService.findOneBy({
              query: {
                planId: resellerPlanId,
              },
              select: {
                _id: true,
                planId: true,
                reseller: {
                  resellerId: true,
                },
                teamMemberLimit: true,
                monitorLimit: true,
                planType: true,
              },
              props: {
                isRoot: true,
              },
            });

          if (!resellerPlan) {
            throw new BadDataException("Invalid reseller plan id.");
          }

          if (resellerPlan.reseller?.resellerId !== resellerId) {
            throw new BadDataException(
              "This plan does not belong to reseller: " + resellerId,
            );
          }

          const licenseKey: string | undefined = req.body.uuid;

          if (!licenseKey) {
            throw new BadDataException("Invalid license key.");
          }

          const userEmail: Email = new Email(req.body.activation_email);

          if (action === "activate") {
            // generate a coupon code. Billing is handled by the reseller so OneUptime will have 100% discount on its plans.

            const couponcode: string = await BillingService.generateCouponCode({
              name: resellerId,
              percentOff: 100,
              maxRedemptions: 2,
              durationInMonths: 12 * 20, // 20 years.
              metadata: {
                licenseKey: licenseKey || "",
                resellerPlanId: resellerPlan?.id?.toString() || "",
              },
            });

            // save this in promocode table.

            const promoCode: PromoCode = new PromoCode();

            promoCode.promoCodeId = couponcode;
            promoCode.resellerId = resellerPlan?.reseller.id as ObjectID;
            promoCode.resellerPlanId = resellerPlan?.id as ObjectID;
            promoCode.userEmail = userEmail;
            promoCode.planType = resellerPlan?.planType as PlanType;
            promoCode.resellerLicenseId = licenseKey || "";

            await PromoCodeService.create({
              data: promoCode,
              props: {
                isRoot: true,
              },
            });

            // now redirect to accounts sign up page with this promocode.

            const accountUrl: URL = await DatabaseConfig.getAccountsUrl();

            return Response.sendJsonObjectResponse(
              req,
              res,
              {
                message: "product activated",
                redirect_url: URL.fromString(accountUrl.toString())
                  .addRoute("/register")
                  .addQueryParams({
                    email: userEmail.toString(),
                    promoCode: couponcode,
                    partnerId: resellerId,
                  })
                  .toString(),
              },
              {
                statusCode: new StatusCode(201),
              },
            );
          } else if (action === "enhance_tier" || action === "reduce_tier") {
            // update monitor and team seat limits.

            const project: Project | null = await ProjectService.findOneBy({
              query: {
                resellerLicenseId: licenseKey,
              },
              select: {
                _id: true,
              },
              props: {
                isRoot: true,
              },
            });

            if (!project) {
              throw new BadDataException(
                "Project not found with this license key",
              );
            }

            // update limits.

            await ProjectService.updateOneById({
              id: project.id!,
              data: {
                activeMonitorsLimit: resellerPlan.monitorLimit!,
                seatLimit: resellerPlan.teamMemberLimit!,
                resellerPlanId: resellerPlan.id!,
              },
              props: {
                isRoot: true,
              },
            });

            return Response.sendJsonObjectResponse(req, res, {
              message:
                action === "enhance_tier"
                  ? "product enhanced"
                  : "product reduced",
            });
          } else if (action === "refund") {
            // delete this project.

            const project: Project | null = await ProjectService.findOneBy({
              query: {
                resellerLicenseId: licenseKey,
              },
              select: {
                _id: true,
              },
              props: {
                isRoot: true,
              },
            });

            if (!project) {
              // maybe already deleted. so, we just issue a refund and return.
              return Response.sendJsonObjectResponse(req, res, {
                message: "product refunded",
              });
            }

            await ProjectService.deleteOneBy({
              query: {
                resellerLicenseId: licenseKey,
                _id: project.id?.toString() as string,
              },
              props: {
                isRoot: true,
              },
            });

            return Response.sendJsonObjectResponse(req, res, {
              message: "product refunded",
            });
          }

          throw new BadDataException("Invalid action.");
        } catch (err) {
          next(err);
        }
      },
    );
  }
}
