/**
 * Self-test ledger read cutover (LEDGER_DATABASE=mysql).
 * Extra: VOID exclusion (...df805d) + Mongo↔MySQL posted-sum parity.
 */
import fs from "fs";
import path from "path";
import mysql from "mysql2/promise";
import mongoose from "mongoose";

const VOID_ID = "cmrxh9kd4f7821148453e76df805d";
const CANONICAL_ID = "cmrxh9ko07d94b53da748e78cb531";

function loadEnv() {
  const t = fs.readFileSync(path.join(process.cwd(), ".env"), "utf8");
  for (const line of t.split(/\r?\n/)) {
    const s = line.trim();
    if (!s || s.startsWith("#")) continue;
    const eq = s.indexOf("=");
    if (eq <= 0) continue;
    const k = s.slice(0, eq).trim();
    let v = s.slice(eq + 1).trim();
    if (
      (v.startsWith('"') && v.endsWith('"')) ||
      (v.startsWith("'") && v.endsWith("'"))
    )
      v = v.slice(1, -1);
    process.env[k] = v;
  }
}

function nearly(a: number, b: number) {
  return Math.abs(Number(a) - Number(b)) < 0.02;
}

function sumMap(
  rows: Array<{ type: string; count: number; amountLyd: number; amountTnd: number }>,
) {
  const m = new Map<string, { count: number; amountLyd: number; amountTnd: number }>();
  for (const r of rows) {
    m.set(r.type, {
      count: Number(r.count),
      amountLyd: Number(r.amountLyd),
      amountTnd: Number(r.amountTnd),
    });
  }
  return m;
}

function mapsEqual(
  a: Map<string, { count: number; amountLyd: number; amountTnd: number }>,
  b: Map<string, { count: number; amountLyd: number; amountTnd: number }>,
) {
  if (a.size !== b.size) return false;
  for (const [k, va] of a) {
    const vb = b.get(k);
    if (!vb) return false;
    if (va.count !== vb.count) return false;
    if (!nearly(va.amountLyd, vb.amountLyd)) return false;
    if (!nearly(va.amountTnd, vb.amountTnd)) return false;
  }
  return true;
}

async function main() {
  loadEnv();
  const port = process.env.PORT || "4000";
  const base = `http://127.0.0.1:${port}`;
  const results: { name: string; pass: boolean; detail: string }[] = [];
  const check = (name: string, pass: boolean, detail: string) => {
    results.push({ name, pass, detail });
    console.log(`${pass ? "PASS" : "FAIL"}  ${name} — ${detail}`);
  };

  check(
    "env_ledger_mysql",
    (process.env.LEDGER_DATABASE || "").toLowerCase() === "mysql",
    `LEDGER_DATABASE=${process.env.LEDGER_DATABASE}`,
  );

  const login = await fetch(`${base}/api/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email: "admin@safarlibya.com", password: "19992000" }),
  });
  const auth: any = await login.json().catch(() => null);
  check("login", !!auth?.accessToken, `status=${login.status}`);
  if (!auth?.accessToken) {
    console.log("\nSUMMARY ledger-read: FAIL");
    process.exit(1);
  }
  const headers = {
    Authorization: `Bearer ${auth.accessToken}`,
    "Content-Type": "application/json",
  };

  const pool = await mysql.createPool({
    host: process.env.MYSQL_HOST,
    port: Number(process.env.MYSQL_PORT),
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    database: process.env.MYSQL_DATABASE,
  });

  // --- VOID exclusion + Mongo↔MySQL posted-sum parity ---
  const [voidRows] = await pool.query(
    `SELECT id, status, amount_lyd FROM ledger_entries WHERE id = ?`,
    [VOID_ID],
  );
  const voidRow = (voidRows as any[])[0];
  check(
    "void_row_exists_mysql",
    !!voidRow && String(voidRow.status) === "VOID",
    `status=${voidRow?.status} amount=${voidRow?.amount_lyd}`,
  );

  const [canonRows] = await pool.query(
    `SELECT id, status FROM ledger_entries WHERE id = ?`,
    [CANONICAL_ID],
  );
  const canonRow = (canonRows as any[])[0];
  check(
    "canonical_posted_mysql",
    !!canonRow && String(canonRow.status) === "POSTED",
    `status=${canonRow?.status}`,
  );

  const [mysqlSumRows] = await pool.query(
    `SELECT type, COUNT(*) AS count,
            COALESCE(SUM(amount_lyd),0) AS amountLyd,
            COALESCE(SUM(amount_tnd),0) AS amountTnd
     FROM ledger_entries WHERE status = 'POSTED'
     GROUP BY type ORDER BY type`,
  );
  const mysqlSums = (mysqlSumRows as any[]).map((r) => ({
    type: String(r.type),
    count: Number(r.count),
    amountLyd: Number(r.amountLyd),
    amountTnd: Number(r.amountTnd),
  }));

  await mongoose.connect(process.env.MONGODB_URI as string);
  const mongoAgg = await mongoose.connection.db!
    .collection("ledgerentries")
    .aggregate([
      { $match: { status: "POSTED" } },
      {
        $group: {
          _id: "$type",
          count: { $sum: 1 },
          amountLyd: { $sum: "$amountLyd" },
          amountTnd: { $sum: "$amountTnd" },
        },
      },
      { $sort: { _id: 1 } },
    ])
    .toArray();
  const mongoSums = mongoAgg.map((r) => ({
    type: String(r._id),
    count: Number(r.count),
    amountLyd: Number(r.amountLyd),
    amountTnd: Number(r.amountTnd),
  }));

  const voidInMongoPosted = await mongoose.connection.db!
    .collection("ledgerentries")
    .findOne({ _id: VOID_ID as any, status: "POSTED" });
  check(
    "void_excluded_from_mongo_posted",
    !voidInMongoPosted,
    voidInMongoPosted ? "VOID still POSTED in Mongo" : "ok",
  );

  const [voidInMysqlPosted] = await pool.query(
    `SELECT COUNT(*) AS n FROM ledger_entries WHERE id = ? AND status = 'POSTED'`,
    [VOID_ID],
  );
  check(
    "void_excluded_from_mysql_posted",
    Number((voidInMysqlPosted as any[])[0].n) === 0,
    `n=${(voidInMysqlPosted as any[])[0].n}`,
  );

  check(
    "posted_sums_mongo_eq_mysql",
    mapsEqual(sumMap(mongoSums), sumMap(mysqlSums)),
    `mongoTypes=${mongoSums.length} mysqlTypes=${mysqlSums.length}`,
  );

  const summaryRes = await fetch(`${base}/api/admin/reconciliation/summary`, { headers });
  const summaryBody: any = await summaryRes.json().catch(() => null);
  check(
    "summary_reads_mysql",
    summaryRes.status === 200 && summaryBody?._db === "mysql",
    `status=${summaryRes.status} _db=${summaryBody?._db}`,
  );

  const apiSums = Array.isArray(summaryBody?.ledgerSumsByType)
    ? summaryBody.ledgerSumsByType
    : [];
  check(
    "api_sums_match_mysql_posted",
    mapsEqual(sumMap(apiSums), sumMap(mysqlSums)),
    `apiTypes=${apiSums.length} mysqlTypes=${mysqlSums.length}`,
  );

  const listRes = await fetch(`${base}/api/admin/reconciliation/ledger`, { headers });
  const listBody: any = await listRes.json().catch(() => null);
  const entries: any[] = Array.isArray(listBody?.entries) ? listBody.entries : [];
  check(
    "list_reads_mysql",
    listRes.status === 200 && listBody?._db === "mysql" && entries.length > 0,
    `status=${listRes.status} _db=${listBody?._db} count=${entries.length}`,
  );
  check(
    "list_excludes_void_id_or_marks_void",
    !entries.some((e) => String(e.id || e._id) === VOID_ID && e.status === "POSTED"),
    entries.some((e) => String(e.id || e._id) === VOID_ID)
      ? `listed as ${entries.find((e) => String(e.id || e._id) === VOID_ID)?.status}`
      : "not in first page (ok if older)",
  );

  // Get-by-id via MySQL adapter path: list filtered by type then find canonical
  const payList = await fetch(
    `${base}/api/admin/reconciliation/ledger?type=BOOKING_PAYMENT`,
    { headers },
  );
  const payBody: any = await payList.json().catch(() => null);
  const foundCanon = (payBody?.entries || []).find(
    (e: any) => String(e.id || e._id) === CANONICAL_ID,
  );
  check(
    "get_canonical_via_list",
    payBody?._db === "mysql" && !!foundCanon && foundCanon.status === "POSTED",
    `found=${!!foundCanon} status=${foundCanon?.status}`,
  );

  // Live booking cycle → ledger posting
  await fetch(`${base}/api/wallet/topups`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      amountLyd: 4000,
      bankName: "مصرف الجمهورية (Demo)",
      reference: `lr-${Date.now()}`,
      instantDemo: true,
    }),
  });

  const props = await fetch(`${base}/api/properties?take=10`);
  const propsBody: any = await props.json();
  const property = propsBody?.properties?.[0];
  check("property", !!property?.id, `id=${property?.id}`);

  const checkIn = new Date();
  checkIn.setUTCDate(checkIn.getUTCDate() + 95);
  const checkOut = new Date(checkIn);
  checkOut.setUTCDate(checkOut.getUTCDate() + 2);

  const quote = await fetch(`${base}/api/bookings/quote`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      propertyId: property.id,
      checkIn: checkIn.toISOString().slice(0, 10),
      checkOut: checkOut.toISOString().slice(0, 10),
      guests: 1,
    }),
  });
  const quoteBody: any = await quote.json().catch(() => null);
  const bookingId = quoteBody?.booking?.id;
  check("quote", quote.status === 201 && !!bookingId, `status=${quote.status}`);

  const pay = await fetch(`${base}/api/bookings/${bookingId}/pay`, {
    method: "POST",
    headers,
    body: JSON.stringify({ useWallet: true }),
  });
  const payRes: any = await pay.json().catch(() => null);
  check("pay", pay.status === 200, `status=${pay.status} booking=${payRes?.booking?.status}`);

  const [ledgerRows] = await pool.query(
    `SELECT id, type, status, amount_lyd, booking_id
     FROM ledger_entries
     WHERE booking_id = ? AND type = 'BOOKING_PAYMENT'
     ORDER BY created_at DESC LIMIT 5`,
    [bookingId],
  );
  const cycleLedger = (ledgerRows as any[])[0];
  check(
    "cycle_ledger_row_mysql",
    !!cycleLedger &&
      cycleLedger.status === "POSTED" &&
      cycleLedger.type === "BOOKING_PAYMENT",
    `id=${cycleLedger?.id} status=${cycleLedger?.status}`,
  );

  const listAfter = await fetch(
    `${base}/api/admin/reconciliation/ledger?type=BOOKING_PAYMENT`,
    { headers },
  );
  const listAfterBody: any = await listAfter.json().catch(() => null);
  const apiLedger = (listAfterBody?.entries || []).find(
    (e: any) => String(e.bookingId) === String(bookingId),
  );
  check(
    "api_ledger_matches_mysql_row",
    listAfterBody?._db === "mysql" &&
      !!apiLedger &&
      String(apiLedger.id || apiLedger._id) === String(cycleLedger?.id) &&
      nearly(Number(apiLedger.amountLyd), Number(cycleLedger?.amount_lyd)),
    `apiId=${apiLedger?.id || apiLedger?._id} mysqlId=${cycleLedger?.id} apiAmt=${apiLedger?.amountLyd} mysqlAmt=${cycleLedger?.amount_lyd}`,
  );

  // Cancel cleanup (best-effort)
  await fetch(`${base}/api/bookings/${bookingId}/cancel`, {
    method: "POST",
    headers,
  });

  await pool.end();
  await mongoose.disconnect();

  const passed = results.filter((r) => r.pass).length;
  console.log(`\nSUMMARY ledger-read: ${passed}/${results.length} pass`);
  process.exit(passed === results.length ? 0 : 1);
}

main().catch((e) => {
  console.error(e instanceof Error ? e.message : e);
  process.exit(1);
});
