/**
 * MongoDB adapter for oidc-provider.
 *
 * Stores OIDC tokens, sessions, and grants in an `oidc_payloads` collection
 * with TTL indexes for automatic cleanup. Uses the existing Mongoose connection.
 */
import mongoose from "mongoose";
import { connectDB } from "./mongoose.js";

const collectionName = "oidc_payloads";

type MongoPayload = {
  _id: string;
  payload: Record<string, unknown>;
  expiresAt?: Date;
  userCode?: string;
  uid?: string;
  grantId?: string;
};

let collectionReady = false;

async function getCollection() {
  await connectDB();
  const db = mongoose.connection.db!;
  const col = db.collection<MongoPayload>(collectionName);

  if (!collectionReady) {
    // TTL index — MongoDB automatically removes documents after expiresAt
    await col
      .createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
      .catch(() => {});
    // Lookup indexes
    await col.createIndex({ "payload.grantId": 1 }).catch(() => {});
    await col.createIndex({ "payload.userCode": 1 }).catch(() => {});
    await col.createIndex({ "payload.uid": 1 }).catch(() => {});
    collectionReady = true;
  }

  return col;
}

function prefixedId(model: string, id: string) {
  return `${model}:${id}`;
}

export class MongoAdapter {
  model: string;

  constructor(model: string) {
    this.model = model;
  }

  async upsert(
    id: string,
    payload: Record<string, unknown>,
    expiresIn: number
  ) {
    const col = await getCollection();
    const expiresAt = expiresIn
      ? new Date(Date.now() + expiresIn * 1000)
      : undefined;

    await col.updateOne(
      { _id: prefixedId(this.model, id) as any },
      {
        $set: {
          payload,
          ...(expiresAt ? { expiresAt } : {}),
        },
      },
      { upsert: true }
    );
  }

  async find(id: string) {
    const col = await getCollection();
    const doc = await col.findOne({ _id: prefixedId(this.model, id) as any });
    if (!doc) return undefined;
    return doc.payload;
  }

  async findByUserCode(userCode: string) {
    const col = await getCollection();
    const doc = await col.findOne({ "payload.userCode": userCode });
    if (!doc) return undefined;
    return doc.payload;
  }

  async findByUid(uid: string) {
    const col = await getCollection();
    const doc = await col.findOne({ "payload.uid": uid });
    if (!doc) return undefined;
    return doc.payload;
  }

  async consume(id: string) {
    const col = await getCollection();
    await col.updateOne(
      { _id: prefixedId(this.model, id) as any },
      { $set: { "payload.consumed": Math.floor(Date.now() / 1000) } }
    );
  }

  async destroy(id: string) {
    const col = await getCollection();
    await col.deleteOne({ _id: prefixedId(this.model, id) as any });
  }

  async revokeByGrantId(grantId: string) {
    const col = await getCollection();
    await col.deleteMany({ "payload.grantId": grantId });
  }
}
