/**
 * Weather service (Session 15).
 *
 * Open-Meteo proxy. No API key required (the service is free for
 * non-commercial use and sportsbook analytics is firmly non-
 * commercial intelligence). 5s hard timeout, 1h Redis cache,
 * graceful degrade (returns null on any failure — never throws,
 * never blocks the grade).
 *
 * Outputs are normalized to the units North-American bettors think
 * in: temperature in Fahrenheit, wind in mph. Open-Meteo's defaults
 * are Celsius + km/h, so we request the imperial units directly via
 * query params.
 *
 * Cache key: `weather:{lat}:{lon}:{hour}` — keyed by the current
 * UTC hour so two requests within the same hour hit cache. Slightly
 * less precise than a sliding TTL but matches Open-Meteo's hourly
 * forecast cadence and keeps cache churn bounded.
 */

const axios = require('axios');
const { cacheGet, cacheSet } = require('../utils/redis');

const BASE_URL = 'https://api.open-meteo.com/v1/forecast';
const HTTP_TIMEOUT_MS = 5_000;
const CACHE_TTL_SEC = 3600; // 1h — Open-Meteo refreshes hourly

function currentHourBucket() {
  const d = new Date();
  return `${d.getUTCFullYear()}${String(d.getUTCMonth() + 1).padStart(2, '0')}${String(d.getUTCDate()).padStart(2, '0')}${String(d.getUTCHours()).padStart(2, '0')}`;
}

function buildKey(lat, lon) {
  // 2-decimal precision is enough for a city-scale lookup and
  // collapses neighboring venues onto the same cache key (no real-
  // world impact — they share the same weather).
  const latKey = Number(lat).toFixed(2);
  const lonKey = Number(lon).toFixed(2);
  return `weather:${latKey}:${lonKey}:${currentHourBucket()}`;
}

/**
 * getWeather — fetch current weather conditions for a lat/lon.
 *
 * @param {number} lat
 * @param {number} lon
 * @returns {Promise<{temp_f:number|null, wind_mph:number|null, wind_dir:number|null, precip_mm:number|null} | null>}
 *
 * Returns null on:
 *   - invalid coordinates
 *   - upstream timeout / 5xx
 *   - missing fields in the response
 *
 * The grading engine and reasoning builder both treat null as "no
 * signal" — features are simply omitted from the prop's overlay.
 */
async function getWeather(lat, lon) {
  if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;

  const cacheKey = buildKey(lat, lon);
  try {
    const cached = await cacheGet(cacheKey);
    if (cached !== null) return cached;
  } catch {
    // Redis hiccup — proceed to network.
  }

  try {
    const res = await axios.get(BASE_URL, {
      timeout: HTTP_TIMEOUT_MS,
      params: {
        latitude: lat,
        longitude: lon,
        current: 'temperature_2m,wind_speed_10m,wind_direction_10m,precipitation',
        temperature_unit: 'fahrenheit',
        wind_speed_unit: 'mph',
        precipitation_unit: 'mm', // mm is the universal precipitation unit
      },
    });
    const current = res.data?.current;
    if (!current) return null;
    const out = {
      temp_f: Number.isFinite(current.temperature_2m) ? current.temperature_2m : null,
      wind_mph: Number.isFinite(current.wind_speed_10m) ? current.wind_speed_10m : null,
      wind_dir: Number.isFinite(current.wind_direction_10m) ? current.wind_direction_10m : null,
      precip_mm: Number.isFinite(current.precipitation) ? current.precipitation : null,
      _fetched_at: new Date().toISOString(),
    };
    try { await cacheSet(cacheKey, out, CACHE_TTL_SEC); } catch { /* graceful */ }
    return out;
  } catch (err) {
    // Open-Meteo down, timeout, 5xx — silently degrade.
    if (err && err.code !== 'ECONNABORTED') {
      console.warn('[weatherService] fetch failed:', err.message);
    }
    return null;
  }
}

module.exports = {
  getWeather,
  __internals: { BASE_URL, HTTP_TIMEOUT_MS, CACHE_TTL_SEC, buildKey, currentHourBucket },
};
