const fs = require('fs');
const path = require('path');

const configPath = path.join(__dirname, 'APIinvima.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8').replace(/^\uFEFF/, ''));

const INVIMA_DATASETS = Object.fromEntries(config.datasets.map((dataset) => [dataset.key, dataset]));
const INVIMA_DATASET_IDS = Object.fromEntries(config.datasets.map((dataset) => [dataset.id, dataset]));

const ALLOWED_SODA_PARAMS = ['$limit', '$offset', '$select', '$where', '$order'];
const RETRYABLE_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const clampInt = (value, fallback, min, max) => {
  const parsed = Number.parseInt(String(value ?? ''), 10);
  if (Number.isNaN(parsed)) return fallback;
  return Math.max(min, Math.min(max, parsed));
};

const computeBackoffMs = (attempt, baseMs = 800, maxMs = 8000) => {
  const exp = baseMs * 2 ** attempt;
  const jitter = Math.floor(Math.random() * 250);
  return Math.min(maxMs, exp + jitter);
};

const isNetworkRetryableError = (error) => {
  const code = String(error?.cause?.code || error?.code || '').toUpperCase();
  if (code.includes('TIMEOUT') || code.includes('ECONNRESET') || code.includes('EAI_AGAIN')) return true;
  const message = String(error?.message || '').toLowerCase();
  return (
    message.includes('fetch failed') ||
    message.includes('timeout') ||
    message.includes('socket hang up') ||
    message.includes('temporar')
  );
};

const cleanParamValue = (value) => {
  if (value === undefined || value === null) return null;
  const text = String(value).trim();
  return text ? text : null;
};

const buildInvimaUrl = (datasetId, params = {}) => {
  const url = new URL(`${config.baseUrl}/${datasetId}.json`);

  for (const key of ALLOWED_SODA_PARAMS) {
    const value = cleanParamValue(params[key]);
    if (value !== null) {
      url.searchParams.set(key, value);
    }
  }

  return url.toString();
};

const resolveDataset = (datasetKeyOrId) => {
  return INVIMA_DATASETS[datasetKeyOrId] || INVIMA_DATASET_IDS[datasetKeyOrId] || null;
};

const fetchInvimaDataset = async (
  datasetKeyOrId,
  options = {},
  appToken = process.env.SOCRATA_APP_TOKEN,
  requestOptions = {}
) => {
  const dataset = resolveDataset(datasetKeyOrId);
  if (!dataset) {
    throw new Error(`Dataset no reconocido: ${datasetKeyOrId}`);
  }

  const url = buildInvimaUrl(dataset.id, options);
  const headers = { Accept: 'application/json' };
  const maxRetries = clampInt(requestOptions.retries, 4, 0, 8);
  const timeoutMs = clampInt(requestOptions.timeoutMs, 35000, 4000, 120000);

  if (appToken) {
    headers['X-App-Token'] = appToken;
  }

  let lastError = null;
  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
    const attemptLabel = `${attempt + 1}/${maxRetries + 1}`;
    try {
      const signal = typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(timeoutMs) : undefined;
      const response = await fetch(url, { headers, signal });
      if (!response.ok) {
        const body = await response.text();
        const error = new Error(
          `Error consultando ${dataset.key} (${dataset.id}): ${response.status} ${response.statusText}. ${body}`
        );
        lastError = error;

        if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
          await sleep(computeBackoffMs(attempt));
          continue;
        }
        throw error;
      }

      return response.json();
    } catch (error) {
      lastError = error;
      if (attempt < maxRetries && isNetworkRetryableError(error)) {
        await sleep(computeBackoffMs(attempt));
        continue;
      }

      if (error instanceof Error && error.message.includes('Error consultando')) {
        throw error;
      }

      throw new Error(
        `Error consultando ${dataset.key} (${dataset.id}) [intento ${attemptLabel}]: ${String(error?.message || error)}`
      );
    }
  }

  throw lastError || new Error(`No fue posible consultar dataset ${dataset.key} (${dataset.id}).`);
};

module.exports = {
  apiConfig: config,
  INVIMA_DATASETS,
  INVIMA_DATASET_IDS,
  ALLOWED_SODA_PARAMS,
  buildInvimaUrl,
  fetchInvimaDataset
};
