'use strict'
/**
 * address-conformity.js — back-end de la page Ops « Conformité des adresses ».
 *
 * Source de vérité : Service Location ERPNext (tabService Location) avec son lien AQ
 * (aq_address_id → rqa_addresses, linked_address canonique, address_validation_status). Cette page
 * permet de RÉSOUDRE une fois pour toutes les adresses non conformes (review/unmatched) :
 *   - Approuver  : la proposition AQ devient officielle (statut validated, coords RQA).
 *   - Corriger   : chercher la bonne adresse AQ (recherche locale) et la lier.
 *   - GPS manuel : poser lat/long (ex. relevé sur map.targointernet.com qui a la géoloc des unités).
 *   - Rejeter    : pas d'adresse AQ (boîte postale, hors-QC…) → statut 'no_address'.
 *
 * Tout est LOCAL (Postgres ERPNext). Routes sous /address/conformity/* (cf. server.js).
 */
const { json, parseBody, log, cors } = require('./helpers')
const { searchRaw, pool } = require('./address-db') // réutilise le pool LOCAL partagé (rqa_addresses + fiber)
const { norm } = require('./util/text')

// Application de la géoloc de remplacement des campings sur leurs lots (match ville normalisée). Réutilisée
// par /campings/apply ET appelée après l'ajout/édition d'un camping. Retourne le décompte par camping.
async function applyCampings (p) {
  const r = await p.query(`WITH applied AS (
    UPDATE "tabService Location" sl SET latitude=c.latitude, longitude=c.longitude,
      linked_address=c.name||' — '||COALESCE(c.address,''), aq_address_id=NULL,
      address_validation_status='validated', address_validated_at=NOW(), modified=NOW()
    FROM camping_registry c
    WHERE c.active AND lower(unaccent(coalesce(sl.city,''))) LIKE '%'||c.keyword||'%'
    RETURNING c.name AS camping)
    SELECT camping, count(*)::int n FROM applied GROUP BY camping ORDER BY 2 DESC`)
  return r.rows
}

// Dernier repli : pour les 'unmatched' qu'on n'a pas pu géocoder, placer au CENTRE (centroïde rqa_addresses)
// du code postal (préféré, plus précis) sinon de la ville → le job apparaît au moins dans le bon secteur.
// Statut 'area' (approximatif). Les hors-QC/junk (pas dans rqa_addresses) restent 'unmatched'.
async function applyAreaFallback (p) {
  const r = await p.query(`
    WITH um AS (
      SELECT name, replace(upper(coalesce(postal_code,'')),' ','') cp, lower(unaccent(coalesce(city,''))) city,
             postal_code cp_raw, city city_raw
      FROM "tabService Location" WHERE address_validation_status='unmatched'
    ),
    pc AS (SELECT replace(upper(code_postal),' ','') cp, avg(latitude) lat, avg(longitude) lon FROM rqa_addresses
           WHERE replace(upper(code_postal),' ','') IN (SELECT cp FROM um WHERE cp ~ '^[A-Z][0-9][A-Z][0-9]') GROUP BY 1),
    cc AS (SELECT lower(unaccent(ville)) city, avg(latitude) lat, avg(longitude) lon FROM rqa_addresses
           WHERE lower(unaccent(ville)) IN (SELECT city FROM um WHERE city NOT IN ('','n/a','ville','x','-')) GROUP BY 1),
    upd AS (
      UPDATE "tabService Location" sl SET
        latitude=COALESCE(pc.lat,cc.lat), longitude=COALESCE(pc.lon,cc.lon),
        linked_address='≈ centre ' || COALESCE(NULLIF(CASE WHEN pc.lat IS NOT NULL THEN um.cp_raw END,''), um.city_raw),
        address_validation_status='area', address_validated_at=NOW(), modified=NOW()
      FROM um LEFT JOIN pc ON pc.cp=um.cp LEFT JOIN cc ON cc.city=um.city
      WHERE sl.name=um.name AND (pc.lat IS NOT NULL OR cc.lat IS NOT NULL)
      RETURNING (pc.lat IS NOT NULL) AS by_postal)
    SELECT count(*)::int total, count(*) FILTER (WHERE by_postal)::int by_postal,
           count(*) FILTER (WHERE NOT by_postal)::int by_city FROM upd`)
  return r.rows[0]
}

// Type d'adresse (pour trier la file) : camping (sobriquet de lot), civique à corriger, non-adresse, review standard.
const TYPE_SQL = `CASE
  WHEN sl.city ILIKE '%camping%' OR sl.address_line ILIKE '%camping%' THEN 'camping'
  WHEN sl.address_validation_status='unmatched' AND sl.address_line ~ '^[0-9]' AND sl.city NOT IN ('','N/A','Ville','x','-') THEN 'civique'
  WHEN sl.address_validation_status='unmatched' THEN 'non_adresse'
  ELSE 'review' END`

async function handle (req, res, method, path) {
  try {
    if (method === 'OPTIONS') { cors(res); res.writeHead(204); res.end(); return }

    // Compteurs par statut + par type (file de travail).
    if (path === '/address/conformity/stats' && method === 'GET') {
      const p = pool()
      const byStatus = (await p.query('SELECT address_validation_status s, count(*) n FROM "tabService Location" GROUP BY 1 ORDER BY 2 DESC')).rows
      const byType = (await p.query(`SELECT ${TYPE_SQL} t, count(*) n FROM "tabService Location" sl WHERE address_validation_status IN ('review','unmatched') GROUP BY 1 ORDER BY 2 DESC`)).rows
      cors(res); return json(res, 200, { ok: true, by_status: byStatus, by_type: byType })
    }

    // Liste paginée des adresses à traiter (review/unmatched), filtrable par type + recherche texte.
    if (path === '/address/conformity/list' && method === 'GET') {
      const u = new URL(req.url, 'http://localhost')
      const type = u.searchParams.get('type') || ''
      const q = (u.searchParams.get('q') || '').trim()
      const limit = Math.min(parseInt(u.searchParams.get('limit')) || 50, 200)
      const offset = Math.max(parseInt(u.searchParams.get('offset')) || 0, 0)
      const where = [`sl.address_validation_status IN ('review','unmatched')`]
      const params = []
      if (type) { params.push(type); where.push(`${TYPE_SQL} = $${params.length}`) }
      if (q) { params.push('%' + q + '%'); where.push(`(sl.address_line ILIKE $${params.length} OR sl.city ILIKE $${params.length} OR sl.customer ILIKE $${params.length} OR sl.name ILIKE $${params.length})`) }
      const whereSql = where.join(' AND ')
      const p = pool()
      const total = (await p.query(`SELECT count(*) n FROM "tabService Location" sl WHERE ${whereSql}`, params)).rows[0].n
      params.push(limit); params.push(offset)
      const rows = (await p.query(
        `SELECT sl.name, sl.customer, sl.address_line, sl.city, sl.postal_code,
                sl.address_validation_status AS status, sl.aq_address_id, sl.linked_address,
                sl.latitude, sl.longitude, ${TYPE_SQL} AS type
         FROM "tabService Location" sl WHERE ${whereSql}
         ORDER BY ${TYPE_SQL}, sl.city, sl.address_line
         LIMIT $${params.length - 1} OFFSET $${params.length}`, params)).rows
      cors(res); return json(res, 200, { ok: true, total: Number(total), limit, offset, rows })
    }

    // Candidats AQ pour l'action « Corriger » (recherche locale rqa_addresses + fibre).
    if (path === '/address/conformity/candidates' && method === 'GET') {
      const u = new URL(req.url, 'http://localhost')
      const q = (u.searchParams.get('q') || '').trim()
      let results = []
      try { results = await searchRaw(q, 8) } catch (e) { log('conformity/candidates:', e.message) }
      cors(res); return json(res, 200, { ok: true, results })
    }

    // Résoudre une adresse : approve | correct | gps | reject. Écrit le lien/coords/statut (source de vérité).
    if (path === '/address/conformity/resolve' && method === 'POST') {
      const b = await parseBody(req)
      const name = b.name
      if (!name) { cors(res); return json(res, 400, { ok: false, error: 'name requis' }) }
      const p = pool()
      const set = ['address_validated_at = NOW()', 'modified = NOW()']
      const params = []
      const add = (frag, val) => { params.push(val); set.push(`${frag} = $${params.length}`) }
      if (b.action === 'approve') {
        // garde la proposition existante (aq_address_id/linked_address) + coords déjà raffinées → validated
        set.push(`address_validation_status = 'validated'`)
      } else if (b.action === 'correct') {
        if (b.aq_address_id != null) add('aq_address_id', String(b.aq_address_id))
        if (b.linked_address != null) add('linked_address', b.linked_address)
        if (b.latitude != null) add('latitude', Number(b.latitude))
        if (b.longitude != null) add('longitude', Number(b.longitude))
        set.push(`address_validation_status = 'validated'`)
      } else if (b.action === 'gps') {
        if (b.latitude == null || b.longitude == null) { cors(res); return json(res, 400, { ok: false, error: 'latitude/longitude requis' }) }
        add('latitude', Number(b.latitude)); add('longitude', Number(b.longitude))
        set.push(`address_validation_status = 'validated'`) // position confirmée manuellement
      } else if (b.action === 'reject') {
        set.push(`address_validation_status = 'no_address'`) // pas d'adresse civique (boîte postale, hors-QC…)
      } else { cors(res); return json(res, 400, { ok: false, error: 'action inconnue' }) }
      params.push(name)
      const r = await p.query(`UPDATE "tabService Location" SET ${set.join(', ')} WHERE name = $${params.length}`, params)
      cors(res); return json(res, 200, { ok: true, updated: r.rowCount, name, action: b.action })
    }

    // ── Registre des campings (géoloc de remplacement fixe) ──
    if (path === '/address/conformity/campings' && method === 'GET') {
      const p = pool()
      const campings = (await p.query('SELECT id, keyword, name, address, latitude, longitude, active FROM camping_registry ORDER BY name, keyword')).rows
      const counts = (await p.query(`SELECT c.name, count(sl.name)::int n FROM camping_registry c
        LEFT JOIN "tabService Location" sl ON c.active AND lower(unaccent(coalesce(sl.city,''))) LIKE '%'||c.keyword||'%'
        GROUP BY c.name`)).rows
      const lots = {}; for (const x of counts) lots[x.name] = x.n
      cors(res); return json(res, 200, { ok: true, campings, lots })
    }
    if (path === '/address/conformity/campings' && method === 'POST') {
      const b = await parseBody(req)
      if (!b.keyword || !b.name || b.latitude == null || b.longitude == null) { cors(res); return json(res, 400, { ok: false, error: 'keyword/name/latitude/longitude requis' }) }
      const p = pool()
      await p.query(`INSERT INTO camping_registry (keyword,name,address,latitude,longitude,active)
        VALUES ($1,$2,$3,$4,$5,true)
        ON CONFLICT (keyword) DO UPDATE SET name=$2, address=$3, latitude=$4, longitude=$5, active=true`,
        [norm(b.keyword), b.name, b.address || '', Number(b.latitude), Number(b.longitude)])
      const applied = await applyCampings(p) // applique direct le nouveau/maj camping
      cors(res); return json(res, 200, { ok: true, applied })
    }
    if (path === '/address/conformity/campings/apply' && method === 'POST') {
      cors(res); return json(res, 200, { ok: true, applied: await applyCampings(pool()) })
    }
    // Repli centroïde (centre du CP/ville) pour les unmatched restants
    if (path === '/address/conformity/apply-area' && method === 'POST') {
      cors(res); return json(res, 200, { ok: true, ...(await applyAreaFallback(pool())) })
    }

    cors(res); return json(res, 404, { ok: false, error: 'route conformité inconnue' })
  } catch (e) {
    log('address-conformity error:', e.message)
    cors(res); return json(res, 500, { ok: false, error: String(e.message || e) })
  }
}

module.exports = { handle }
