'use strict'
/**
 * address-db.js — recherche d'adresses LOCALE (Postgres ERPNext), remplace le Supabase cloud externe.
 *
 * Source : `rqa_addresses` (5,24M adresses du Québec, search_text désaccentué, index trigram GIN)
 * LEFT JOIN `fiber_availability` (couverture fibre Targo, joint par address_id) — toutes deux DÉJÀ
 * locales dans la db ERPNext `_eb65bdc0c4b1b2d6`. Le hub est sur le réseau erpnext_erpnext + a `pg`.
 *
 * Deux phases (comme l'autocomplete de dispo) :
 *   Phase 1 — civique présent : filtre `numero` (btree) + mots de rue (LIKE), tri fibre→J0L/J0S→similarité. ~20 ms.
 *   Phase 2 — sans civique / phase 1 vide : `word_similarity` (`<%`, indexable GIN), tri idem. ~700 ms.
 *     (NB : le `%`/similarity() plein sur 5,24M = 24-76 s → on utilise `<%` qui est sélectif et indexé.)
 *
 * Retourne une forme compatible avec l'ancien Supabase (adresse_formatee, numero_municipal,
 * odonyme_recompose_normal, nom_municipalite, latitude, longitude, fiber_available, zone_tarifaire,
 * max_speed, similarity_score) → consommateurs hub inchangés (pont, onboarding, checkout, site).
 */
const { Pool } = require('pg')
const { log } = require('./helpers')

const { norm } = require('./util/text') // helper texte partagé (Phase 1 : dé-duplication)

let _pool
function pool () {
  if (!_pool) {
    _pool = new Pool({
      host: process.env.ADDR_DB_HOST || 'erpnext-db-1',
      port: +(process.env.ADDR_DB_PORT || 5432),
      user: process.env.ADDR_DB_USER || 'postgres',
      password: process.env.ADDR_DB_PASS || '123',
      database: process.env.ADDR_DB_NAME || '_eb65bdc0c4b1b2d6',
      max: 4, idleTimeoutMillis: 30000, connectionTimeoutMillis: 6000,
    })
    _pool.on('error', (e) => log('address-db pool error:', e.message))
  }
  return _pool
}

const COLS = `a.id, a.address_full, a.numero, a.rue, a.ville, a.code_postal, a.longitude, a.latitude,
  (f.id IS NOT NULL) AS fiber_available, COALESCE(f.zone_tarifaire,0) AS zone_tarifaire, COALESCE(f.max_speed,0) AS max_speed`
const ORDER = `(f.id IS NOT NULL) DESC, (a.code_postal LIKE 'J0L%' OR a.code_postal LIKE 'J0S%') DESC, sim DESC`

function mapRow (r) {
  return {
    identifiant_unique_adresse: String(r.id),
    adresse_formatee: r.address_full || [r.numero, r.rue].filter(Boolean).join(' ') + (r.ville ? ', ' + r.ville : '') + (r.code_postal ? ' ' + r.code_postal : ''),
    numero_municipal: r.numero,
    numero_unite: null,
    code_postal: r.code_postal,
    odonyme_recompose_normal: r.rue,
    nom_municipalite: r.ville,
    latitude: r.latitude,
    longitude: r.longitude,
    fiber_available: !!r.fiber_available,
    zone_tarifaire: r.zone_tarifaire,
    max_speed: r.max_speed,
    similarity_score: r.sim != null ? +r.sim : null,
  }
}

// Forme BRUTE (colonnes de la fonction search_addresses : id, address_full, numero, rue, ville,
// code_postal, longitude, latitude, fiber_available, zone_tarifaire, max_speed, similarity_score)
// → consommée telle quelle par l'autocomplete du site web (endpoint /rpc/search_addresses).
function toRaw (r) {
  return {
    id: r.id, address_full: r.address_full, numero: r.numero, rue: r.rue, ville: r.ville,
    code_postal: r.code_postal, longitude: r.longitude, latitude: r.latitude,
    fiber_available: !!r.fiber_available, zone_tarifaire: r.zone_tarifaire, max_speed: r.max_speed,
    similarity_score: r.sim != null ? +r.sim : null,
  }
}

// Recherche principale (lignes brutes DB). term = texte tapé ("2338 rue rené-vinet" ou "rené-vinet ...").
async function searchRows (term, limit = 8) {
  const clean = norm(term)
  if (clean.length < 3) return []
  const lim = Math.min(Math.max(parseInt(limit) || 8, 1), 25)
  const civic = (clean.match(/^\s*(\d+)/) || [])[1] || null
  const streetPart = clean.replace(/^\s*\d+\s*/, '').trim()
  const words = streetPart ? streetPart.split(' ').filter(w => w.length >= 2) : []
  const p = pool()

  // ── Phase 1 : civique présent (rapide, btree numero) ──
  if (civic) {
    const sql = `SELECT ${COLS}, similarity(a.search_text, $3) AS sim
      FROM rqa_addresses a LEFT JOIN fiber_availability f ON f.address_id = a.id
      WHERE a.numero = $1
        AND (array_length($2::text[], 1) IS NULL OR NOT EXISTS (
          SELECT 1 FROM unnest($2::text[]) w WHERE a.search_text NOT LIKE '%' || w || '%'))
      ORDER BY ${ORDER}, a.numero
      LIMIT $4`
    const r = await p.query(sql, [civic, words, clean, lim])
    if (r.rows.length) return r.rows
  }

  // ── Phase 2 : word_similarity (sans civique, ou phase 1 vide) ──
  // `<%` (indexable GIN) au lieu de `%`/similarity() plein (24-76 s sur 5,24M). Seuil + statement_timeout
  // posés en SET LOCAL dans une transaction (déterministe + auto-reset au COMMIT ; borne le pire cas).
  const sql2 = `SELECT ${COLS}, word_similarity($1, a.search_text) AS sim
    FROM rqa_addresses a LEFT JOIN fiber_availability f ON f.address_id = a.id
    WHERE $1 <% a.search_text
    ORDER BY ${ORDER}
    LIMIT $2`
  const client = await p.connect()
  try {
    await client.query('BEGIN')
    await client.query('SET LOCAL pg_trgm.word_similarity_threshold = 0.6') // ~700 ms ; 0.5 plus large/lent
    await client.query('SET LOCAL statement_timeout = 4000') // garde-fou anti-hang (sécurité)
    const r2 = await client.query(sql2, [clean, lim])
    await client.query('COMMIT')
    return r2.rows
  } catch (e) {
    try { await client.query('ROLLBACK') } catch (_) {}
    log('searchRows phase2:', e.message)
    return []
  } finally { client.release() }
}

// Forme MAPPÉE (compat Supabase historique) → consommée par les services hub (pont, onboarding, checkout).
async function searchLocal (term, limit = 8) { return (await searchRows(term, limit)).map(mapRow) }
// Forme BRUTE (colonnes de la fonction) → consommée par l'autocomplete du site web.
async function searchRaw (term, limit = 8) { return (await searchRows(term, limit)).map(toRaw) }

// Municipalité (ville) DOMINANTE d'un code postal — pour résoudre le SECTEUR d'un job dont l'adresse finit par un
// code postal (pas de ville texte). code_postal est INDEXÉ (btree) → exact = ~48 ms. Repli FSA (3 lettres) plus lent
// (~2,4 s) mais CACHÉ par FSA (peu de FSA sur le territoire). Cache mémoire par code postal.
const _postalCache = new Map(); const _fsaCache = new Map()
async function villeForPostal (raw) {
  const pc = String(raw || '').toUpperCase().replace(/[^A-Z0-9]/g, '')
  if (!/^[A-Z]\d[A-Z]\d[A-Z]\d$/.test(pc)) return null
  if (_postalCache.has(pc)) return _postalCache.get(pc)
  const spaced = pc.slice(0, 3) + ' ' + pc.slice(3)
  let ville = null
  try {
    const { rows } = await pool().query('SELECT ville FROM rqa_addresses WHERE code_postal IN ($1,$2) AND ville IS NOT NULL GROUP BY ville ORDER BY COUNT(*) DESC LIMIT 1', [pc, spaced])
    ville = (rows[0] && rows[0].ville) || null
  } catch (e) { log('villeForPostal exact:', e.message) }
  if (!ville) {
    const fsa = pc.slice(0, 3)
    if (_fsaCache.has(fsa)) ville = _fsaCache.get(fsa)
    else { try { const { rows } = await pool().query('SELECT ville FROM rqa_addresses WHERE code_postal LIKE $1 AND ville IS NOT NULL GROUP BY ville ORDER BY COUNT(*) DESC LIMIT 1', [fsa + '%']); ville = (rows[0] && rows[0].ville) || null } catch (e) { log('villeForPostal fsa:', e.message) } _fsaCache.set(fsa, ville) }
  }
  _postalCache.set(pc, ville)
  return ville
}

module.exports = { searchLocal, searchRaw, pool, norm, villeForPostal }
