/**
 * Read-only MongoDB schema analysis for Safar Libya migration Phase 1.
 * Does NOT modify any data.
 */
import mongoose from "mongoose";
import fs from "fs";
import path from "path";

const uri = process.env.MONGODB_URI || "mongodb://localhost:27017/safar_libya";
const SAMPLE_N = 3;
const OUT = path.join(process.cwd(), "scripts", "mongo-analysis-raw.json");

type FieldInfo = {
  types: Set<string>;
  nullish: number;
  array: boolean;
  nestedKeys?: Record<string, FieldInfo>;
  sampleValues: unknown[];
};

function typeOf(v: unknown): string {
  if (v === null) return "null";
  if (v === undefined) return "undefined";
  if (Array.isArray(v)) return "array";
  if (v instanceof Date) return "date";
  if (typeof v === "object" && v !== null && (v as { _bsontype?: string })._bsontype === "ObjectID") {
    return "objectId";
  }
  if (typeof v === "object" && v !== null && typeof (v as { toHexString?: () => string }).toHexString === "function") {
    return "objectId";
  }
  if (Buffer.isBuffer(v)) return "buffer";
  return typeof v;
}

function mergeField(info: FieldInfo | undefined, value: unknown, depth = 0): FieldInfo {
  const out: FieldInfo = info || { types: new Set(), nullish: 0, array: false, sampleValues: [] };
  const t = typeOf(value);
  if (t === "null" || t === "undefined") {
    out.nullish += 1;
    return out;
  }
  out.types.add(t);
  if (out.sampleValues.length < 3) {
    if (t === "object" || t === "array") {
      try {
        out.sampleValues.push(JSON.parse(JSON.stringify(value)));
      } catch {
        out.sampleValues.push(String(value));
      }
    } else if (t === "objectId") {
      out.sampleValues.push(String(value));
    } else if (t === "date") {
      out.sampleValues.push((value as Date).toISOString());
    } else {
      out.sampleValues.push(value);
    }
  }

  if (t === "array") {
    out.array = true;
    const arr = value as unknown[];
    if (!out.nestedKeys) out.nestedKeys = {};
    const elemKey = "__element__";
    for (const el of arr.slice(0, 5)) {
      out.nestedKeys[elemKey] = mergeField(out.nestedKeys[elemKey], el, depth + 1);
    }
  } else if (t === "object" && depth < 4) {
    if (!out.nestedKeys) out.nestedKeys = {};
    for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
      out.nestedKeys[k] = mergeField(out.nestedKeys[k], v, depth + 1);
    }
  }
  return out;
}

function serializeField(info: FieldInfo): Record<string, unknown> {
  const nested: Record<string, unknown> | undefined = info.nestedKeys
    ? Object.fromEntries(Object.entries(info.nestedKeys).map(([k, v]) => [k, serializeField(v)]))
    : undefined;
  return {
    types: [...info.types],
    nullishCount: info.nullish,
    isArray: info.array,
    nested: nested,
    sampleValues: info.sampleValues,
  };
}

function pickSample(doc: Record<string, unknown>) {
  // strip large binary-ish / redact password hashes partially
  const clone = JSON.parse(JSON.stringify(doc, (_k, v) => {
    if (typeof v === "string" && v.length > 200) return v.slice(0, 80) + "…";
    return v;
  }));
  if (clone.passwordHash) clone.passwordHash = "[REDACTED]";
  if (clone.tokenHash) clone.tokenHash = "[REDACTED]";
  if (clone.codeHash) clone.codeHash = "[REDACTED]";
  return clone;
}

async function main() {
  await mongoose.connect(uri);
  const db = mongoose.connection.db!;
  const cols = (await db.listCollections().toArray()).map((c) => c.name).sort();

  const report: Record<string, unknown> = {
    database: db.databaseName,
    analyzedAt: new Date().toISOString(),
    collectionCount: cols.length,
    collections: {},
  };

  for (const name of cols) {
    const col = db.collection(name);
    const count = await col.countDocuments();
    const samples = await col.find({}).limit(SAMPLE_N).toArray();
    // scan more docs for schema inference (up to 200)
    const scan = await col.find({}).limit(Math.min(200, Math.max(count, SAMPLE_N))).toArray();
    const fields: Record<string, FieldInfo> = {};
    for (const doc of scan) {
      for (const [k, v] of Object.entries(doc)) {
        fields[k] = mergeField(fields[k], v);
      }
    }

    // indexes
    const indexes = await col.indexes();

    (report.collections as Record<string, unknown>)[name] = {
      count,
      indexes: indexes.map((i) => ({ name: i.name, key: i.key, unique: Boolean(i.unique), sparse: Boolean(i.sparse) })),
      fields: Object.fromEntries(Object.entries(fields).map(([k, v]) => [k, serializeField(v)])),
      samples: samples.map((s) => pickSample(s as Record<string, unknown>)),
    };
    console.error(`analyzed ${name}: ${count}`);
  }

  fs.writeFileSync(OUT, JSON.stringify(report, null, 2), "utf8");
  console.log(`Wrote ${OUT}`);
  console.log(`database=${db.databaseName} collections=${cols.length}`);
  for (const name of cols) {
    const c = (report.collections as any)[name].count;
    console.log(`${String(c).padStart(6)}  ${name}`);
  }
  await mongoose.disconnect();
}

main().catch(async (e) => {
  console.error(e);
  try {
    await mongoose.disconnect();
  } catch {
    /* ignore */
  }
  process.exit(1);
});
