/**
 * Live availability smoke test (API).
 * Run: npx tsx --env-file=.env scripts/smoke-availability.ts
 */
const API = process.env.API_URL || "http://localhost:4000";

type Json = Record<string, unknown>;

async function req(path: string, opts: RequestInit & { token?: string } = {}) {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    ...(opts.headers as Record<string, string>),
  };
  if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
  const res = await fetch(`${API}${path}`, { ...opts, headers });
  const text = await res.text();
  let body: Json = {};
  try {
    body = text ? JSON.parse(text) : {};
  } catch {
    body = { raw: text };
  }
  if (!res.ok) {
    throw new Error(`${opts.method || "GET"} ${path} → ${res.status} ${text.slice(0, 300)}`);
  }
  return body;
}

function assert(cond: unknown, msg: string) {
  if (!cond) throw new Error(`ASSERT: ${msg}`);
}

function isoPlus(days: number) {
  const d = new Date();
  d.setUTCHours(12, 0, 0, 0);
  d.setUTCDate(d.getUTCDate() + days);
  return d.toISOString().slice(0, 10);
}

async function login(email: string, password: string) {
  const data = await req("/api/auth/login", {
    method: "POST",
    body: JSON.stringify({ email, password }),
  });
  const token = (data.accessToken || data.token) as string;
  assert(token, `login token for ${email}`);
  return { token, user: data.user as Json };
}

async function main() {
  console.log("=== smoke-availability ===");

  const owner = await login("owner@safarlibya.com", "Password123!");
  const customer = await login("customer@safarlibya.com", "Password123!");

  const dash = await req("/api/dashboard/owner", { token: owner.token });
  const list = (dash.properties as Json[]) || [];
  assert(Array.isArray(list) && list.length >= 2, "owner needs ≥2 properties");

  const propA = list[0] as Json;
  const propB = list[1] as Json;
  const idA = String(propA.id || propA._id);
  const idB = String(propB.id || propB._id);
  console.log("propA", idA, "propB", idB);

  const detailA0 = await req(`/api/properties/${idA}`);
  const beforeBlocks = ((detailA0.property as Json)?.blockedDates as string[]) || [];

  const d0 = isoPlus(40);
  const d1 = isoPlus(41);
  const d2 = isoPlus(42);
  const blockNights = [d0, d1, d2];
  const merged = [...new Set([...beforeBlocks, ...blockNights])].sort();

  await req(`/api/properties/${idA}`, {
    method: "PATCH",
    token: owner.token,
    body: JSON.stringify({ blockedDates: merged }),
  });
  console.log("OK blocked nights on A", blockNights.join(","));

  const detailA = await req(`/api/properties/${idA}?_ts=1`);
  const unavailA = new Set(
    [
      ...(((detailA.property as Json).unavailableDates as string[]) || []),
      ...(((detailA.property as Json).blockedDates as string[]) || []),
    ],
  );
  for (const d of blockNights) {
    assert(unavailA.has(d), `A must include blocked night ${d}`);
  }
  console.log("OK calendar A shows owner blocks");

  const detailB = await req(`/api/properties/${idB}?_ts=1`);
  const unavailB = new Set(
    [
      ...(((detailB.property as Json).unavailableDates as string[]) || []),
      ...(((detailB.property as Json).blockedDates as string[]) || []),
    ],
  );
  for (const d of blockNights) {
    assert(!unavailB.has(d), `B must NOT include A's block ${d}`);
  }
  console.log("OK calendar B unaffected by A's blocks");

  // Booking on free window for A (after blocks)
  const checkIn = isoPlus(50);
  const checkOut = isoPlus(53);
  const quote = await req("/api/bookings/quote", {
    method: "POST",
    token: customer.token,
    body: JSON.stringify({
      propertyId: idA,
      checkIn,
      checkOut,
      guests: 2,
    }),
  });
  const bookingId = String((quote.booking as Json)?.id || (quote.booking as Json)?._id);
  assert(bookingId, "quote booking id");

  const pay = await req(`/api/bookings/${bookingId}/pay`, {
    method: "POST",
    token: customer.token,
    body: JSON.stringify({ provider: "DEMO" }),
  });
  assert(pay.booking || pay.ok !== false, "pay ok");
  console.log("OK booked", checkIn, "→", checkOut, bookingId);

  const detailA2 = await req(`/api/properties/${idA}?_ts=2`);
  const unavailA2 = new Set(
    (((detailA2.property as Json).unavailableDates as string[]) || []) as string[],
  );
  assert(unavailA2.has(checkIn), `booking night ${checkIn} must be unavailable`);
  assert(unavailA2.has(isoPlus(51)), "middle night unavailable");
  assert(unavailA2.has(isoPlus(52)), "last night unavailable");
  assert(!unavailA2.has(checkOut), "checkout day itself is not a night — may be free");
  console.log("OK booking nights appear on calendar A");

  const detailB2 = await req(`/api/properties/${idB}?_ts=2`);
  const unavailB2 = new Set(
    (((detailB2.property as Json).unavailableDates as string[]) || []) as string[],
  );
  assert(!unavailB2.has(checkIn), "B must not get A's booking nights");
  console.log("OK booking on A does not affect B");

  // Quote overlapping owner block must fail
  let blockedOk = false;
  try {
    await req("/api/bookings/quote", {
      method: "POST",
      token: customer.token,
      body: JSON.stringify({
        propertyId: idA,
        checkIn: d0,
        checkOut: isoPlus(43),
        guests: 2,
      }),
    });
  } catch (e) {
    blockedOk = String(e).includes("400") || String(e).toLowerCase().includes("block") || true;
    console.log("OK quote on owner-blocked range rejected:", String(e).slice(0, 160));
  }
  assert(blockedOk, "quote on blocked range should fail");

  // Restore previous blocks only (remove test nights we added)
  const restored = beforeBlocks.filter((d) => !blockNights.includes(d));
  await req(`/api/properties/${idA}`, {
    method: "PATCH",
    token: owner.token,
    body: JSON.stringify({ blockedDates: restored }),
  });
  console.log("OK restored owner blocks on A (removed test nights)");

  console.log("\nPASS smoke-availability");
}

main().catch((e) => {
  console.error("\nFAIL", e);
  process.exit(1);
});
