const config = require("../config");

/**
 * Lee del plugin de telemetria estandar de la comunidad ETS2
 * (Funbit/ets2-telemetry-server, basado en el SCS Telemetry SDK).
 * Ese plugin corre dentro/junto al juego y expone el estado del camion
 * como JSON en una URL local. Aqui solo lo normalizamos a lo que
 * espera la API de EuroTrans.
 *
 * Referencia de campos: https://github.com/Funbit/ets2-telemetry-server
 */

let totalRouteKm = null;
let lastDestination = null;
let _nearDestinationAt = null;  // timestamp de la última vez que remaining <= 500m con destino activo
let _nearDestinationCity = null; // ciudad destino cuando estábamos cerca

// Registro de la transicion de estado de conexion con el plugin Funbit.
// Antes esto era totalmente silencioso: si raw.game.connected nunca llegaba
// a true, nada en el log lo indicaba y "no funciona la telemetria" no se
// podia distinguir de "el .exe ni siquiera responde" (ver checkTelemetryServerHealth
// en index.js) ni de "conectado pero sin partida activa".
let _lastConnected = null;
let _firstConnectAttemptAt = null;
let _lastWarnedAtMin = 0;

function average(values) {
  const valid = values.filter((v) => typeof v === "number" && !Number.isNaN(v));
  if (!valid.length) return 0;
  return valid.reduce((a, b) => a + b, 0) / valid.length;
}

async function read() {
  if (_firstConnectAttemptAt === null) _firstConnectAttemptAt = Date.now();

  const res = await fetch(config.telemetryUrl);
  if (!res.ok) {
    throw new Error(`telemetry source respondio ${res.status}`);
  }
  const raw = await res.json();

  if (!raw.game || !raw.game.connected) {
    if (_lastConnected !== false) {
      console.log('[funbit] game.connected=false — el servidor de telemetria responde pero ETS2 no esta en partida, o el plugin scs-telemetry.dll no se cargo (¿reiniciaste ETS2 despues de instalar el plugin la primera vez?)');
      _lastConnected = false;
    } else {
      const minsSinceFirst = Math.floor((Date.now() - _firstConnectAttemptAt) / 60000);
      if (minsSinceFirst > 0 && minsSinceFirst % 5 === 0 && minsSinceFirst !== _lastWarnedAtMin) {
        _lastWarnedAtMin = minsSinceFirst;
        console.warn(`[funbit] siguen sin llegar datos de partida tras ${minsSinceFirst} min — revisa que ETS2 este abierto, con un camion cargado, y que se haya reiniciado una vez tras instalar el plugin`);
      }
    }
    return null;
  }
  if (_lastConnected !== true) {
    console.log('[funbit] game.connected=true — telemetria activa, recibiendo datos de ETS2');
    _lastConnected = true;
  }

  // Log diagnóstico: mostrar todas las claves de nivel raíz y cualquier campo
  // relacionado con multas para identificar el formato exacto de Funbit
  const topKeys = Object.keys(raw);
  const finesRelated = {};
  for (const k of topKeys) {
    const v = raw[k];
    if (k === 'game' || k === 'truck' || k === 'trailer' || k === 'job' || k === 'navigation') continue;
    finesRelated[k] = typeof v === 'object' ? '(object)' : v;
  }
  // Comprobar si hay campos de multa en las secciones conocidas
  const gameKeys   = Object.keys(raw.game  || {});
  const truckKeys  = Object.keys(raw.truck || {}).filter(k => /fine|penalty|offence|offense|sanc/i.test(k));
  if (truckKeys.length > 0 || Object.keys(finesRelated).length > 0) {
    console.log('[funbit-raw] claves extra:', JSON.stringify(finesRelated), '| truck fines-keys:', truckKeys);
  }
  if (gameKeys.some(k => /fine|penalty|event/i.test(k))) {
    console.log('[funbit-game-keys]', gameKeys.filter(k => /fine|penalty|event/i.test(k)));
  }

  const truck = raw.truck || {};
  const job = raw.job || {};
  const trailer = raw.trailer || {};
  const navigation = raw.navigation || {};

  const fuelPercent = truck.fuelCapacity
    ? Math.max(0, Math.min(100, (truck.fuel / truck.fuelCapacity) * 100))
    : 0;

  // Desglose de desgaste del camión (0-1 → %)
  const wearEngine       = Number(truck.wearEngine       || 0) * 100;
  const wearTransmission = Number(truck.wearTransmission || 0) * 100;
  const wearCabin        = Number(truck.wearCabin        || 0) * 100;
  const wearChassis      = Number(truck.wearChassis      || 0) * 100;
  const wearWheels       = Number(truck.wearWheels       || 0) * 100;
  // Daño del camión = peor componente (igual que el indicador HUD de ETS2)
  const truckDamagePercent = Math.max(wearEngine, wearTransmission, wearCabin, wearChassis, wearWheels);

  // Desgaste del remolque — el campo varía según versión del plugin Funbit
  const trailerWearRaw = trailer.wear ?? trailer.wearChassiss ?? trailer.wearBody ?? 0;
  const trailerDamagePercent = Number(trailerWearRaw || 0) * 100;

  // Valor global de daño para cálculo de costes: peor entre camión y remolque
  const damagePercent = Math.max(truckDamagePercent, trailerDamagePercent);

  const remainingMeters = Number(navigation.estimatedDistance || navigation.estimatedDistanceKm && navigation.estimatedDistanceKm * 1000 || 0);
  // El campo de ciudad origen varía según versión del plugin Funbit
  const origin      = job.sourceCity || job.citySource || job.citySrc || null;
  const destination = job.destinationCity || job.cityDst || null;
  // plannedDistanceKm: distancia total del trabajo según ETS2.
  // El campo varía según versión del plugin Funbit; puede venir en km o en metros.
  const plannedKmRaw = job.plannedDistanceKm || job.plannedDistance || 0;
  const plannedKm    = Number(plannedKmRaw) > 5000
    ? Number(plannedKmRaw) / 1000   // vino en metros → convertir a km
    : Number(plannedKmRaw);
  // income: ingreso planificado en la moneda interna del juego (EUR en ETS2).
  // Usado para calcular la recompensa VTLGST al precio de mercado actual.
  const jobIncome   = Number(job.income || job.earnedXp && 0 || 0);

  // Plazo de entrega: el SDK de SCS (via Funbit) expone job.deadlineTime y
  // game.time como fechas "sinteticas" — no son un calendario real, son una
  // duracion de minutos codificada como DateTime desde 0001-01-01T00:00:00Z
  // (ver Ets2TelemetryData.cs / MinutesToDate del proyecto Funbit). Restando
  // ese origen a ambas se obtienen minutos comparables entre si. Se calcula
  // aqui la resta en vez de usar job.remainingTime porque ese campo lo
  // computa el propio plugin como (deadline - actual) y construye un DateTime
  // con ese resultado — si ya vas tarde el resultado es negativo, y .NET no
  // permite construir un DateTime con ticks negativos, así que remainingTime
  // puede venir vacío o dar error justo en el caso que más nos importa
  // detectar (retraso). Haciendo la resta nosotros mismos evitamos eso.
  const JOB_EPOCH_MS = Date.parse("0001-01-01T00:00:00.000Z");
  function minutesFromGameDate(iso) {
    if (!iso) return null;
    const ms = Date.parse(iso);
    if (Number.isNaN(ms)) return null;
    return Math.round((ms - JOB_EPOCH_MS) / 60000);
  }
  const gameTimeMinutes    = minutesFromGameDate(raw.game && raw.game.time);
  const deadlineMinutesAbs = minutesFromGameDate(job.deadlineTime);
  // deadlineTime en 0 (o ausente) significa que este trabajo no tiene plazo.
  const jobDeadlineMinutesLeft =
    (deadlineMinutesAbs && deadlineMinutesAbs > 0 && gameTimeMinutes !== null)
      ? deadlineMinutesAbs - gameTimeMinutes
      : null;

  if (destination !== lastDestination) {
    lastDestination = destination;
    if (plannedKm > 0) {
      // Fuente más fiable: distancia planificada total del trabajo
      totalRouteKm = plannedKm;
    } else if (remainingMeters > 0) {
      // Fallback: distancia restante en el momento de arrancar (pierde km previos)
      totalRouteKm = remainingMeters / 1000;
    }
  }

  const remainingKm = remainingMeters / 1000;
  const distanceDrivenKm =
    totalRouteKm !== null ? Math.max(0, totalRouteKm - remainingKm) : 0;

  // city indica llegada al destino: se activa cuando quedan menos de 2000m O cuando
  // el jugador está aparcado en el punto de entrega (remainingMeters === 0).
  // Sin este umbral, city==destination desde el primer tick y la entrega se
  // registraría antes de que el jugador conduzca nada.
  //
  // Umbral y ventana de gracia ampliados tras un caso real: en una entrega de
  // ~83km el ultimo tick capturado con destino activo ya mostraba remaining≈1km
  // (nunca bajo de los 500m originales entre dos ticks de ~10s), y despues hubo
  // un hueco de 2 minutos SIN telemetria (pantalla de "entrega completada"
  // bloqueando el polling) antes de que el trabajo desapareciera. Con el umbral
  // de 500m y la ventana de gracia de 90s antiguos, ninguno de los dos disparaba
  // — la entrega se quedaba "en ejecucion" para siempre y el mecanismo de
  // abandono automatico (5 min sin trabajo) acababa cobrando la penalizacion
  // completa por una entrega que si se hizo bien.
  let city = (destination && remainingMeters <= 2000) ? destination : null;

  if (city) {
    // Registrar cuándo estuvimos por última vez cerca del destino
    _nearDestinationAt   = Date.now();
    _nearDestinationCity = destination;
  } else if (!destination && _nearDestinationAt && (Date.now() - _nearDestinationAt) < 10 * 60 * 1000) {
    // Funbit limpió el trabajo antes de que el tick lo capturara cerca del
    // destino: si hace menos de 10 min estábamos cerca, tratamos esto como
    // llegada al destino.
    city = _nearDestinationCity;
    _nearDestinationAt   = null;
    _nearDestinationCity = null;
  }

  // Identificación del camión — los campos varían según versión del plugin Funbit
  const truckBrand = truck.make || truck.brand || truck.manufacturer || null;
  const truckModel = truck.model || null;
  const truckId    = truck.id   || null;

  // Identificación del remolque
  const trailerAttached = trailer.attached !== false && (trailer.id || trailer.name);
  const trailerName  = trailerAttached ? (trailer.name || trailer.model || null) : null;
  const trailerId    = trailerAttached ? (trailer.id   || null) : null;
  const trailerCargo = trailer.cargo || job.cargo || null;

  // Sanciones de tráfico — el plugin Funbit puede exponerlas en varios lugares
  // según la versión. Buscamos en todos los candidatos conocidos.
  const fines = { speeding: 0, redLight: 0, collision: 0, wrongWay: 0, noLights: 0, other: 0, totalEur: 0 };

  // Candidatos donde Funbit/SCS puede poner las multas
  const finesRaw = Array.isArray(raw.fines)           ? raw.fines
                 : Array.isArray(raw.game?.fines)      ? raw.game.fines
                 : Array.isArray(raw.events)            ? raw.events
                 : Array.isArray(raw.game?.events)      ? raw.game.events
                 : Array.isArray(raw.gameplay?.events)  ? raw.gameplay.events
                 : [];

  // Multa individual en raw.game (algunos plugins exponen un solo evento por tick)
  const singleFine = raw.game?.fine || raw.game?.lastFine || raw.fine || null;
  if (singleFine && (singleFine.offence || singleFine.offense || singleFine.type || singleFine.amount)) {
    finesRaw.push(singleFine);
  }

  // Log diagnóstico: si hay algo en finesRaw lo volcamos para poder ver el formato real
  if (finesRaw.length > 0) {
    console.log('[eurotrans-fines] raw fines recibidas:', JSON.stringify(finesRaw));
  }

  for (const f of finesRaw) {
    // Filtrar eventos que no sean multas (algunos plugins mezclan tipos de eventos)
    const evtType = (f.event || f.eventType || f.event_type || '').toLowerCase();
    if (evtType && !evtType.includes('fine') && !evtType.includes('penalty') && !evtType.includes('offence')) continue;

    const type   = (f.offence || f.offense || f.type || f.offenceType || f.offence_type || '').toLowerCase();
    const amount = Number(f.amount || f.penalty || f.value || f.fine || 0);
    fines.totalEur += amount;
    if      (/speed/i.test(type))                                                              fines.speeding++;
    else if (/red.?light|signal|mandatory.?stop|no.?mandatory/i.test(type))                   fines.redLight++;
    else if (/crash|collisi|avoid.?vehicle|avoid.?car|vehicle.?crash|prohib/i.test(type))     fines.collision++;
    else if (/wrong.?way|wrong.?dir|avoid.?ped|pedestrian/i.test(type))                       fines.wrongWay++;
    else if (/no.?head|headlight|no.?light|without.?light|driving.?light|light.?off/i.test(type)) fines.noLights++;
    else                                                           fines.other++;
  }

  return {
    speed: Number(truck.speed || 0),
    fuelPercent,
    damagePercent,
    truckDamagePercent,
    trailerDamagePercent,
    wearCabin,
    city,
    origin,
    destination,
    cargo: trailerCargo,
    jobIncome,
    plannedDistanceKm: plannedKm,
    distanceDrivenKm:  Math.round(distanceDrivenKm),
    remainingKm:       Math.round(remainingKm),
    jobDeadlineMinutesLeft,
    truckBrand,
    truckModel,
    truckId,
    trailerName,
    trailerId,
    fines
  };
}

// ── Poller de alta frecuencia para multas ──────────────────────────────────────
// El SCS SDK emite el evento de multa de forma efímera; si solo leemos cada 10s
// lo perdemos. Este poller lee Funbit cada 1s solo para acumular multas.

let _fineBuffer = { speeding: 0, redLight: 0, collision: 0, wrongWay: 0, noLights: 0, other: 0, totalEur: 0 };

async function _pollFines() {
  try {
    const res = await fetch(config.telemetryUrl);
    if (!res.ok) return;
    const raw = await res.json();
    if (!raw.game?.connected) return;

    // Volcado diagnóstico completo solo cuando hay datos interesantes
    const allKeys = Object.keys(raw);
    const unusualKeys = allKeys.filter(k => !['game','truck','trailer','job','navigation'].includes(k));
    if (unusualKeys.length > 0) {
      console.log('[funbit-fine-poll] claves inesperadas:', unusualKeys,
        JSON.stringify(unusualKeys.map(k => ({ k, v: raw[k] }))));
    }

    const finesRaw = Array.isArray(raw.fines)          ? raw.fines
                   : Array.isArray(raw.game?.fines)     ? raw.game.fines
                   : Array.isArray(raw.events)           ? raw.events
                   : Array.isArray(raw.game?.events)     ? raw.game.events
                   : Array.isArray(raw.gameplay?.events) ? raw.gameplay.events
                   : [];
    const single = raw.game?.fine || raw.game?.lastFine || raw.fine || null;
    if (single && (single.offence || single.offense || single.type || single.amount)) finesRaw.push(single);

    for (const f of finesRaw) {
      const evtType = (f.event || f.eventType || f.event_type || '').toLowerCase();
      if (evtType && !evtType.includes('fine') && !evtType.includes('penalty') && !evtType.includes('offence')) continue;
      const type   = (f.offence || f.offense || f.type || f.offenceType || '').toLowerCase();
      const amount = Number(f.amount || f.penalty || f.value || f.fine || 0);
      _fineBuffer.totalEur += amount;
      if      (/speed/i.test(type))                                                              _fineBuffer.speeding++;
      else if (/red.?light|signal|mandatory.?stop|no.?mandatory/i.test(type))                   _fineBuffer.redLight++;
      else if (/crash|collisi|avoid.?vehicle|avoid.?car|vehicle.?crash|prohib/i.test(type))     _fineBuffer.collision++;
      else if (/wrong.?way|wrong.?dir|avoid.?ped|pedestrian/i.test(type))                       _fineBuffer.wrongWay++;
      else if (/no.?head|headlight|no.?light|without.?light|driving.?light|light.?off/i.test(type)) _fineBuffer.noLights++;
      else                                                           _fineBuffer.other++;
      console.log('[funbit-fine-poll] multa detectada:', JSON.stringify(f));
    }
  } catch (_) {}
}

// Iniciar el poller inmediatamente (se llama desde index.js con setInterval)
function startFinePoller() {
  setInterval(_pollFines, 1000);
}

// El tick de telemetría principal llama esto para obtener las multas acumuladas
function flushFineBuffer() {
  const snap = { ..._fineBuffer };
  Object.keys(_fineBuffer).forEach(k => (_fineBuffer[k] = 0));
  return snap;
}

// Limpia el estado de "cerca del destino" al aceptar un nuevo trabajo
function resetNearDestination() {
  _nearDestinationAt   = null;
  _nearDestinationCity = null;
}

module.exports = { read, startFinePoller, flushFineBuffer, resetNearDestination };
