import { Vendor, VendorPlan, VendorSubscription } from "@/models";
import { findLatestVendorApplication } from "@/lib/vendor-application";
import { successResponse, notFoundResponse } from "@/lib/api/response";
import { NotFoundError, ValidationError } from "@/lib/api/errors";
import { withApi } from "@/lib/api/handler";
import { isValidObjectId } from "@/lib/api/validate";
import { auditUpdate, createAuditContext } from "@/lib/audit";
import { getSettings, type ISettings } from "@/models/settings.model";
import { connectDB } from "@/lib/db";
import { isDefaultVendorRecord } from "@/lib/multi-vendor";
import { resolveVendorCommission } from "@/lib/vendor-commission";
import {
  VENDOR_APPLICATION_PAYMENT_STATUS,
  VENDOR_APPLICATION_STATUS,
  VENDOR_BILLING_INTERVAL,
  VENDOR_PAYMENT_INVITATION,
  VENDOR_STATUS,
  VENDOR_SUBSCRIPTION_STATUS,
} from "@/config/app.config";
import { subscriptionOccupiesSlot } from "@/models/vendorSubscription.model";
import {
  buildSubscriptionForPlan,
  supersededStripeAssignmentFilter,
  supersededStripeAssignmentPatch,
} from "@/lib/vendor-subscriptions";
import { draftExcessProducts } from "@/lib/vendor-limits";
import {
  assertStripeBillingReady,
  ensureStripePriceForVendorPlan,
} from "@/lib/vendor-plan-stripe";
import { getStripeForSecretKey } from "@/lib/stripe";
import {
  stageVendorPlanChange,
  type TargetVendorPlan,
} from "@/lib/vendor-subscription-changes";
import {
  reverseStripeVendorCancellation,
  scheduleStripeVendorCancellation,
  scheduleStripeVendorDowngrade,
} from "@/lib/vendor-stripe-adapter";
import { synchronizeVendorBilling } from "@/lib/vendor-billing-sync";
import { dispatchVendorBillingNotifications } from "@/lib/vendor-billing-notifications";

type RouteParams = { id: string };

async function assertPlansEnabled() {
  await connectDB();
  const settings = await getSettings();
  if (!settings.multiVendorMode?.enabled || !settings.vendorConfig?.plansEnabled) {
    throw new NotFoundError("Vendor plans");
  }
  return settings;
}

function paidPlan(plan: { billingInterval?: string; price?: number }) {
  return (
    plan.billingInterval !== VENDOR_BILLING_INTERVAL.NONE &&
    Number(plan.price ?? 0) > 0
  );
}

function targetPlan(
  plan: Record<string, any>,
  stripePriceId?: string | null,
): TargetVendorPlan {
  return {
    id: String(plan._id),
    name: String(plan.name),
    price: Number(plan.price ?? 0),
    billingInterval: String(plan.billingInterval),
    currency: String(plan.stripePriceCurrency || "USD"),
    commissionRate: Number(plan.commissionRate ?? 0),
    features: plan.features ?? [],
    limits: plan.limits ?? {},
    capabilities: plan.capabilities ?? {},
    stripePriceId: stripePriceId ?? plan.stripePriceId ?? null,
    status: String(plan.status),
  };
}

function applicationPlanSnapshot(
  plan: Record<string, any>,
  stripePriceId: string,
) {
  return {
    name: String(plan.name),
    price: Number(plan.price),
    currency: String(plan.stripePriceCurrency || "USD"),
    billingInterval: plan.billingInterval,
    commissionRate: Number(plan.commissionRate ?? 0),
    trialDays: 0,
    features: plan.features ?? [],
    limits: plan.limits ?? {},
    capabilities: plan.capabilities ?? {},
    stripeProductId: plan.stripeProductId ?? null,
    stripePriceId,
  };
}

async function stageInitialPaidAssignment(input: {
  vendor: any;
  plan: any;
  current: any | null;
  actorId: string;
  settings: ISettings;
}) {
  const { vendor, plan, current, actorId, settings } = input;
  const application = await findLatestVendorApplication({
    vendorId: vendor._id,
    userId: vendor.userId,
  });
  if (!application) {
    throw new ValidationError(
      "Paid plan assignment requires the vendor application billing record",
    );
  }
  const stripeFields = await ensureStripePriceForVendorPlan(plan, settings);
  const stripePriceId = stripeFields.stripePriceId;
  const now = new Date();
  application.status = VENDOR_APPLICATION_STATUS.APPROVED;
  application.planId = plan._id;
  application.planSnapshot = applicationPlanSnapshot(
    { ...plan.toObject(), ...stripeFields },
    stripePriceId,
  );
  application.paymentStatus = VENDOR_APPLICATION_PAYMENT_STATUS.PENDING;
  application.paymentDueAt = new Date(
    now.getTime() +
      VENDOR_PAYMENT_INVITATION.DEADLINE_DAYS * 24 * 60 * 60 * 1000,
  );
  application.paymentCompletedAt = null;
  application.paymentExpiredAt = null;
  application.setupAccessExpiredAt = null;
  application.paymentReminder3SentAt = null;
  application.paymentReminder6SentAt = null;
  application.lastError = null;
  await application.save();

  const payload = {
    ...buildSubscriptionForPlan(vendor._id, {
      ...plan.toObject(),
      currency: stripeFields.stripePriceCurrency,
      stripePriceId,
    }, actorId),
    applicationId: application._id,
    providerStatus: "not_started",
    stripePriceId,
  };
  let subscription = await VendorSubscription.findOne({
    vendorId: vendor._id,
    status: VENDOR_SUBSCRIPTION_STATUS.INCOMPLETE,
    provider: "stripe",
  });
  if (subscription) {
    subscription.set(payload);
    await subscription.save();
  } else {
    subscription = await VendorSubscription.create(payload);
  }

  const keepsExistingFreePlan =
    current?.provider === "manual" &&
    current?.status === VENDOR_SUBSCRIPTION_STATUS.ACTIVE;

  // Staging a paid plan supersedes any other pending Stripe attempt for this
  // vendor, so an abandoned earlier assignment cannot linger and make checkout
  // ambiguous. A live free plan is untouched — the vendor keeps selling on it
  // until the paid subscription is paid for.
  await VendorSubscription.updateMany(
    supersededStripeAssignmentFilter({
      vendorId: vendor._id,
      keepSubscriptionId: subscription._id,
    }),
    { $set: supersededStripeAssignmentPatch() },
  );

  if (!keepsExistingFreePlan) {
    vendor.status = VENDOR_STATUS.PAYMENT_REQUIRED;
    vendor.storeActive = false;
    vendor.planId = null;
    vendor.commission = resolveVendorCommission(vendor, null, settings);
    await vendor.save();
  }

  return { subscription, application, keepsExistingFreePlan };
}

export const POST = withApi<RouteParams>(
  {
    auth: "admin",
    rateLimit: { action: "admin:vendorSub:assign", preset: "moderate" },
  },
  async ({ request, params, session }) => {
    const settings = await assertPlansEnabled();
    const { id } = params;
    if (!isValidObjectId(id)) return notFoundResponse("Vendor");
    const body = (await request.json().catch(() => ({}))) as {
      planId?: unknown;
      activationMode?: unknown;
    };
    const planId = typeof body.planId === "string" ? body.planId : "";
    if (!isValidObjectId(planId)) {
      throw new ValidationError({ planId: ["A valid plan is required"] });
    }

    const [vendor, plan] = await Promise.all([
      Vendor.findById(id),
      VendorPlan.findById(planId),
    ]);
    if (!vendor) return notFoundResponse("Vendor");
    if (!plan) return notFoundResponse("Plan");
    if (isDefaultVendorRecord(vendor)) {
      throw new ValidationError("The default store cannot be assigned a plan");
    }
    if (plan.status !== "active") {
      throw new ValidationError("This plan is archived and cannot be assigned");
    }

    const current = await VendorSubscription.findOne({
      vendorId: vendor._id,
      occupiesActiveSlot: true,
    }).sort({ createdAt: -1 });
    if (current && String(current.planId) === String(plan._id)) {
      throw new ValidationError("The vendor is already on this plan");
    }

    if (current?.provider === "stripe") {
      const stripe = getStripeForSecretKey(assertStripeBillingReady(settings));
      const target = targetPlan(plan.toObject());
      const staged = await stageVendorPlanChange(
        {
          id: String(current._id),
          planId: String(current.planId),
          provider: current.provider,
          providerSubscriptionId: current.paymentProviderRef,
          subscriptionItemId: current.stripeSubscriptionItemId ?? null,
          stripePriceId: current.stripePriceId ?? null,
          currentPeriodEnd: current.currentPeriodEnd ?? null,
          planSnapshot: {
            name: current.planSnapshot.name,
            price: current.planSnapshot.price,
            billingInterval: current.planSnapshot.billingInterval,
            currency: current.planSnapshot.currency,
            features: current.planSnapshot.features,
            limits: current.planSnapshot.limits,
            capabilities: current.planSnapshot.capabilities,
            stripePriceId: current.planSnapshot.stripePriceId,
          },
          commissionRateSnapshot: current.commissionRateSnapshot,
        },
        target,
        {
          async ensurePrice() {
            const fields = await ensureStripePriceForVendorPlan(plan, settings);
            return fields.stripePriceId;
          },
          async scheduleDowngrade(change) {
            if (!current.currentPeriodStart) {
              throw new ValidationError(
                "Stripe current period start is missing; run billing reconciliation first",
              );
            }
            return scheduleStripeVendorDowngrade(stripe, {
              subscriptionId: change.providerSubscriptionId,
              currentPriceId: change.currentPriceId,
              targetPriceId: change.targetPriceId,
              currentPeriodStart: current.currentPeriodStart,
              effectiveAt: change.effectiveAt,
              targetPlanId: change.targetPlanId,
            });
          },
          async scheduleFreePlanAtPeriodEnd(change) {
            await scheduleStripeVendorCancellation(
              stripe,
              change.providerSubscriptionId,
            );
            return { scheduleId: null };
          },
          async savePending(patch) {
            current.set(patch);
            await current.save();
          },
        },
      );
      return successResponse(
        { subscription: current.toObject(), staged },
        staged.changeType === "upgrade"
          ? "Upgrade staged. The vendor must confirm and pay before access changes."
          : "Downgrade scheduled for the current paid period end.",
      );
    }

    if (paidPlan(plan)) {
      assertStripeBillingReady(settings);
      const staged = await stageInitialPaidAssignment({
        vendor,
        plan,
        current,
        actorId: session.user.id,
        settings,
      });
      return successResponse(
        {
          subscription: staged.subscription,
          paymentRequired: true,
          keepsExistingFreePlan: staged.keepsExistingFreePlan,
        },
        staged.keepsExistingFreePlan
          ? "Paid plan staged. The existing free plan remains active until payment."
          : "Paid plan assigned. The vendor must complete Stripe payment.",
        201,
      );
    }

    if (current) {
      current.status = VENDOR_SUBSCRIPTION_STATUS.CANCELLED;
      current.occupiesActiveSlot = false;
      await current.save();
    }
    const before = vendor.toObject();
    vendor.commission = resolveVendorCommission(vendor, plan, settings);
    vendor.planId = plan._id;
    vendor.storeActive = true;
    await vendor.save();
    const subscription = await VendorSubscription.create(
      buildSubscriptionForPlan(vendor._id, plan, session.user.id, {
        activationMode:
          body.activationMode === "auto" || body.activationMode === "manual"
            ? body.activationMode
            : undefined,
      }),
    );
    const draftResult = await draftExcessProducts(
      vendor._id,
      plan.limits?.products ?? null,
    );
    await auditUpdate(
      createAuditContext(request, session),
      "vendor",
      String(vendor._id),
      before as unknown as Record<string, unknown>,
      vendor.toObject() as unknown as Record<string, unknown>,
    );
    return successResponse(
      {
        subscription,
        vendor: vendor.toObject(),
        draftedProducts: draftResult.drafted,
      },
      "Free plan assigned",
      201,
    );
  },
);

export const DELETE = withApi<RouteParams>(
  {
    auth: "admin",
    rateLimit: { action: "admin:vendorSub:cancel", preset: "moderate" },
  },
  async ({ request, params, session }) => {
    const settings = await assertPlansEnabled();
    const { id } = params;
    if (!isValidObjectId(id)) return notFoundResponse("Vendor");
    const vendor = await Vendor.findById(id);
    if (!vendor) return notFoundResponse("Vendor");
    const subscription = await VendorSubscription.findOne({
      vendorId: vendor._id,
      occupiesActiveSlot: true,
    }).sort({ createdAt: -1 });
    if (!subscription) {
      return successResponse(
        { vendor: vendor.toObject() },
        "No active subscription to cancel",
      );
    }

    if (
      subscription.provider === "stripe" &&
      subscription.paymentProviderRef
    ) {
      const stripe = getStripeForSecretKey(assertStripeBillingReady(settings));
      const snapshot = await scheduleStripeVendorCancellation(
        stripe,
        subscription.paymentProviderRef,
      );
      const result = await synchronizeVendorBilling(snapshot, {
        eventType: "admin.cancellation_scheduled",
      });
      await dispatchVendorBillingNotifications(result, settings);
      subscription.pendingChangeType = "cancel";
      subscription.pendingChangeStatus = "scheduled";
      subscription.pendingChangeEffectiveAt =
        snapshot.subscription.currentPeriodEnd;
      subscription.cancelAtPeriodEnd = true;
      await subscription.save();
      return successResponse(
        { subscription: subscription.toObject() },
        "Cancellation scheduled for the current paid period end",
      );
    }

    const before = vendor.toObject();
    subscription.status = VENDOR_SUBSCRIPTION_STATUS.CANCELLED;
    subscription.occupiesActiveSlot = subscriptionOccupiesSlot(
      VENDOR_SUBSCRIPTION_STATUS.CANCELLED,
    );
    await subscription.save();
    vendor.commission = resolveVendorCommission(vendor, null, settings);
    vendor.planId = null;
    await vendor.save();
    await auditUpdate(
      createAuditContext(request, session),
      "vendor",
      String(vendor._id),
      before as unknown as Record<string, unknown>,
      vendor.toObject() as unknown as Record<string, unknown>,
    );
    return successResponse(
      { vendor: vendor.toObject() },
      "Free/manual subscription cancelled",
    );
  },
);

export const PATCH = withApi<RouteParams>(
  {
    auth: "admin",
    rateLimit: { action: "admin:vendorSub:cancelReverse", preset: "moderate" },
  },
  async ({ params }) => {
    const settings = await assertPlansEnabled();
    const { id } = params;
    if (!isValidObjectId(id)) return notFoundResponse("Vendor");
    const subscription = await VendorSubscription.findOne({
      vendorId: id,
      provider: "stripe",
      occupiesActiveSlot: true,
      cancelAtPeriodEnd: true,
    });
    if (!subscription?.paymentProviderRef) {
      throw new ValidationError("No scheduled Stripe cancellation was found");
    }
    const stripe = getStripeForSecretKey(assertStripeBillingReady(settings));
    const snapshot = await reverseStripeVendorCancellation(
      stripe,
      subscription.paymentProviderRef,
    );
    await synchronizeVendorBilling(snapshot, {
      eventType: "admin.cancellation_reversed",
    });
    subscription.cancelAtPeriodEnd = false;
    subscription.pendingChangeType = null;
    subscription.pendingChangeStatus = null;
    subscription.pendingChangeEffectiveAt = null;
    await subscription.save();
    return successResponse(
      { subscription: subscription.toObject() },
      "Scheduled cancellation reversed",
    );
  },
);
