#!/usr/bin/env node
const fs = require('fs');
const path = require('path');

const DEFAULT_KEY_PATH = path.join(process.env.HOME || '', '.openclaw/workspace/.clawdbot/credentials/google-maps/apikey.txt');
const FALLBACK_KEY_PATHS = [
  DEFAULT_KEY_PATH,
  path.join(process.env.HOME || '', '.openclaw/credentials/google-maps/apikey.txt'),
];

function parseArgs(argv) {
  const out = { _: [] };
  for (let i = 0; i < argv.length; i++) {
    const t = argv[i];
    if (t.startsWith('--')) {
      const k = t.slice(2);
      const n = argv[i + 1];
      if (!n || n.startsWith('--')) out[k] = true;
      else {
        out[k] = n;
        i++;
      }
    } else out._.push(t);
  }
  return out;
}

function usage() {
  console.log(`Google Maps Traffic CLI

Commands:
  eta --from "Origin" --to "Destination" [--departAt now|ISO]
  leave-by --from "Origin" --to "Destination" --arriveBy ISO

Optional flags:
  --keyPath <path>     API key file path (default: ${DEFAULT_KEY_PATH})
  --timeZone <IANA>    Display timezone (default: America/Chicago)
  --avoidTolls         Avoid toll roads when possible
  --avoidHighways      Avoid highways when possible
  --avoidFerries       Avoid ferries when possible

Notes:
  - Requires Google Maps APIs (Routes API + Geocoding API) enabled for the key.
`);
}

function must(opts, keys) {
  const miss = keys.filter((k) => !opts[k]);
  if (miss.length) throw new Error(`Missing: ${miss.map((k) => '--' + k).join(', ')}`);
}

function readApiKey(opts) {
  if (process.env.GOOGLE_MAPS_API_KEY) return process.env.GOOGLE_MAPS_API_KEY.trim();
  const candidates = opts.keyPath ? [opts.keyPath] : FALLBACK_KEY_PATHS;
  for (const p of candidates) {
    if (!fs.existsSync(p)) continue;
    const key = fs.readFileSync(p, 'utf8').trim();
    if (!key) throw new Error(`API key file is empty: ${p}`);
    return key;
  }
  throw new Error(`API key file not found. Checked: ${candidates.join(', ')}`);
}

async function geocode(address, key) {
  const u = new URL('https://maps.googleapis.com/maps/api/geocode/json');
  u.searchParams.set('address', address);
  u.searchParams.set('key', key);
  const r = await fetch(u);
  const j = await r.json();
  if (j.status !== 'OK' || !j.results?.length) {
    throw new Error(`Geocoding failed for "${address}": ${j.status}${j.error_message ? ` (${j.error_message})` : ''}`);
  }
  const loc = j.results[0].geometry.location;
  return { lat: loc.lat, lng: loc.lng, formatted: j.results[0].formatted_address };
}

function parseGoogleDuration(s) {
  // e.g. "1534s"
  const m = /^(-?\d+)s$/.exec(String(s || ''));
  if (!m) return null;
  return Number(m[1]);
}

function fmtMinutes(sec) {
  return `${Math.round(sec / 60)} min`;
}

function routeModifiersFromOpts(opts = {}) {
  const modifiers = {};
  if (opts.avoidTolls) modifiers.avoidTolls = true;
  if (opts.avoidHighways) modifiers.avoidHighways = true;
  if (opts.avoidFerries) modifiers.avoidFerries = true;
  return Object.keys(modifiers).length ? modifiers : undefined;
}

async function computeRoute({ from, to, departAt, key, modifiers }) {
  const [o, d] = await Promise.all([geocode(from, key), geocode(to, key)]);
  const body = {
    origin: { location: { latLng: { latitude: o.lat, longitude: o.lng } } },
    destination: { location: { latLng: { latitude: d.lat, longitude: d.lng } } },
    travelMode: 'DRIVE',
    routingPreference: 'TRAFFIC_AWARE',
    computeAlternativeRoutes: false,
    languageCode: 'en-US',
    units: 'IMPERIAL',
  };

  if (modifiers) body.routeModifiers = modifiers;

  if (departAt && departAt !== 'now') body.departureTime = new Date(departAt).toISOString();

  const res = await fetch('https://routes.googleapis.com/directions/v2:computeRoutes', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Goog-Api-Key': key,
      'X-Goog-FieldMask': 'routes.duration,routes.distanceMeters,routes.staticDuration',
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const txt = await res.text();
    throw new Error(`Routes API error ${res.status}: ${txt}`);
  }

  const data = await res.json();
  if (!data.routes?.length) throw new Error('No route found');

  const r = data.routes[0];
  const durationSec = parseGoogleDuration(r.duration);
  const staticSec = parseGoogleDuration(r.staticDuration);
  return {
    origin: o.formatted,
    destination: d.formatted,
    durationSec,
    staticSec,
    distanceMiles: r.distanceMeters ? r.distanceMeters / 1609.344 : null,
  };
}

function localIso(ts, tz = 'America/Chicago') {
  return new Date(ts).toLocaleString('en-US', { timeZone: tz, hour12: true });
}

async function cmdEta(opts) {
  must(opts, ['from', 'to']);
  const key = readApiKey(opts);
  const tz = opts.timeZone || 'America/Chicago';
  const departTs = opts.departAt && opts.departAt !== 'now' ? new Date(opts.departAt).getTime() : Date.now();

  const modifiers = routeModifiersFromOpts(opts);
  const route = await computeRoute({ from: opts.from, to: opts.to, departAt: opts.departAt || 'now', key, modifiers });
  const arriveTs = departTs + (route.durationSec || 0) * 1000;

  console.log(JSON.stringify({
    from: route.origin,
    to: route.destination,
    departureLocal: localIso(departTs, tz),
    arrivalLocal: localIso(arriveTs, tz),
    eta: fmtMinutes(route.durationSec),
    trafficDelay: route.staticSec && route.durationSec ? fmtMinutes(Math.max(0, route.durationSec - route.staticSec)) : null,
    distanceMiles: route.distanceMiles ? Number(route.distanceMiles.toFixed(1)) : null,
    avoidTolls: !!opts.avoidTolls,
    avoidHighways: !!opts.avoidHighways,
    avoidFerries: !!opts.avoidFerries,
    timeZone: tz,
  }, null, 2));
}

async function cmdLeaveBy(opts) {
  must(opts, ['from', 'to', 'arriveBy']);
  const key = readApiKey(opts);
  const tz = opts.timeZone || 'America/Chicago';
  const arriveTs = new Date(opts.arriveBy).getTime();
  if (!Number.isFinite(arriveTs)) throw new Error('Invalid --arriveBy ISO datetime');

  const modifiers = routeModifiersFromOpts(opts);

  // two-pass estimate
  let departGuess = arriveTs - 45 * 60 * 1000;
  for (let i = 0; i < 2; i++) {
    const route = await computeRoute({ from: opts.from, to: opts.to, departAt: new Date(departGuess).toISOString(), key, modifiers });
    departGuess = arriveTs - (route.durationSec || 0) * 1000;
  }

  const finalRoute = await computeRoute({ from: opts.from, to: opts.to, departAt: new Date(departGuess).toISOString(), key, modifiers });

  console.log(JSON.stringify({
    from: finalRoute.origin,
    to: finalRoute.destination,
    leaveByLocal: localIso(departGuess, tz),
    targetArrivalLocal: localIso(arriveTs, tz),
    eta: fmtMinutes(finalRoute.durationSec),
    trafficDelay: finalRoute.staticSec && finalRoute.durationSec ? fmtMinutes(Math.max(0, finalRoute.durationSec - finalRoute.staticSec)) : null,
    distanceMiles: finalRoute.distanceMiles ? Number(finalRoute.distanceMiles.toFixed(1)) : null,
    avoidTolls: !!opts.avoidTolls,
    avoidHighways: !!opts.avoidHighways,
    avoidFerries: !!opts.avoidFerries,
    timeZone: tz,
  }, null, 2));
}

(async function main() {
  try {
    const args = parseArgs(process.argv.slice(2));
    const cmd = args._[0];
    if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') return usage();

    if (cmd === 'eta') return await cmdEta(args);
    if (cmd === 'leave-by') return await cmdLeaveBy(args);
    throw new Error(`Unknown command: ${cmd}`);
  } catch (e) {
    console.error(`ERROR: ${e.message}`);
    process.exit(1);
  }
})();
