import { Document, Model, Schema, Types, model } from "mongoose";

export type WalletTransactionType =
  | "topup"
  | "payment_debit"
  | "payment_credit"
  | "withdrawal"
  | "withdrawal_reversal"
  | "refund"
  | "adjustment";

export type WalletTransactionStatus = "pending" | "processing" | "completed" | "failed";

export interface IWalletTransaction extends Document {
  userId: Types.ObjectId;
  /** Positive = credit, negative = debit */
  deltaCents: number;
  balanceAfterCents: number;
  currency: string;
  type: WalletTransactionType;
  status: WalletTransactionStatus;
  description: string;
  stripePaymentIntentId?: string | null;
  externalRef?: string | null;
  metadata?: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
}

const WalletTransactionSchema = new Schema<IWalletTransaction>(
  {
    userId: { type: Schema.Types.ObjectId, ref: "User", required: true },
    deltaCents: { type: Number, required: true },
    balanceAfterCents: { type: Number, required: true },
    currency: { type: String, default: "usd", trim: true, lowercase: true },
    type: {
      type: String,
      enum: [
        "topup",
        "payment_debit",
        "payment_credit",
        "withdrawal",
        "withdrawal_reversal",
        "refund",
        "adjustment",
      ],
      required: true,
    },
    status: {
      type: String,
      enum: ["pending", "processing", "completed", "failed"],
      default: "completed",
    },
    description: { type: String, default: "", trim: true, maxlength: 2000 },
    stripePaymentIntentId: { type: String, trim: true },
    externalRef: { type: String, trim: true },
    metadata: { type: Schema.Types.Mixed },
  },
  { timestamps: true }
);

WalletTransactionSchema.index({ userId: 1, createdAt: -1 });

/** Non-unique: fast lookup by Stripe PI; idempotency is enforced in application code. */
WalletTransactionSchema.index(
  { stripePaymentIntentId: 1 },
  {
    name: "stripePaymentIntentId_partial",
    partialFilterExpression: { stripePaymentIntentId: { $type: "string" } },
  }
);
WalletTransactionSchema.index(
  { userId: 1, externalRef: 1 },
  {
    name: "userId_externalRef_partial",
    partialFilterExpression: { externalRef: { $type: "string" } },
  }
);

const WalletTransactionModel: Model<IWalletTransaction> =
  model<IWalletTransaction>("WalletTransaction", WalletTransactionSchema);

const DESIRED_WALLET_TX_INDEX_NAMES = new Set([
  "stripePaymentIntentId_partial",
  "userId_externalRef_partial",
  "userId_1_createdAt_-1",
  "_id_",
]);

/** Former unique index names — dropped on startup so sync can replace with non-unique. */
const LEGACY_UNIQUE_INDEX_NAMES = new Set([
  "stripePaymentIntentId_unique_partial",
  "userId_externalRef_unique_partial",
]);

/**
 * Aligns WalletTransaction indexes with the schema and removes obsolete unique indexes.
 */
export async function ensureWalletTransactionIndexes(): Promise<void> {
  const collection = WalletTransactionModel.collection;

  let existing: Array<{
    name?: string;
    key?: Record<string, number>;
    sparse?: boolean;
    unique?: boolean;
    partialFilterExpression?: Record<string, unknown>;
  }> = [];
  try {
    existing = (await collection.indexes()) as typeof existing;
  } catch (e) {
    console.warn(
      "[wallet-indexes] Could not list indexes (collection may not exist yet):",
      e instanceof Error ? e.message : e
    );
  }

  for (const idx of existing) {
    if (!idx.name || idx.name === "_id_") continue;

    if (LEGACY_UNIQUE_INDEX_NAMES.has(idx.name)) {
      try {
        await collection.dropIndex(idx.name);
        console.warn(
          `[wallet-indexes] Dropped legacy unique index "${idx.name}" (replaced by non-unique partial index).`
        );
      } catch (e) {
        console.error(
          `[wallet-indexes] Failed to drop legacy index "${idx.name}":`,
          e instanceof Error ? e.message : e
        );
      }
      continue;
    }

    if (DESIRED_WALLET_TX_INDEX_NAMES.has(idx.name)) continue;

    const keys = Object.keys(idx.key ?? {});
    const isLegacyTarget =
      (keys.length === 1 && keys[0] === "stripePaymentIntentId") ||
      (keys.length === 2 &&
        keys.includes("userId") &&
        keys.includes("externalRef"));

    if (isLegacyTarget) {
      try {
        await collection.dropIndex(idx.name);
        console.warn(
          `[wallet-indexes] Dropped legacy index "${idx.name}" (sparse=${idx.sparse}, unique=${idx.unique}).`
        );
      } catch (e) {
        console.error(
          `[wallet-indexes] Failed to drop legacy index "${idx.name}":`,
          e instanceof Error ? e.message : e
        );
      }
    }
  }

  try {
    await WalletTransactionModel.syncIndexes();
    console.log("[wallet-indexes] Indexes synced to schema.");
  } catch (e) {
    console.error(
      "[wallet-indexes] syncIndexes failed:",
      e instanceof Error ? e.message : e
    );
  }
}

export default WalletTransactionModel;
