import { mongoose } from "@/lib/db";
import { ORDER_STATUS, PAYMENT_STATUS } from "@/config/app.config";
import type { IOrder, OrderItem, SubOrder, Address } from "@/types";

const { Schema, models, model } = mongoose;

/**
 * Address Sub-Schema
 */
const AddressSchema = new Schema<Address>(
  {
    fullName: { type: String },
    firstName: { type: String },
    lastName: { type: String },
    street: { type: String, required: true },
    apartment: { type: String },
    city: { type: String, required: true },
    state: { type: String, required: true },
    postalCode: { type: String, required: true },
    country: { type: String, required: true },
    phone: { type: String },
  },
  { _id: false }
);

/**
 * Order Item Sub-Schema
 */
const OrderItemSchema = new Schema<OrderItem>(
  {
    productId: {
      type: Schema.Types.ObjectId,
      ref: "Product",
      required: true,
    },
    variantId: {
      type: Schema.Types.ObjectId,
    },
    vendorId: {
      type: Schema.Types.ObjectId,
      ref: "Vendor",
      required: true,
    },
    name: {
      type: String,
      required: true,
    },
    sku: {
      type: String,
      required: true,
    },
    price: {
      type: Number,
      required: true,
      min: 0,
    },
    quantity: {
      type: Number,
      required: true,
      min: 1,
    },
    image: {
      type: String,
    },
    purchaseType: {
      type: String,
      enum: ["standard", "preorder"],
      default: "standard",
    },
    preorderReleaseDate: {
      type: Date,
    },
    preorderMessage: {
      type: String,
      trim: true,
      maxlength: 500,
    },
    preorderStatus: {
      type: String,
      enum: [
        "reserved",
        "payment_due",
        "delayed",
        "partially_ready",
        "ready",
        "fulfilled",
        "cancelled",
        "expired",
      ],
    },
    preorderPaymentMode: {
      type: String,
      enum: ["full", "deposit", "pay_later"],
    },
    preorderDepositAmount: {
      type: Number,
      min: 0,
    },
    preorderOutstandingAmount: {
      type: Number,
      min: 0,
    },
    preorderSupplierEta: {
      type: Date,
    },
    preorderBatchName: {
      type: String,
      trim: true,
      maxlength: 120,
    },
    customs: {
      countryOfOrigin: { type: String, trim: true },
      hsCode: { type: String, trim: true },
      description: { type: String, trim: true, maxlength: 500 },
      weight: { type: Number, min: 0 },
      weightUnit: { type: String, enum: ["g", "kg", "lb", "oz"] },
    },
    // Per-line discount (applied before any order-level discount)
    lineDiscount: {
      type: {
        type: String,
        enum: ["percent", "amount"],
      },
      value: { type: Number, min: 0 },
      amount: { type: Number, min: 0, default: 0 },
    },
    // Per-line note attached by the cashier
    lineNote: {
      type: String,
      trim: true,
      maxlength: 500,
    },
  },
  { _id: false }
);

/**
 * Sub-Order Schema (for multi-vendor order splitting)
 */
const SubOrderSchema = new Schema<SubOrder>({
  vendorId: {
    type: Schema.Types.ObjectId,
    ref: "Vendor",
    required: true,
  },
  items: {
    type: [OrderItemSchema],
    default: [],
  },
  subtotal: {
    type: Number,
    required: true,
    min: 0,
  },
  commission: {
    type: Number,
    required: true,
    min: 0,
  },
  vendorEarnings: {
    type: Number,
    required: true,
    min: 0,
  },
  // Shipping charged for this vendor's shipment. In multi-vendor carts each
  // sub-order is rated independently and the order-level shippingCost is the
  // sum of these.
  shippingCost: {
    type: Number,
    default: 0,
    min: 0,
  },
  shippingMethod: {
    name: { type: String },
    optionId: { type: String },
    minDays: { type: Number },
    maxDays: { type: Number },
  },
  status: {
    type: String,
    enum: Object.values(ORDER_STATUS),
    default: ORDER_STATUS.PENDING,
  },
  trackingNumber: {
    type: String,
  },
  shippedAt: {
    type: Date,
  },
  deliveredAt: {
    type: Date,
  },
  // True while this sub-order's items currently hold a reservation against
  // product stock. Set to true after a successful decrement, flipped back to
  // false (atomically) when its inventory has been restored. Cancel/refund
  // paths use this to avoid double-restoring or restoring a sub-order that
  // never decremented in the first place (e.g., abandoned PayPal orders).
  inventoryReserved: {
    type: Boolean,
    default: false,
  },
  preorderReserved: {
    type: Boolean,
    default: false,
  },
  payoutStatus: {
    type: String,
    enum: ["unpaid", "scheduled", "paid"],
    default: "unpaid",
    index: true,
  },
  payoutId: {
    type: Schema.Types.ObjectId,
    ref: "Payout",
  },
  payoutDate: {
    type: Date,
  },
});

/**
 * Order Schema
 */
const OrderSchema = new Schema<IOrder>(
  {
    orderNumber: {
      type: String,
      required: true,
      unique: true,
    },
    customerId: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    items: {
      type: [OrderItemSchema],
      required: true,
    },
    subOrders: {
      type: [SubOrderSchema],
      default: [],
    },
    // Per-file download counters for digital deliverables, keyed by the
    // product's digitalAssets._id. Written atomically by the order-gated
    // download route; entitlements themselves are derived from the ordered
    // products, so this only tracks usage against a product's downloadLimit.
    digitalDownloads: {
      type: [
        new Schema(
          {
            assetId: { type: String, required: true },
            count: { type: Number, default: 0 },
            lastDownloadedAt: { type: Date },
          },
          { _id: false },
        ),
      ],
      default: undefined,
    },
    shippingAddress: {
      type: AddressSchema,
      required: true,
    },
    // True when no item on the order needs physical shipping. Digital-only
    // checkouts collect billing only; shippingAddress then holds a copy of
    // the billing address so downstream consumers always have an address —
    // this flag lets displays label it correctly.
    digitalOnly: {
      type: Boolean,
      default: false,
    },
    billingAddress: {
      type: AddressSchema,
    },
    paymentMethod: {
      type: String,
      required: true,
    },
    paymentStatus: {
      type: String,
      enum: Object.values(PAYMENT_STATUS),
      default: PAYMENT_STATUS.PENDING,
    },
    // Currency the order was charged in, frozen at creation. Refunds and the
    // ledger must use this — resolving from the CURRENT default currency
    // mislabels historical orders whenever the store currency changes.
    currency: {
      type: String,
      trim: true,
      uppercase: true,
    },
    // Denormalized running total of succeeded refunds. Written via an atomic
    // guarded update so two concurrent refunds cannot both pass the cap check.
    // Null on legacy orders — refund handlers seed it from the historical
    // PaymentTransaction aggregate on first touch.
    refundedTotal: {
      type: Number,
    },
    // Short-lived claim serializing return-request creation per order, so two
    // concurrent requests can't both pass the returnable-quantity validation.
    // Stale claims (crashed request) expire after a few seconds.
    returnRequestLockAt: {
      type: Date,
    },
    // Client-generated idempotency key for POS sales. A network blip after the
    // server commits but before the client sees the response makes the cashier
    // retry — the unique partial index below turns that retry into "return the
    // existing order" instead of a second sale + double stock decrement.
    posClientRequestId: {
      type: String,
      trim: true,
    },
    paymentId: {
      type: String,
    },
    stripeSessionId: {
      type: String,
    },
    stripePaymentIntentId: {
      type: String,
    },
    // Indexed via the unique partial indexes declared below — a field-level
    // `index: true` here would collide with them (same key, different options).
    paypalOrderId: {
      type: String,
    },
    paypalCaptureId: {
      type: String,
    },
    razorpayOrderId: {
      type: String,
    },
    razorpayPaymentId: {
      type: String,
    },
    paystackReference: {
      type: String,
    },
    paystackTransactionId: {
      type: String,
    },
    pesapalOrderTrackingId: {
      type: String,
    },
    pesapalMerchantReference: {
      type: String,
    },
    pesapalConfirmationCode: {
      type: String,
    },
    iotecTransactionId: {
      type: String,
    },
    iotecExternalId: {
      type: String,
    },
    subtotal: {
      type: Number,
      required: true,
      min: 0,
    },
    shippingCost: {
      type: Number,
      default: 0,
      min: 0,
    },
    // Selected shipping method for single-shipment orders. Multi-vendor orders
    // additionally carry a per-subOrder shippingMethod.
    shippingMethod: {
      name: { type: String },
      optionId: { type: String },
      minDays: { type: Number },
      maxDays: { type: Number },
    },
    // Import duties/customs collected at checkout (DDP) or deferred to the
    // customer on delivery (DDU/DAP).
    customs: {
      dutyAmount: { type: Number, default: 0, min: 0 },
      dutyMode: { type: String, enum: ["DDP", "DDU"] },
      international: { type: Boolean, default: false },
      collectedAtCheckout: { type: Boolean, default: false },
    },
    tax: {
      type: Number,
      default: 0,
      min: 0,
    },
    discount: {
      type: Number,
      default: 0,
      min: 0,
    },
    discountMeta: {
      source: {
        type: String,
        enum: ["pos", "coupon", "manual", "other"],
        default: "pos",
      },
      type: {
        type: String,
        enum: ["percent", "amount"],
      },
      value: {
        type: Number,
        min: 0,
      },
      reason: {
        type: String,
        trim: true,
      },
      note: {
        type: String,
        trim: true,
      },
    },
    coupon: {
      code: {
        type: String,
        uppercase: true,
        trim: true,
      },
      type: {
        type: String,
      },
      value: {
        type: Number,
        min: 0,
      },
      couponId: {
        type: Schema.Types.ObjectId,
        ref: "Coupon",
      },
      // Set to true once the coupon's usedCount has actually been
      // incremented for this order. Used to gate decrementing on
      // cancel/refund so we never under- or over-count usage.
      usageIncremented: {
        type: Boolean,
        default: false,
      },
    },
    total: {
      type: Number,
      required: true,
      min: 0,
    },
    hasPreorder: {
      type: Boolean,
      default: false,
      index: true,
    },
    preorderStatus: {
      type: String,
      enum: [
        "reserved",
        "payment_due",
        "delayed",
        "partially_ready",
        "ready",
        "fulfilled",
        "cancelled",
        "expired",
      ],
      index: true,
    },
    preorderReleaseDate: {
      type: Date,
      index: true,
    },
    preorderReserved: {
      type: Boolean,
      default: false,
    },
    preorderAcknowledgedAt: {
      type: Date,
    },
    preorderPaymentMode: {
      type: String,
      enum: ["full", "deposit", "pay_later"],
    },
    preorderDepositAmount: {
      type: Number,
      min: 0,
    },
    preorderOutstandingAmount: {
      type: Number,
      min: 0,
    },
    preorderOriginalReleaseDate: {
      type: Date,
    },
    preorderDelayReason: {
      type: String,
      trim: true,
      maxlength: 500,
    },
    preorderReleaseDateUpdatedAt: {
      type: Date,
    },
    preorderCustomerNotifiedAt: {
      type: Date,
    },
    channel: {
      type: String,
      enum: ["online", "pos"],
      default: "online",
      index: true,
    },
    posLocationId: {
      type: String,
      index: true,
    },
    staffId: {
      type: String,
    },
    status: {
      type: String,
      enum: Object.values(ORDER_STATUS),
      default: ORDER_STATUS.PENDING,
    },
    trackingNumber: {
      type: String,
      trim: true,
      maxlength: 100,
    },
    carrier: {
      type: String,
      trim: true,
      maxlength: 100,
    },
    processingAt: {
      type: Date,
    },
    shippedAt: {
      type: Date,
    },
    deliveredAt: {
      type: Date,
    },
    cancelledAt: {
      type: Date,
    },
    cancelReason: {
      type: String,
      trim: true,
      maxlength: 500,
    },
    statusChangedBy: {
      type: String,
      trim: true,
    },
    notes: {
      type: String,
      maxlength: 1000,
    },
  },
  {
    timestamps: true,
    toJSON: { virtuals: true },
    toObject: { virtuals: true },
  }
);

// Indexes
// Compound indexes match the real list-query shapes (filter field + createdAt
// desc sort) so admin/customer/vendor/staff order lists seek instead of doing
// an in-memory sort over a filtered collection scan. Each compound's leading
// field also serves the equality-only lookups, so the former single-field
// {customerId}, {status}, {paymentStatus}, {subOrders.vendorId} indexes are
// redundant prefixes and have been folded in. (Drop those stale single-field
// indexes from existing databases via a migration — Mongoose autoIndex only
// creates, it never drops.)
OrderSchema.index({ status: 1, createdAt: -1 });
OrderSchema.index({ paymentStatus: 1, createdAt: -1 });
OrderSchema.index({ customerId: 1, createdAt: -1 });
OrderSchema.index({ "subOrders.vendorId": 1, createdAt: -1 });
OrderSchema.index({ channel: 1, staffId: 1, createdAt: -1 });
OrderSchema.index({ createdAt: -1 });
OrderSchema.index({ customerId: 1, "coupon.code": 1 });
OrderSchema.index({ channel: 1, posLocationId: 1 });

// One order per Stripe payment. Partial so the many orders without a Stripe
// id (COD/POS/other gateways, or empty-string values) do not collide. This
// makes finalizeStripe*Order's duplicate-key (11000) guard effective and
// prevents the webhook + /verify fallback from racing into two orders for
// one payment.
OrderSchema.index(
  { stripePaymentIntentId: 1 },
  {
    unique: true,
    partialFilterExpression: { stripePaymentIntentId: { $gt: "" } },
  },
);
OrderSchema.index(
  { stripeSessionId: 1 },
  {
    unique: true,
    partialFilterExpression: { stripeSessionId: { $gt: "" } },
  },
);
OrderSchema.index(
  { pesapalOrderTrackingId: 1 },
  {
    unique: true,
    partialFilterExpression: { pesapalOrderTrackingId: { $gt: "" } },
  },
);
OrderSchema.index(
  { pesapalMerchantReference: 1 },
  {
    unique: true,
    partialFilterExpression: { pesapalMerchantReference: { $gt: "" } },
  },
);
// One POS order per client sale attempt (idempotency key). Partial so the
// many orders without a key don't collide.
OrderSchema.index(
  { posClientRequestId: 1 },
  {
    unique: true,
    partialFilterExpression: { posClientRequestId: { $gt: "" } },
  },
);
// Mirror the Stripe/Pesapal duplicate-order protection for the remaining
// gateways: nothing should ever create two orders with the same gateway
// reference, and the DB now enforces it.
OrderSchema.index(
  { paypalOrderId: 1 },
  {
    unique: true,
    partialFilterExpression: { paypalOrderId: { $gt: "" } },
  },
);
OrderSchema.index(
  { razorpayOrderId: 1 },
  {
    unique: true,
    partialFilterExpression: { razorpayOrderId: { $gt: "" } },
  },
);
OrderSchema.index(
  { paystackReference: 1 },
  {
    unique: true,
    partialFilterExpression: { paystackReference: { $gt: "" } },
  },
);
OrderSchema.index(
  { iotecTransactionId: 1 },
  {
    unique: true,
    partialFilterExpression: { iotecTransactionId: { $gt: "" } },
  },
);
OrderSchema.index(
  { iotecExternalId: 1 },
  {
    unique: true,
    partialFilterExpression: { iotecExternalId: { $gt: "" } },
  },
);

// Virtual for customer
OrderSchema.virtual("customer", {
  ref: "User",
  localField: "customerId",
  foreignField: "_id",
  justOne: true,
});

export const Order = models.Order || model<IOrder>("Order", OrderSchema);
