#!/usr/bin/env node
/**
 * diag-water-pin.mjs — diagnostic script for the "PIN only works
 * once" bug in the water log module.
 *
 * What it does:
 *   1. Snapshots every row in `water_irrigators` and `water_sessions`.
 *   2. For each irrigator, verifies the stored `pin_hash` format is
 *      a parseable `scrypt$N$r$p$salt$hash` string.
 *   3. Cross-checks: any session whose `expires_at < now()` would
 *      fail `requireFieldSession` but should NOT affect `verifyWaterPin`
 *      (verifyWaterPin re-issues a session on success).
 *   4. Optionally tests a provided PIN against a provided pin_hash to
 *      see if the cryptographic verify matches.
 *
 * Usage:
 *   DATABASE_URL="postgresql://..." node scripts/diag-water-pin.mjs
 *   DATABASE_URL="..." \
 *     node scripts/diag-water-pin.mjs --pin=4739 --id=<irrigator-uuid>
 *
 * Run this BEFORE and AFTER a failing second-login attempt to confirm
 * whether `pin_hash` is being modified mid-session.
 */
import "dotenv/config";
import { Pool } from "pg";
import { scryptSync, timingSafeEqual } from "node:crypto";

const args = Object.fromEntries(
  process.argv.slice(2).map((a) => {
    const m = a.match(/^--([^=]+)(?:=(.*))?$/);
    return m ? [m[1], m[2] ?? true] : [a, true];
  }),
);

const pin = typeof args.pin === "string" ? args.pin : null;
const targetId = typeof args.id === "string" ? args.id : null;

if (!process.env.DATABASE_URL) {
  console.error("DATABASE_URL is not set");
  process.exit(1);
}

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.DATABASE_URL.includes("sslmode")
    ? { rejectUnauthorized: false }
    : undefined,
});

function verifyPin(rawPin, stored) {
  if (!stored || !stored.startsWith("scrypt$")) return false;
  const parts = stored.split("$");
  if (parts.length !== 6) return false;
  const [, nStr, rStr, pStr, saltB64, hashB64] = parts;
  const n = parseInt(nStr, 10),
    r = parseInt(rStr, 10),
    p = parseInt(pStr, 10);
  if (![n, r, p].every(Number.isFinite)) return false;
  let salt, expected;
  try {
    salt = Buffer.from(saltB64, "base64");
    expected = Buffer.from(hashB64, "base64");
  } catch {
    return false;
  }
  if (expected.length !== 32) return false;
  let candidate;
  try {
    candidate = scryptSync(rawPin.normalize("NFKC"), salt, 32, {
      N: n,
      r,
      p,
    });
  } catch {
    return false;
  }
  return (
    candidate.length === expected.length &&
    timingSafeEqual(candidate, expected)
  );
}

function truncate(s, n = 60) {
  return s && s.length > n ? s.slice(0, n) + "..." : s;
}

async function main() {
  const client = await pool.connect();
  try {
    console.log("=== SNAPSHOT @ " + new Date().toISOString() + " ===\n");

    // Bypass RLS via withPlatformAdmin equivalent (raw app vars).
    await client.query("BEGIN");
    await client.query(
      "SELECT set_config('app.current_brand_id', '', true)",
    );
    await client.query(
      "SELECT set_config('app.platform_admin', 'true', true)",
    );

    const irrigators = await client.query(
      "SELECT id, brand_id, name, role, active, " +
        "substr(pin_hash, 1, 40) || '...' AS pin_hash_preview, " +
        "length(pin_hash) AS pin_hash_len, last_used_at, created_at " +
        "FROM water_irrigators ORDER BY name",
    );

    console.log("water_irrigators (" + irrigators.rowCount + " rows):");
    for (const r of irrigators.rows) {
      console.log(`  • [${r.id}] ${r.name}`);
      console.log(`      brand=${r.brand_id}`);
      console.log(`      role=${r.role}  active=${r.active}`);
      console.log(`      pin_hash_preview="${r.pin_hash_preview}" (len=${r.pin_hash_len})`);
      console.log(`      last_used_at=${r.last_used_at}`);

      // Format check
      const fullRow = await client.query(
        "SELECT pin_hash FROM water_irrigators WHERE id = $1",
        [r.id],
      );
      const ph = fullRow.rows[0]?.pin_hash;
      if (!ph || !ph.startsWith("scrypt$") || ph.split("$").length !== 6) {
        console.log(`      ⚠️  pin_hash has UNEXPECTED format`);
      } else {
        console.log(`      ✓ pin_hash is well-formed scrypt string`);
      }

      // Targeted PIN verify
      if (pin && targetId === r.id) {
        const ok = verifyPin(pin, ph);
        console.log(`      ${ok ? "✓" : "✗"} pin '${pin}' matches pin_hash for ${r.name} → ${ok}`);
      }
      console.log();
    }

    const sessions = await client.query(
      "SELECT id, irrigator_id, expires_at, created_at FROM water_sessions ORDER BY created_at DESC LIMIT 50",
    );
    console.log("\nwater_sessions (" + sessions.rowCount + " recent rows):");
    for (const s of sessions.rows) {
      const expired = new Date(s.expires_at).getTime() < Date.now();
      const irrigator = irrigators.rows.find((i) => i.id === s.irrigator_id);
      console.log(
        `  • [${s.id}]  irrigator=${s.irrigator_id} (${irrigator?.name ?? "?"})  expires_at=${s.expires_at.toISOString()} ${expired ? "(EXPIRED)" : "(active)"}`,
      );
    }

    await client.query("COMMIT");
  } finally {
    client.release();
    await pool.end();
  }
}

main().catch((err) => {
  console.error("error:", err.message);
  process.exit(1);
});
