/**
 * sweep.mjs — confirm real API response shapes WITHOUT running the app and
 * WITHOUT leaking your personal data.
 *
 * It logs into the ParkSmarter API with the library, calls the safe READ-ONLY
 * endpoints, and writes a "schema skeleton" (field names + value *types* only,
 * never the values) to ./capture/. Share ./capture/ freely — it contains no PII.
 *
 * It NEVER calls anything that spends money or mutates your account
 * (no start-session, add/update/delete card or vehicle, profile edits, etc.).
 *
 * Setup:
 *   1) npm run build           (already done if dist/ exists)
 *   2) Create ./.creds.json (gitignored):
 *        {
 *          "phoneNumber": "5551234567",   // digits only, no country code
 *          "password": "…",
 *          "environment": "prodv2",        // optional
 *          "lat": 40.4406,                  // optional: a coordinate near real meters
 *          "lng": -79.9959                  // optional
 *        }
 *   3) node sweep.mjs
 */
import { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
import { ParkSmarterClient } from './dist/index.js';

/* ---- load creds (file or env) ---- */
let creds = {};
if (existsSync('./.creds.json')) {
  creds = JSON.parse(readFileSync('./.creds.json', 'utf8'));
}
const phoneNumber = creds.phoneNumber ?? process.env.PS_PHONE;
const password = creds.password ?? process.env.PS_PASS;
const environment = creds.environment ?? process.env.PS_ENV ?? 'prodv2';
const lat = creds.lat ?? (process.env.PS_LAT ? Number(process.env.PS_LAT) : undefined);
const lng = creds.lng ?? (process.env.PS_LNG ? Number(process.env.PS_LNG) : undefined);

if (!phoneNumber || !password) {
  console.error(
    'Missing credentials. Create ./.creds.json {phoneNumber, password} or set PS_PHONE/PS_PASS.',
  );
  process.exit(1);
}

/* ---- schema skeleton: keep KEYS and TYPES, drop VALUES (no PII) ---- */
function skeleton(v, depth = 0) {
  if (v === null) return 'null';
  if (Array.isArray(v)) {
    if (v.length === 0) return ['<empty>'];
    // merge keys across up to 5 elements so we don't miss sparse fields
    const sample = v.slice(0, 5).map((e) => skeleton(e, depth + 1));
    if (typeof sample[0] === 'object' && sample[0] !== null) {
      const merged = {};
      for (const s of sample) Object.assign(merged, s);
      return [merged, `<len:${v.length}>`];
    }
    return [sample[0], `<len:${v.length}>`];
  }
  if (typeof v === 'object') {
    const out = {};
    for (const k of Object.keys(v).sort()) out[k] = skeleton(v[k], depth + 1);
    return out;
  }
  // primitives: report type only, plus a coarse hint for strings
  if (typeof v === 'string') {
    if (/^\d{4}-\d{2}-\d{2}/.test(v)) return 'string<date>';
    if (/^-?\d+(\.\d+)?$/.test(v)) return 'string<numeric>';
    return 'string';
  }
  return typeof v; // number | boolean
}

const capDir = './capture';
mkdirSync(capDir, { recursive: true });

const ps = new ParkSmarterClient({ environment, timeoutMs: 20000 });
const results = {};

async function grab(name, fn) {
  try {
    const data = await fn();
    const skel = skeleton(data);
    results[name] = { ok: true, schema: skel };
    writeFileSync(`${capDir}/${name}.json`, JSON.stringify(skel, null, 2));
    const top = skel && typeof skel === 'object' ? Object.keys(skel) : skel;
    console.log(`✓ ${name}:`, JSON.stringify(top));
  } catch (e) {
    results[name] = { ok: false, error: `${e.name} ${e.status ?? ''} ${e.message}` };
    console.log(`✗ ${name}: ${e.name} ${e.status ?? ''} ${e.message}`);
  }
}

/* ---- run ---- */
console.log(`\nEnvironment: ${environment}`);
await grab('applicationValidity', () => ps.getApplicationValidity());

console.log('\nLogging in…');
try {
  const auth = await ps.loginWithPhone({ phoneNumber, password });
  results.__login = { ok: true, schema: skeleton(auth) };
  writeFileSync(`${capDir}/_authResponse.json`, JSON.stringify(skeleton(auth), null, 2));
  console.log('✓ login: keys =', JSON.stringify(Object.keys(auth)));
} catch (e) {
  console.log(`✗ login FAILED: ${e.name} ${e.status ?? ''} ${e.message}`);
  console.log('  (a 201 here means the account needs verification — see README note.)');
  process.exit(1);
}

/* authenticated READ-ONLY sweep */
await grab('userDetail', () => ps.getUserDetail());
await grab('notificationSettings', () => ps.getNotificationSettings());
await grab('activeSessions', () => ps.getActiveParkingSessions());
await grab('pastSessions', () => ps.getPastParkingSessions({ currentPage: 1, pageSize: 10 }));
await grab('parkingLots', () => ps.getParkingLots());
await grab('states', () => ps.getStates());
await grab('states_withId', () => ps.getStates(1));

let firstZone = null;
if (lat != null && lng != null) {
  const meters = await (async () => {
    try {
      return await ps.getMetersByLocation({ latitude: lat, longitude: lng });
    } catch (e) {
      results.metersByLocation = { ok: false, error: `${e.name} ${e.status ?? ''} ${e.message}` };
      console.log(`✗ metersByLocation: ${e.name} ${e.status ?? ''} ${e.message}`);
      return null;
    }
  })();
  if (meters) {
    writeFileSync(`${capDir}/metersByLocation.json`, JSON.stringify(skeleton(meters), null, 2));
    results.metersByLocation = { ok: true, schema: skeleton(meters) };
    console.log('✓ metersByLocation:', JSON.stringify(Object.keys(meters)));
    firstZone = meters?.Zones?.[0] ?? null;
  }
  await grab('limitedMetersByLocation', () =>
    ps.getLimitedMetersByLocation({ latitude: lat, longitude: lng }),
  );
} else {
  console.log('\n(skip meters-by-location: add "lat"/"lng" to .creds.json to include them)');
}

/* estimates are read-only price quotes — try them from a real zone + the user's vehicle */
try {
  const me = await ps.getUserDetail();
  const vehicleId = me?.VehicleDetails?.[0]?.VehicleID;
  const zone = firstZone;
  const space = zone?.Spaces?.[0];
  const zoneId = zone?.ZoneId ?? zone?.ZoneID;
  const spaceId = space?.SpaceID ?? space?.SpaceId;
  const customerId = zone?.CustomerID ?? zone?.CustomerId;
  if (zoneId != null && spaceId != null && customerId != null && vehicleId != null) {
    const common = { zoneId, spaceId, customerId, vehicleId };
    await grab('estimateItems', () => ps.getParkingEstimateItems(common));
    await grab('estimateMulti', () => ps.getParkingEstimateMulti(common));
    await grab('estimateSingle', () =>
      ps.getParkingEstimateSingle({ ...common, durationInMinutes: 60, creditCardId: 0 }),
    );
  } else {
    console.log('(skip estimates: no zone/space/vehicle available to quote against)');
  }
} catch (e) {
  console.log(`(skip estimates: ${e.message})`);
}

/* try a receipt shape from a past session id, if any (read-only) */
try {
  const past = await ps.getPastParkingSessions({ currentPage: 1, pageSize: 1 });
  const tid = past?.Session?.[0]?.TransactionID;
  if (tid != null) await grab('parkingReceipt', () => ps.getParkingReceipt(tid));
} catch {}

/* public content shapes */
await grab('states_public', () => ps.getStates());

writeFileSync(`${capDir}/_summary.json`, JSON.stringify(results, null, 2));
console.log('\nDone. Schema skeletons (no PII) written to ./capture/. Share that folder with me.');
