/**
 * Fetch pharmacy lists from an n8n (or any) HTTP webhook and map into FarmaClic rows.
 * Default URL: FARMACIAS_WEBHOOK_URL env or the project webhook.
 */

import { parseOsmOpeningHours } from '../API/opening-hours-osm.js';

export const DEFAULT_FARMACIAS_WEBHOOK =
  process.env.FARMACIAS_WEBHOOK_URL ||
  'https://n8n.hacecalor.net/webhook/farmacias';

/**
 * Append region query params, e.g. GET /webhook/farmacias?lat=41.5631&lon=2.0038&radio=1500
 * @param {string} baseUrl - Absolute webhook URL (may already include other query params)
 * @param {{ lat?: number|string, lon?: number|string, lng?: number|string, radio?: number|string }} region
 */
export function buildFarmaciasWebhookUrl(baseUrl, region = {}) {
  const u = new URL(baseUrl);
  const lat = region.lat;
  const lon = region.lon ?? region.lng;
  const radio = region.radio;

  if (lat !== undefined && lat !== null && String(lat).trim() !== '') {
    u.searchParams.set('lat', String(lat).trim());
  }
  if (lon !== undefined && lon !== null && String(lon).trim() !== '') {
    u.searchParams.set('lon', String(lon).trim());
  }
  if (radio !== undefined && radio !== null && String(radio).trim() !== '') {
    u.searchParams.set('radio', String(radio).trim());
  }
  return u.toString();
}

function pick(obj, keys) {
  if (!obj || typeof obj !== 'object') return null;
  for (const k of keys) {
    if (Object.prototype.hasOwnProperty.call(obj, k)) {
      const v = obj[k];
      if (v !== undefined && v !== null && String(v).trim() !== '') {
        return String(v).trim();
      }
    }
  }
  return null;
}

function toNumber(v) {
  if (v === undefined || v === null || v === '') return null;
  const n = typeof v === 'number' ? v : parseFloat(String(v).replace(',', '.'));
  return Number.isFinite(n) ? n : null;
}

/**
 * Normalize one raw record (Spanish / English field names, GeoJSON-ish).
 */
export function normalizePharmacyRecord(raw) {
  if (!raw || typeof raw !== 'object') return null;
  if (raw.json != null && typeof raw.json === 'object' && !Array.isArray(raw.json)) {
    return normalizePharmacyRecord(raw.json);
  }

  let name = pick(raw, [
    'name',
    'nombre',
    'farmacia',
    'titular',
    'denominacion',
    'denominación',
    'razon_social',
    'razón_social',
    'title',
  ]);
  let address = pick(raw, [
    'address',
    'direccion',
    'dirección',
    'domicilio',
    'ubicacion',
    'ubicación',
    'calle',
    'full_address',
    'direccion_completa',
  ]);
  const phone = pick(raw, [
    'phone',
    'telefono',
    'teléfono',
    'tel',
    'telephone',
    'movil',
    'móvil',
  ]);

  let latitude = toNumber(raw.latitude ?? raw.latitud ?? raw.lat ?? raw.y);
  let longitude = toNumber(raw.longitude ?? raw.longitud ?? raw.lng ?? raw.lon ?? raw.x);

  const coords = raw.geometry?.coordinates;
  if (Array.isArray(coords) && coords.length >= 2) {
    if (longitude == null) longitude = toNumber(coords[0]);
    if (latitude == null) latitude = toNumber(coords[1]);
  }
  if (raw.location && typeof raw.location === 'object') {
    if (latitude == null) latitude = toNumber(raw.location.lat ?? raw.location.latitude);
    if (longitude == null) longitude = toNumber(raw.location.lng ?? raw.location.lon ?? raw.location.longitude);
  }

  if (!name && pick(raw, ['properties'])) {
    return normalizePharmacyRecord(raw.properties);
  }

  if (!address && name) {
    const parts = [pick(raw, ['localidad', 'city', 'municipio']), pick(raw, ['cp', 'codigo_postal', 'postal_code'])]
      .filter(Boolean)
      .join(', ');
    if (parts) address = parts;
  }

  return {
    name: name || null,
    address: address || null,
    phone: phone || null,
    latitude,
    longitude,
    opening_hours: extractOpeningHours(raw),
  };
}

function extractOpeningHours(raw) {
  if (!raw || typeof raw !== 'object') return null;
  const direct = raw.opening_hours;
  if (direct && typeof direct === 'object' && !Array.isArray(direct)) {
    return direct;
  }
  const candidates = [
    direct,
    raw.openingHours,
    raw.horario,
    raw.hours,
    raw.tags?.opening_hours,
    raw.properties?.opening_hours,
  ];
  for (const c of candidates) {
    if (typeof c === 'string' && c.trim()) {
      const parsed = parseOsmOpeningHours(c);
      if (parsed) return parsed;
    }
  }
  return null;
}

/** n8n often returns [{ json: { ... } }, ...] */
function unwrapN8nItemArray(arr) {
  if (!Array.isArray(arr) || arr.length === 0) return arr || [];
  const first = arr[0];
  if (
    first &&
    typeof first === 'object' &&
    first.json != null &&
    typeof first.json === 'object' &&
    !Array.isArray(first.json)
  ) {
    return arr.map((x) => x.json);
  }
  return arr;
}

export function extractPharmacyRows(payload) {
  if (payload == null) return [];
  let list = [];

  if (Array.isArray(payload)) list = payload;
  else if (typeof payload === 'object') {
    const candidates = [
      payload.farmacias,
      payload.data,
      payload.results,
      payload.items,
      payload.rows,
      payload.records,
      payload.pharmacies,
      payload.body,
      payload.output,
    ];
    for (const c of candidates) {
      if (Array.isArray(c)) {
        list = c;
        break;
      }
    }
    if (list.length === 0 && Array.isArray(payload.json)) list = payload.json;
  }

  return unwrapN8nItemArray(list);
}

export async function fetchWebhookJson(url, fetchOptions = {}) {
  const res = await fetch(url, {
    method: 'GET',
    headers: { Accept: 'application/json', ...fetchOptions.headers },
    ...fetchOptions,
  });
  const text = await res.text();
  let json;
  try {
    json = text ? JSON.parse(text) : null;
  } catch {
    throw new Error(
      `Webhook returned non-JSON (HTTP ${res.status}): ${text.slice(0, 300)}`
    );
  }
  if (!res.ok) {
    const hint = json?.message || JSON.stringify(json);
    throw new Error(`Webhook HTTP ${res.status}: ${hint}`);
  }
  return json;
}

/**
 * @param {Function} dbGet - (sql, params) => Promise<row|undefined>
 * @param {Function} dbRun - (sql, params) => Promise<{lastID, changes}>
 * @param {object[]} rows - raw webhook items
 */
/** Insert normalized pharmacy rows; exported for OSM/Google/open-data importers */
export async function importPharmaciesFromRows(dbGet, dbRun, rows) {
  let inserted = 0;
  let skipped = 0;
  let invalid = 0;
  const errors = [];

  for (let i = 0; i < rows.length; i++) {
    const normalized = normalizePharmacyRecord(rows[i]);
    if (!normalized?.name || !normalized?.address) {
      invalid++;
      continue;
    }

    const { name, address, phone, latitude, longitude, opening_hours } = normalized;

    try {
      const existing = await dbGet(
        'SELECT id FROM pharmacies WHERE name = ? AND address = ?',
        [name, address]
      );
      if (existing) {
        skipped++;
        continue;
      }

      const openingHoursValue = opening_hours ? JSON.stringify(opening_hours) : null;

      await dbRun(
        'INSERT INTO pharmacies (name, address, phone, latitude, longitude, opening_hours) VALUES (?, ?, ?, ?, ?, ?)',
        [name, address, phone || null, latitude, longitude, openingHoursValue]
      );
      inserted++;
    } catch (err) {
      errors.push({ index: i, message: err.message });
    }
  }

  return { inserted, skipped, invalid, errors };
}

/**
 * Full flow: GET webhook → parse rows → insert into DB.
 * @param {string} [url] - Webhook base URL
 * @param {{ lat?: number|string, lon?: number|string, lng?: number|string, radio?: number|string } | null} [region] - Optional; adds ?lat=&lon=&radio= (meters)
 */
export async function runFarmaciaWebhookImport(
  dbGet,
  dbRun,
  url = DEFAULT_FARMACIAS_WEBHOOK,
  region = null
) {
  const finalUrl =
    region && (region.lat != null || region.lon != null || region.lng != null || region.radio != null)
      ? buildFarmaciasWebhookUrl(url, region)
      : url;

  const json = await fetchWebhookJson(finalUrl);
  const rows = extractPharmacyRows(json);

  if (rows.length === 0) {
    const keys = json && typeof json === 'object' ? Object.keys(json).join(', ') : typeof json;
    const err = new Error(
      `No pharmacy list found in webhook JSON (top-level keys: ${keys}). ` +
        `Fix the n8n workflow so the last node returns an array or { data: [...] }.`
    );
    err.details = json;
    throw err;
  }

  const stats = await importPharmaciesFromRows(dbGet, dbRun, rows);
  const out = {
    ...stats,
    totalReceived: rows.length,
    webhookUrl: finalUrl,
  };
  if (region && (region.lat != null || region.lon != null || region.lng != null || region.radio != null)) {
    out.region = {
      lat: region.lat ?? null,
      lon: region.lon ?? region.lng ?? null,
      radio: region.radio ?? null,
    };
  }
  return out;
}
