import {
  Product,
  User,
  Vendor,
  VendorApplication,
  VendorPlan,
  VendorSubscription,
  VendorSubscriptionPayment,
} from "@/models";
import type { Types } from "mongoose";
import { connectDB } from "@/lib/db";
import { NotFoundError, ValidationError } from "@/lib/api/errors";
import { requestEmailVerification } from "@/lib/auth";
import { defaultLocale } from "@/config/i18n.config";
import {
  USER_ACCOUNT_STATUS,
  USER_ROLES,
  VENDOR_APPLICATION_PAYMENT_STATUS,
  VENDOR_APPLICATION_STATUS,
  VENDOR_BILLING_INTERVAL,
  VENDOR_PAYMENT_INVITATION,
  VENDOR_STATUS,
} from "@/config/app.config";
import {
  ALL_VENDOR_PERMISSIONS,
  type VendorPermission,
} from "@/config/permissions.config";
import { successResponse, notFoundResponse } from "@/lib/api/response";
import { setUserRole } from "@/lib/user-role";
import { isStaffRole } from "@/lib/staff-role";
import { getSettings } from "@/models/settings.model";
import { isValidObjectId } from "@/lib/api/validate";
import { rateLimitByUser } from "@/lib/api/rate-limit-middleware";
import {
  DEFAULT_VENDOR_SLUG,
  isDefaultVendorRecord,
  syncDefaultVendorWithSettings,
} from "@/lib/multi-vendor";
import {
  createAuditContext,
  auditDelete,
  auditVendorDecision,
  auditUpdate,
} from "@/lib/audit";
import { getEffectiveSubscription } from "@/lib/vendor-plans";
import { findLatestVendorApplication } from "@/lib/vendor-application";
import {
  sendVendorApprovedEmail,
  sendVendorPaymentRequiredEmail,
} from "@/lib/vendor-emails";
import { notifyVendorApplicationStatus } from "@/lib/notifications";
import { normalizeNotificationSettings } from "@/lib/notification-settings";
import { revalidateProductContent } from "@/lib/cache-invalidation";
import { withApi } from "@/lib/api/handler";
import { cancelVendorApplicationBilling } from "@/lib/vendor-stripe-billing";
import { assertStripeBillingReady } from "@/lib/vendor-plan-stripe";
import { getStripeForSecretKey } from "@/lib/stripe";
import { retrieveVendorBillingSnapshot } from "@/lib/vendor-stripe-adapter";
import {
  assertVendorBillingTerminalForDeletion,
  retireVendorBillingRecords,
} from "@/lib/vendor-billing-deletion";
import {
  areCountryValuesEquivalent,
  isCountryAllowed,
} from "@/lib/country-availability";

function sanitizeVendorPermissions(input: unknown): VendorPermission[] {
  if (!Array.isArray(input)) return [];
  const filtered = input.filter((p: unknown): p is VendorPermission =>
    typeof p === "string" && ALL_VENDOR_PERMISSIONS.includes(p as VendorPermission),
  );
  return Array.from(new Set(filtered));
}

async function assertCanChangeVendorOwnerRole(userId: unknown) {
  const owner = await User.findById(userId).select("role roles").lean();
  const roles = Array.isArray((owner as { roles?: unknown } | null)?.roles)
    ? ((owner as { roles?: string[] }).roles || [])
    : [];
  const role = (owner as { role?: string } | null)?.role;

  if (
    role === USER_ROLES.ADMIN ||
    isStaffRole(role) ||
    roles.includes(USER_ROLES.ADMIN) ||
    roles.some(isStaffRole)
  ) {
    throw new ValidationError(
      "Admin and staff accounts cannot be converted through vendor updates",
    );
  }
}

/**
 * GET /api/admin/vendors/[id]
 * Get single vendor
 */
export const GET = withApi<{ id: string }>(
  { auth: "admin" },
  async ({ params, session }) => {
    const { id } = params;

    if (!isValidObjectId(id)) {
      return notFoundResponse("Vendor");
    }

    await connectDB();
    const settings = await getSettings();
    if (!settings.multiVendorMode?.enabled) throw new NotFoundError("Vendor");
    await syncDefaultVendorWithSettings(session.user.id, settings);

    // Lazily reconcile an expired trial/period before reading the vendor, so the
    // returned commission and subscription status are current.
    const effective = settings.vendorConfig?.plansEnabled
      ? await getEffectiveSubscription(id, { settings })
      : { subscription: null, status: null };
    const latestSubscription = settings.vendorConfig?.plansEnabled
      ? await VendorSubscription.findOne({ vendorId: id })
          .sort({ createdAt: -1 })
          .lean<
            | (Record<string, unknown> & {
                _id?: unknown;
                status?: string;
                pendingChangeStatus?: string | null;
              })
            | null
          >()
      : null;
    const subscription =
      latestSubscription &&
      (latestSubscription.status === "incomplete" ||
        latestSubscription.pendingChangeStatus)
        ? latestSubscription
        : effective.subscription || latestSubscription;
    const lastSubscriptionPayment = subscription?._id
      ? await VendorSubscriptionPayment.findOne({
          subscriptionId: subscription._id,
        })
          .sort({ providerCreatedAt: -1, createdAt: -1 })
          .lean<Record<string, unknown> | null>()
      : null;

    const vendor = await Vendor.findById(id)
      .populate("user", "name email image phone status")
      .lean();

    if (!vendor) {
      return notFoundResponse("Vendor");
    }
    if (isDefaultVendorRecord(vendor)) {
      return notFoundResponse("Vendor");
    }

    return successResponse({
      ...vendor,
      subscription: subscription
        ? {
            ...subscription,
            lastPayment: lastSubscriptionPayment,
          }
        : null,
      subscriptionStatus:
        String(subscription?.status || effective.status || "") || null,
    });
  },
);

/**
 * PUT /api/admin/vendors/[id]
 * Update vendor status or details
 */
export const PUT = withApi<{ id: string }>(
  { auth: "admin" },
  async ({ request, params, session }) => {
    const { id } = params;

    if (!isValidObjectId(id)) {
      return notFoundResponse("Vendor");
    }

    await rateLimitByUser(
      request,
      session.user.id,
      "admin:vendors:update",
      "moderate",
      session.user.role,
    );

    const body = await request.json();

    await connectDB();
    const settings = await getSettings();
    if (!settings.multiVendorMode?.enabled) throw new NotFoundError("Vendor");
    await syncDefaultVendorWithSettings(session.user.id, settings);

    const vendorBefore = await Vendor.findById(id)
      .populate("user", "name email status")
      .lean();

    if (!vendorBefore) {
      return notFoundResponse("Vendor");
    }
    if (isDefaultVendorRecord(vendorBefore)) {
      throw new ValidationError(
        "The default store vendor is managed from General Settings",
      );
    }

    const application = await findLatestVendorApplication({
      vendorId: vendorBefore._id,
      userId: vendorBefore.userId,
    });
    const selectedPlan =
      !application?.planSnapshot && vendorBefore.planId
        ? await VendorPlan.findById(vendorBefore.planId)
            .select("price billingInterval")
            .lean<{ price?: number; billingInterval?: string } | null>()
        : null;
    if (
      body.status === VENDOR_STATUS.PAYMENT_REQUIRED &&
      vendorBefore.status !== VENDOR_STATUS.PAYMENT_REQUIRED
    ) {
      throw new ValidationError(
        "Payment Required is a system-managed status. Approve the paid application to create setup access.",
      );
    }
    const paidApplication = Boolean(
      (application?.planSnapshot || selectedPlan) &&
        (application?.planSnapshot?.billingInterval ||
          selectedPlan?.billingInterval) !==
          VENDOR_BILLING_INTERVAL.NONE &&
        Number(
          application?.planSnapshot?.price || selectedPlan?.price || 0,
        ) > 0,
    );
    const requiresInitialPayment = Boolean(
      body.status === VENDOR_STATUS.APPROVED &&
        paidApplication &&
        application?.paymentStatus !==
          VENDOR_APPLICATION_PAYMENT_STATUS.PAID,
    );
    if (requiresInitialPayment) {
      if (!application) {
        throw new ValidationError(
          "Paid vendor approval requires a submitted application billing record",
        );
      }
      assertStripeBillingReady(settings);
    }

    const updates: Record<string, unknown> = {};
    const unsetFields: Record<string, "" | 1> = {};
    const userUpdates: Record<string, unknown> = {};

    if (body.status && Object.values(VENDOR_STATUS).includes(body.status)) {
      updates.status = requiresInitialPayment
        ? VENDOR_STATUS.PAYMENT_REQUIRED
        : body.status;
      if (body.status === VENDOR_STATUS.APPROVED) {
        updates.storeActive = !requiresInitialPayment;
      }
      if (
        body.status === VENDOR_STATUS.REJECTED ||
        body.status === VENDOR_STATUS.SUSPENDED
      ) {
        updates.storeActive = false;
      }
    }

    if (body.commission !== undefined) {
      updates.commission = body.commission;
    }

    if (body.permissions !== undefined) {
      const sanitizedPermissions = sanitizeVendorPermissions(body.permissions);
      if (sanitizedPermissions.length === 0) {
        throw new ValidationError("Select at least one valid permission");
      }
      updates.permissions = sanitizedPermissions;
    }

    if (body.storeName !== undefined && String(body.storeName).trim()) {
      updates.storeName = String(body.storeName).trim();
    }

    if (body.description !== undefined) {
      updates.description = String(body.description || "").trim() || undefined;
    }

    if (body.notes !== undefined) {
      const normalizedNotes = String(body.notes || "").trim();
      if (normalizedNotes) {
        updates.notes = normalizedNotes;
      } else {
        unsetFields.notes = "";
      }
    }

    if (body.logo !== undefined) {
      const normalizedLogo = String(body.logo || "").trim();
      if (normalizedLogo) {
        updates.logo = normalizedLogo;
      } else {
        unsetFields.logo = "";
      }
    }

    if (body.banner !== undefined) {
      const normalizedBanner = String(body.banner || "").trim();
      if (normalizedBanner) {
        updates.banner = normalizedBanner;
      } else {
        unsetFields.banner = "";
      }
    }

    if (body.address !== undefined) {
      const address = (body.address ?? {}) as Record<string, unknown>;
      const normalizedAddress = {
        street: String(address.street || "").trim(),
        city: String(address.city || "").trim(),
        state: String(address.state || "").trim(),
        postalCode: String(address.postalCode || "").trim(),
        country: String(address.country || "").trim(),
        phone: String(address.phone || "").trim(),
      };
      const previousCountry =
        (vendorBefore as { address?: { country?: unknown } }).address?.country ||
        "";
      const countryChanged = !areCountryValuesEquivalent(
        normalizedAddress.country,
        previousCountry,
      );
      if (
        normalizedAddress.country &&
        countryChanged &&
        !isCountryAllowed(
          normalizedAddress.country,
          settings.general?.countryAvailability,
        )
      ) {
        throw new ValidationError({
          "address.country": ["Selected country is not available"],
        });
      }
      const hasAnyAddressValue = Object.values(normalizedAddress).some(Boolean);
      if (hasAnyAddressValue) {
        updates.address = normalizedAddress;
      } else {
        unsetFields.address = "";
      }
    }

    if (body.bankDetails !== undefined) {
      const bank = (body.bankDetails ?? {}) as Record<string, unknown>;
      const normalizedBank = {
        accountName: String(bank.accountName || "").trim(),
        accountNumber: String(bank.accountNumber || "").trim(),
        bankName: String(bank.bankName || "").trim(),
        routingNumber: String(bank.routingNumber || "").trim(),
        swiftCode: String(bank.swiftCode || "").trim(),
      };
      const hasAnyBankValue = Object.values(normalizedBank).some(Boolean);
      if (hasAnyBankValue) {
        updates.bankDetails = normalizedBank;
      } else {
        unsetFields.bankDetails = "";
      }
    }

    if (body.documents !== undefined) {
      const docs = (body.documents ?? {}) as Record<string, unknown>;
      const normalizedDocs = {
        businessLicense: String(docs.businessLicense || "").trim(),
        taxId: String(docs.taxId || "").trim(),
        taxCertificate: String(docs.taxCertificate || "").trim(),
        governmentId: String(docs.governmentId || "").trim(),
      };
      const hasAnyDocValue = Object.values(normalizedDocs).some(Boolean);
      if (hasAnyDocValue) {
        updates.documents = normalizedDocs;
      } else {
        unsetFields.documents = "";
      }
    }

    if (body.slug !== undefined && String(body.slug).trim()) {
      const slug = String(body.slug)
        .toLowerCase()
        .trim()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/(^-|-$)/g, "");
      if (!slug) throw new ValidationError("Invalid store slug");
      if (slug === DEFAULT_VENDOR_SLUG) {
        throw new ValidationError("This store slug is reserved for the default store");
      }

      const existingSlug = await Vendor.findOne({ slug, _id: { $ne: id } })
        .select("_id")
        .lean();
      if (existingSlug) {
        throw new ValidationError("Store slug already exists");
      }

      updates.slug = slug;
    }

    if (body.ownerName !== undefined && String(body.ownerName).trim()) {
      userUpdates.name = String(body.ownerName).trim();
    }

    if (body.ownerPhone !== undefined) {
      userUpdates.phone = String(body.ownerPhone || "").trim() || undefined;
    }

    if (body.ownerEmail !== undefined) {
      const normalizedEmail = String(body.ownerEmail).trim().toLowerCase();
      if (!normalizedEmail) throw new ValidationError("Owner email is required");

      const existingEmailUser = await User.findOne({
        email: normalizedEmail,
        _id: { $ne: vendorBefore.userId },
      })
        .select("_id")
        .lean();

      if (existingEmailUser) {
        throw new ValidationError("Another user already uses this email");
      }

      userUpdates.email = normalizedEmail;
    }

    if (
      body.userStatus &&
      Object.values(USER_ACCOUNT_STATUS).includes(body.userStatus)
    ) {
      userUpdates.status = body.userStatus;
    }

    if (body.status && body.status !== vendorBefore.status && vendorBefore.userId) {
      await assertCanChangeVendorOwnerRole(vendorBefore.userId);
    }

    const vendor = await Vendor.findByIdAndUpdate(
      id,
      {
        $set: updates,
        ...(Object.keys(unsetFields).length ? { $unset: unsetFields } : {}),
      },
      { new: true },
    ).populate("user", "name email status emailVerified");

    if (!vendor) {
      return notFoundResponse("Vendor");
    }

    if (body.status && body.status !== vendorBefore.status && vendor.userId) {
      if (body.status === VENDOR_STATUS.APPROVED) {
        await setUserRole(vendor.userId.toString(), USER_ROLES.VENDOR);
        if (settings.security?.emailVerificationForVendors) {
          userUpdates.emailVerificationRequiredAt = new Date();
        }
        if (!body.userStatus) {
          userUpdates.status = USER_ACCOUNT_STATUS.ACTIVE;
        }
      } else if (
        body.status === VENDOR_STATUS.REJECTED ||
        body.status === VENDOR_STATUS.SUSPENDED
      ) {
        await setUserRole(vendor.userId.toString(), USER_ROLES.CUSTOMER);
        if (!body.userStatus) {
          userUpdates.status = USER_ACCOUNT_STATUS.ACTIVE;
        }
      }
    }

    if (body.status && body.status !== vendorBefore.status && vendor.userId) {
      if (application) {
        if (body.status === VENDOR_STATUS.APPROVED) {
          application.status = VENDOR_APPLICATION_STATUS.APPROVED;
          const approvedAt = new Date();
          application.approvedAt = approvedAt;
          if (requiresInitialPayment) {
            application.paymentStatus =
              VENDOR_APPLICATION_PAYMENT_STATUS.PENDING;
            application.paymentDueAt = new Date(
              approvedAt.getTime() +
                VENDOR_PAYMENT_INVITATION.DEADLINE_DAYS * 24 * 60 * 60 * 1000,
            );
            application.paymentExpiredAt = null;
            application.setupAccessExpiredAt = null;
            application.paymentReminder3SentAt = null;
            application.paymentReminder6SentAt = null;
          } else {
            application.paymentDueAt = null;
          }
          application.lastError = null;
          await application.save();
        } else if (body.status === VENDOR_STATUS.REJECTED) {
          application.status = VENDOR_APPLICATION_STATUS.REJECTED;
          application.rejectedAt = new Date();
          application.lastError = null;
          await application.save();
          if (
            application.paymentStatus ===
            VENDOR_APPLICATION_PAYMENT_STATUS.PAID
          ) {
            await cancelVendorApplicationBilling(
              application,
              settings,
            ).catch((error) =>
              console.error(
                "Failed to cancel rejected vendor billing:",
                error,
              ),
            );
          }
        }
      }
    }

    if (Object.keys(userUpdates).length > 0 && vendor.userId) {
      await User.updateOne({ _id: vendor.userId }, { $set: userUpdates });
    }

    if (
      body.status === VENDOR_STATUS.APPROVED &&
      body.status !== vendorBefore.status &&
      vendor.userId
    ) {
      const notificationSettings = normalizeNotificationSettings(
        settings.notifications,
      );
      const vendorApplicationChannels =
        notificationSettings.vendor.applicationStatus;
      const vendorUser = vendor.user as {
        name?: string;
        email?: string;
        emailVerified?: boolean;
      } | null;
      const vendorEmail = String(userUpdates.email || vendorUser?.email || "");
      if (
        settings.security?.emailVerificationForVendors &&
        vendorEmail &&
        !vendorUser?.emailVerified
      ) {
        await requestEmailVerification(
          vendorEmail,
          `/${defaultLocale}/email-verified`,
        ).catch(
          (error) => {
            console.error("Failed to request vendor email verification:", error);
          },
        );
      }
      if (vendorApplicationChannels.email && vendorEmail) {
        if (
          requiresInitialPayment &&
          application?.planSnapshot &&
          application.paymentDueAt
        ) {
          await sendVendorPaymentRequiredEmail({
            vendorEmail,
            vendorName: String(userUpdates.name || vendorUser?.name || ""),
            storeName: vendor.storeName,
            planName: application.planSnapshot.name,
            price: application.planSnapshot.price,
            currency: application.planSnapshot.currency,
            billingInterval: application.planSnapshot.billingInterval,
            paymentDueAt: application.paymentDueAt,
            settings,
          });
        } else {
          await sendVendorApprovedEmail({
            vendorEmail,
            vendorName: String(userUpdates.name || vendorUser?.name || ""),
            storeName: vendor.storeName,
            settings,
          });
        }
      }
      await notifyVendorApplicationStatus(
        vendor.userId.toString(),
        requiresInitialPayment
          ? VENDOR_STATUS.PAYMENT_REQUIRED
          : VENDOR_STATUS.APPROVED,
        { settings, channels: vendorApplicationChannels },
      );
    }

    const auditContext = createAuditContext(request, session);

    if (body.status && body.status !== vendorBefore.status) {
      const decision = body.status as "approved" | "rejected" | "suspended";
      await auditVendorDecision(auditContext, id, decision, vendorBefore.storeName);
    } else if (body.commission !== undefined && body.commission !== vendorBefore.commission) {
      await auditUpdate(
        auditContext,
        "vendor",
        id,
        { commission: vendorBefore.commission },
        { commission: body.commission },
        vendorBefore.storeName,
      );
    }

    revalidateProductContent();

    return successResponse(vendor);
  },
);

/**
 * DELETE /api/admin/vendors/[id]
 * Delete vendor and revert user role
 */
export const DELETE = withApi<{ id: string }>(
  {
    auth: "admin",
    rateLimit: { action: "admin:vendors:delete", preset: "strict" },
  },
  async ({ request, params, session }) => {
    const { id } = params;
    if (!isValidObjectId(id)) {
      return notFoundResponse("Vendor");
    }

    await connectDB();
    const settings = await getSettings();
    if (!settings.multiVendorMode?.enabled) throw new NotFoundError("Vendor");
    await syncDefaultVendorWithSettings(session.user.id, settings);

    const vendor = await Vendor.findById(id)
      .populate("user", "name email")
      .lean();

    if (!vendor) {
      return notFoundResponse("Vendor");
    }
    if (isDefaultVendorRecord(vendor)) {
      throw new ValidationError(
        "The default store vendor is managed from General Settings",
      );
    }

    const stripeSubscription = await VendorSubscription.findOne({
      vendorId: id,
      provider: "stripe",
      paymentProviderRef: { $ne: null },
    })
      .sort({ createdAt: -1 })
      .select("paymentProviderRef providerStatus")
      .lean<{
        paymentProviderRef?: string | null;
        providerStatus?: string | null;
      } | null>();
    if (stripeSubscription?.paymentProviderRef) {
      const stripe = getStripeForSecretKey(assertStripeBillingReady(settings));
      try {
        const snapshot = await retrieveVendorBillingSnapshot(
          stripe,
          stripeSubscription.paymentProviderRef,
        );
        assertVendorBillingTerminalForDeletion({
          provider: "stripe",
          providerSubscriptionId: stripeSubscription.paymentProviderRef,
          providerStatus: snapshot.subscription.status,
        });
      } catch (error) {
        const message =
          error instanceof Error ? error.message : String(error);
        if (!/No such subscription/i.test(message)) throw error;
      }
    }

    const productCount = await Product.countDocuments({ vendorId: id });
    if (productCount > 0) {
      throw new ValidationError(
        "Cannot delete vendor with existing products. Reassign or delete products first.",
      );
    }

    if (vendor.userId) {
      await assertCanChangeVendorOwnerRole(vendor.userId);
    }

    await Vendor.deleteOne({ _id: id });

    // Retire the billing rows. They outlive the vendor on purpose (the payment
    // rows are the financial record), but they must not keep occupying the
    // vendor's active subscription slot or counting as live plan usage.
    await retireVendorBillingRecords({
      retireSubscriptions: async (patch) =>
        (await VendorSubscription.updateMany({ vendorId: id }, { $set: patch }))
          .modifiedCount ?? 0,
      retireApplications: async (patch) =>
        (
          await VendorApplication.updateMany(
            {
              vendorId: id,
              status: { $ne: VENDOR_APPLICATION_STATUS.REJECTED },
            },
            { $set: patch },
          )
        ).modifiedCount ?? 0,
    }).catch((err) =>
      console.error("Failed to retire billing records for deleted vendor:", err),
    );

    // Deactivate the deleted vendor's coupons so they can't keep being
    // redeemed against a vendor that no longer exists.
    const { Coupon } = await import("@/models");
    const { CouponStatus } = await import("@/models/coupon.model");
    await Coupon.updateMany(
      { vendorId: id },
      { $set: { status: CouponStatus.INACTIVE } },
    ).catch((err) =>
      console.error("Failed to deactivate coupons for deleted vendor:", err),
    );

    // Messaging connections hold live encrypted Meta access tokens plus the
    // phoneNumberId/pageId the inbound webhook routes on. Leaving them behind
    // means Storify keeps receiving and sending on behalf of a store that no
    // longer exists, and keeps custody of credentials nobody can revoke here.
    const { ChannelConnection, WhatsAppTemplate } = await import("@/models");
    try {
      // Templates are keyed by connection, not by vendor, so they have to be
      // collected before the connections go away.
      const connectionIds = (
        await ChannelConnection.find({ ownerKey: `vendor:${id}` })
          .select("_id")
          .lean<Array<{ _id: Types.ObjectId }>>()
      ).map((connection) => connection._id);
      if (connectionIds.length) {
        await WhatsAppTemplate.deleteMany({
          channelConnectionId: { $in: connectionIds },
        });
        await ChannelConnection.deleteMany({ _id: { $in: connectionIds } });
      }
    } catch (err) {
      console.error(
        "Failed to remove messaging connections for deleted vendor:",
        err,
      );
    }

    if (vendor.userId) {
      await setUserRole(String(vendor.userId), USER_ROLES.CUSTOMER);
      await User.updateOne(
        { _id: vendor.userId },
        { $set: { status: USER_ACCOUNT_STATUS.ACTIVE } },
      );
    }

    const auditContext = createAuditContext(request, session);
    await auditDelete(
      auditContext,
      "vendor",
      id,
      { storeName: vendor.storeName, userId: String(vendor.userId || "") },
      vendor.storeName,
    );

    revalidateProductContent();

    return successResponse({ message: "Vendor deleted successfully" });
  },
);
