/**
 * Full booking cycle dual-write parity test (quote → pay → cancel).
 * Dev only. Uses DEMO pay path.
 */
import fs from "fs";
import path from "path";
import mongoose from "mongoose";
import mysql from "mysql2/promise";

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;
}

async function main() {
  loadEnv();
  delete process.env.DUAL_WRITE_FINANCIAL_FAIL;
  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}`);
  };

  // Register fresh customer
  const email = `dualwrite.${Date.now()}@example.com`;
  const password = "Test1234!";
  const reg = await fetch(`${base}/api/auth/register`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      email,
      password,
      fullName: "Dual Write Tester",
      phone: "0910000000",
      role: "CUSTOMER",
    }),
  });
  const regBody: any = await reg.json().catch(() => null);
  check("register", reg.status === 201 || reg.status === 200, `status=${reg.status}`);

  // Verify via OTP if needed — login may fail until verified. Use admin to verify or login path.
  let token = regBody?.accessToken as string | undefined;
  if (!token) {
    // Try login; if unverified, mark and use admin to set emailVerified via mysql/mongo quickly
    await mongoose.connect(process.env.MONGODB_URI!);
    await mongoose.connection.db!.collection("users").updateOne(
      { email },
      { $set: { emailVerifiedAt: new Date(), status: "ACTIVE" } },
    );
    // also mysql
    const pool0 = 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,
    });
    await pool0.execute(
      `UPDATE users SET email_verified_at = ?, status = 'ACTIVE' WHERE email = ?`,
      [new Date(), email],
    );
    await pool0.end();
    await mongoose.disconnect();

    const login = await fetch(`${base}/api/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });
    const lb: any = await login.json().catch(() => null);
    token = lb?.accessToken;
    check("login", !!token, `status=${login.status}`);
  } else {
    check("login", true, "token from register");
  }
  if (!token) {
    console.log("\nSUMMARY dualwrite-cycle: FAIL (no token)");
    process.exit(1);
  }
  const headers = {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  };

  // Fund wallet via instant demo topup so pay can use wallet or demo
  const top = await fetch(`${base}/api/wallet/topups`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      amountLyd: 5000,
      bankName: "مصرف الجمهورية (Demo)",
      reference: `dw-${Date.now()}`,
      instantDemo: true,
    }),
  });
  const topBody: any = await top.json().catch(() => null);
  check("topup", top.status === 201, `status=${top.status}`);

  const props = await fetch(`${base}/api/properties?take=20`);
  const propsBody: any = await props.json().catch(() => null);
  const property = (propsBody?.properties || []).find(
    (p: any) => p.status === "PUBLISHED" || !p.status,
  ) || propsBody?.properties?.[0];
  check("property", !!property?.id, `id=${property?.id}`);

  const checkIn = new Date();
  checkIn.setUTCDate(checkIn.getUTCDate() + 40);
  const checkOut = new Date(checkIn);
  checkOut.setUTCDate(checkOut.getUTCDate() + 2);
  const ci = checkIn.toISOString().slice(0, 10);
  const co = checkOut.toISOString().slice(0, 10);

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

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

  // Optional accept if WAITING_OWNER — use admin
  if (payBody?.booking?.status === "WAITING_OWNER") {
    const adminLogin = await fetch(`${base}/api/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "admin@safarlibya.com", password: "19992000" }),
    });
    const ab: any = await adminLogin.json();
    const accept = await fetch(`${base}/api/bookings/${bookingId}/accept`, {
      method: "POST",
      headers: { Authorization: `Bearer ${ab.accessToken}` },
    });
    check("accept", accept.status === 200 || accept.status === 409, `status=${accept.status}`);
  } else {
    check("accept", true, `skipped status=${payBody?.booking?.status}`);
  }

  const cancel = await fetch(`${base}/api/bookings/${bookingId}/cancel`, {
    method: "POST",
    headers,
  });
  const cancelBody: any = await cancel.json().catch(() => null);
  check(
    "cancel",
    cancel.status === 200 || cancel.status === 409,
    `status=${cancel.status}`,
  );

  await mongoose.connect(process.env.MONGODB_URI!);
  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,
  });

  const mongoBooking = await mongoose.connection.db!.collection("bookings").findOne({
    _id: bookingId as any,
  });
  const [mysqlBookings] = await pool.query(`SELECT * FROM bookings WHERE id = ?`, [bookingId]);
  const mysqlBooking = (mysqlBookings as any[])[0];

  check(
    "booking_parity",
    !!mongoBooking &&
      !!mysqlBooking &&
      String(mongoBooking.status) === String(mysqlBooking.status) &&
      nearly(Number(mongoBooking.totalLyd), Number(mysqlBooking.total_lyd)),
    `mongo=${mongoBooking?.status} mysql=${mysqlBooking?.status}`,
  );

  const mongoLedger = await mongoose.connection
    .db!.collection("ledgerentries")
    .find({ bookingId, status: { $ne: "VOID" } })
    .toArray();
  const [mysqlLedger] = await pool.query(
    `SELECT * FROM ledger_entries WHERE booking_id = ? AND status = 'POSTED'`,
    [bookingId],
  );
  check(
    "ledger_parity_count",
    mongoLedger.length === (mysqlLedger as any[]).length && mongoLedger.length >= 1,
    `mongo=${mongoLedger.length} mysql=${(mysqlLedger as any[]).length} types=${mongoLedger.map((e) => e.type).join(",")}`,
  );

  for (const e of mongoLedger) {
    const m = (mysqlLedger as any[]).find((r) => r.id === String(e._id));
    check(
      `ledger_row_${e.type}_${String(e._id).slice(-6)}`,
      !!m && nearly(Number(e.amountLyd), Number(m.amount_lyd)),
      m ? `ok` : "missing mysql",
    );
  }

  const userId = String(mongoBooking?.customerId || "");
  const mongoWallet = await mongoose.connection.db!.collection("wallets").findOne({
    userId: userId as any,
  });
  const [mysqlWallets] = await pool.query(`SELECT * FROM wallets WHERE user_id = ?`, [userId]);
  const mysqlWallet = (mysqlWallets as any[])[0];
  check(
    "wallet_parity",
    !!mongoWallet &&
      !!mysqlWallet &&
      nearly(Number(mongoWallet.balanceLyd), Number(mysqlWallet.balance_lyd)),
    `mongo=${mongoWallet?.balanceLyd} mysql=${mysqlWallet?.balance_lyd}`,
  );

  const mongoTxns = await mongoose.connection
    .db!.collection("wallettxns")
    .find({ userId })
    .toArray();
  const [mysqlTxns] = await pool.query(`SELECT id FROM wallet_txns WHERE user_id = ?`, [userId]);
  check(
    "wallet_txn_parity",
    mongoTxns.length === (mysqlTxns as any[]).length,
    `mongo=${mongoTxns.length} mysql=${(mysqlTxns as any[]).length}`,
  );

  const passed = results.filter((r) => r.pass).length;
  console.log(`\nSUMMARY dualwrite-cycle: ${passed}/${results.length} pass`);
  await pool.end();
  await mongoose.disconnect();
  process.exit(passed === results.length ? 0 : 1);
}

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