/**
 * Standalone verification of the Smartcar access-token date helpers.
 *
 * The original bug: `smartcarController.ts` used an `as` cast that lied about
 * the SDK's runtime shape. `access.expiresIn` was undefined, so
 * `new Date(Date.now() + undefined * 1000)` produced an Invalid Date and
 * Mongoose threw `Cast to date failed for value "Invalid Date" (type Date)
 * at path "tokenExpiresAt"`.
 *
 * These tests prove `resolveSmartcarAccessExpiration` and `toValidDate`:
 *   - Accept the v9+ SDK shape (`expiration: Date`)
 *   - Accept the legacy shape (`expiresIn: number` seconds-from-now)
 *   - Reject every shape that previously produced Invalid Date,
 *     returning `null` so the caller can short-circuit gracefully.
 *
 * Run: npx ts-node src/scripts/testSmartcarDate.ts
 */

import {
  toValidDate,
  resolveSmartcarAccessExpiration,
} from "../utils/smartcarDate";

let pass = 0;
let fail = 0;

function assert(condition: boolean, label: string, detail?: unknown): void {
  if (condition) {
    pass++;
    console.log(`  PASS  ${label}`);
  } else {
    fail++;
    console.log(`  FAIL  ${label}`);
    if (detail !== undefined) console.log(`        ${JSON.stringify(detail)}`);
  }
}

function isReasonablyClose(d: Date, expectedMs: number, toleranceMs = 1000): boolean {
  return Math.abs(d.getTime() - expectedMs) < toleranceMs;
}

console.log("\nScenario 1 — toValidDate accepts a real Date:");
{
  const d = new Date("2030-01-01T00:00:00Z");
  const out = toValidDate(d);
  assert(out instanceof Date && out.getTime() === d.getTime(), "real Date passes through");
}

console.log("\nScenario 2 — toValidDate rejects Invalid Date:");
{
  const out = toValidDate(new Date(NaN));
  assert(out === null, "Invalid Date → null (no NaN-backed Date returned)");
}

console.log("\nScenario 3 — toValidDate accepts a numeric `expiresIn` (seconds offset):");
{
  const out = toValidDate(7200);
  const expected = Date.now() + 7200 * 1000;
  assert(
    out instanceof Date && isReasonablyClose(out, expected),
    "7200 → ~now+2h Date",
    out?.toISOString()
  );
}

console.log("\nScenario 4 — toValidDate accepts a unix-ms timestamp:");
{
  const ms = Date.UTC(2030, 0, 1);
  const out = toValidDate(ms);
  assert(out instanceof Date && out.getTime() === ms, "unix-ms → matching Date");
}

console.log("\nScenario 5 — toValidDate rejects undefined/null/NaN/garbage:");
{
  assert(toValidDate(undefined) === null, "undefined → null");
  assert(toValidDate(null) === null, "null → null");
  assert(toValidDate(NaN) === null, "NaN → null");
  assert(toValidDate("not a date") === null, "garbage string → null");
  assert(toValidDate({}) === null, "empty object → null");
  assert(toValidDate("") === null, "empty string → null");
}

console.log("\nScenario 6 — toValidDate accepts an ISO string:");
{
  const out = toValidDate("2030-01-01T00:00:00Z");
  assert(
    out instanceof Date && out.getTime() === Date.UTC(2030, 0, 1),
    "ISO → matching Date"
  );
}

console.log("\nScenario 7 — resolveSmartcarAccessExpiration with v9+ SDK shape:");
{
  const expected = new Date(Date.now() + 7200 * 1000);
  const sdkV9Response = {
    accessToken: "at_123",
    refreshToken: "rt_456",
    expiration: expected,
    refreshExpiration: new Date(Date.now() + 60 * 86400 * 1000),
  };
  const out = resolveSmartcarAccessExpiration(sdkV9Response);
  assert(
    out instanceof Date && out.getTime() === expected.getTime(),
    "v9+ shape → expiration Date used directly"
  );
}

console.log("\nScenario 8 — resolveSmartcarAccessExpiration with legacy shape:");
{
  const legacyResponse = {
    accessToken: "at_123",
    refreshToken: "rt_456",
    expiresIn: 7200,
    refreshExpiresIn: 5184000,
  };
  const expected = Date.now() + 7200 * 1000;
  const out = resolveSmartcarAccessExpiration(legacyResponse);
  assert(
    out instanceof Date && isReasonablyClose(out, expected),
    "legacy shape → ~now+2h Date"
  );
}

console.log("\nScenario 9 — THE ORIGINAL BUG: cast lies about runtime shape:");
{
  // Exactly the response the SDK actually returned, but treated like the
  // controller used to treat it (cast to a shape with `expiresIn: number`).
  const sdkResponse = {
    accessToken: "at_123",
    refreshToken: "rt_456",
    expiration: new Date(Date.now() + 7200 * 1000),
    refreshExpiration: new Date(Date.now() + 60 * 86400 * 1000),
  } as unknown as { accessToken: string; refreshToken: string; expiresIn: number };

  // Old (broken) computation:
  const oldDate = new Date(Date.now() + sdkResponse.expiresIn * 1000);
  assert(
    Number.isNaN(oldDate.getTime()),
    "Old `Date.now() + expiresIn * 1000` produces Invalid Date — this is the bug"
  );

  // New computation via the helper:
  const newDate = resolveSmartcarAccessExpiration(sdkResponse);
  assert(
    newDate instanceof Date && Number.isFinite(newDate.getTime()),
    "Helper returns a valid Date by reading `expiration` instead"
  );
}

console.log("\nScenario 10 — resolveSmartcarAccessExpiration with a totally broken response:");
{
  assert(
    resolveSmartcarAccessExpiration({}) === null,
    "no expiration / no expiresIn → null (caller short-circuits)"
  );
  assert(resolveSmartcarAccessExpiration(null) === null, "null → null");
  assert(resolveSmartcarAccessExpiration(undefined) === null, "undefined → null");
  assert(
    resolveSmartcarAccessExpiration({ expiration: "Invalid Date" }) === null,
    "garbage `expiration` string → null"
  );
  assert(
    resolveSmartcarAccessExpiration({ expiration: new Date(NaN) }) === null,
    "Invalid Date object → null"
  );
}

console.log("\n────────────────────────────────────");
console.log(`Results: ${pass} passed, ${fail} failed`);
console.log("────────────────────────────────────");
process.exit(fail > 0 ? 1 : 0);
