'use strict'
// ── Identité unifiée du personnel — SOURCE UNIQUE ─────────────────────────────
// Problème résolu : une même personne a plusieurs COURRIELS (louis@targo.ca +
// louispaul@targointernet.com) et un enregistrement Dispatch Technician séparé,
// SANS identifiant unifié → doublons d'assignation, nom système (« louis ») au
// lieu du nom complet, user SSO ≠ tech. Ici : 1 personne = 1 identité canonique
//   { key, label, primary_email, alias_emails[], tech_id, employee }
// Tous les courriels (primaire + alias) et le tech_id résolvent à CETTE identité.
//
// SSO partout → la clé = le courriel de login (primaire). Éditable depuis OPS,
// écritures réservées aux admins (mêmes groupes que avatars/user-prefs). Store
// JSON hub (data/identities.json) — pas de custom-field ERPNext (API instable).
const fs = require('fs')
const path = require('path')
const { json, parseBody, log } = require('./helpers')

const FILE = path.join(__dirname, '..', 'data', 'identities.json')
const norm = (e) => String(e || '').trim().toLowerCase()
const meOf = (req) => norm(req.headers['x-authentik-email'])
// Admin = mêmes groupes Authentik que les autres écritures self/admin (avatars.js, user-prefs.js).
function isAdmin (req) {
  const g = String(req.headers['x-authentik-groups'] || req.headers['x-authentik-group'] || '')
  return /(^|[,|;])\s*(ops-?admin|admin(istrateurs?)?|superviseurs?)\s*([,|;]|$)/i.test(g)
}

function load () { try { return JSON.parse(fs.readFileSync(FILE, 'utf8')) || {} } catch { return {} } }
function save (m) { try { fs.mkdirSync(path.dirname(FILE), { recursive: true }) } catch (e) {} fs.writeFileSync(FILE, JSON.stringify(m, null, 1)); _index = null; return m }

// Index inverse (courriel→clé, tech→clé, nom→clé), reconstruit paresseusement, invalidé à chaque save().
let _index = null
function index () {
  if (_index) return _index
  const m = load(); const byEmail = {}, byTech = {}, byName = {}
  for (const [key, id] of Object.entries(m)) {
    for (const e of [id.primary_email, ...(id.alias_emails || [])].map(norm).filter(Boolean)) byEmail[e] = key
    if (id.tech_id) byTech[String(id.tech_id)] = key
    if (id.label) byName[norm(id.label)] = key
  }
  _index = { m, byEmail, byTech, byName }; return _index
}

// resolveIdentity(input) : input = courriel | tech_id | nom complet → { key, ...identité } | null
function resolveIdentity (input) {
  if (!input) return null
  const { m, byEmail, byTech, byName } = index()
  const s = String(input).trim()
  const key = byEmail[norm(s)] || byTech[s] || byName[norm(s)]
  return key ? { key, ...m[key] } : null
}
// Libellé alias-aware (pour getDisplayNameByEmail) : '' si inconnu → l'appelant retombe sur Authentik.
function labelForEmail (email) { const id = resolveIdentity(email); return id ? id.label : '' }
// Courriel canonique (primaire) pour un alias — sert à DÉ-DUPLIQUER les assignations. Repli = le courriel donné.
function canonicalEmail (email) { const id = resolveIdentity(email); return id ? norm(id.primary_email) : norm(email) }
// Clé d'identité pour dé-dupliquer (courriel OU tech_id) — repli = le courriel normalisé (identité « inconnue » stable).
function identityKey (input) { const id = resolveIdentity(input); return id ? id.key : norm(input) }
// Deux entrées (courriels/tech) = la même personne ?
function sameIdentity (a, b) { return identityKey(a) === identityKey(b) }
// Actif ? (départ = active:false → masqué des sélecteurs/roster mais l'identité RESTE pour l'historique).
// Inconnu de la carte → true (on ne bloque pas quelqu'un qu'on ne connaît pas).
function isActive (input) { const id = resolveIdentity(input); return id ? id.active !== false : true }

// Écriture programmatique (utilisée par le provisioning staff) — upsert par clé (= primary_email par défaut).
function upsert ({ key, label, primary_email, alias_emails, tech_id, employee, active, kind } = {}) {
  const primary = norm(primary_email); if (!primary || !label) return null
  const m = load(); const k = key || primary; const cur = m[k] || {}
  m[k] = {
    label: String(label).slice(0, 120), primary_email: primary,
    alias_emails: [...new Set([...(cur.alias_emails || []), ...(alias_emails || [])].map(norm).filter(e => e && e !== primary))],
    tech_id: tech_id || cur.tech_id || '', employee: employee || cur.employee || '',
    active: active === undefined ? (cur.active !== false) : (active !== false),
    kind: kind || cur.kind || 'employee',
  }
  save(m); return m[k]
}
function setActive (keyOrEmail, active) {
  const id = resolveIdentity(keyOrEmail); if (!id) return null
  const m = load(); if (!m[id.key]) return null
  m[id.key].active = active !== false; save(m); return m[id.key]
}

async function handle (req, res, method, path, url) {
  // ── Lecture (tout membre du personnel authentifié — annuaire interne) ──
  // Carte compacte pour le SPA : email→{key,label}, tech→{key,label}, + liste éditable.
  if (path === '/identity/map' && method === 'GET') {
    const { m } = index(); const emails = {}, techs = {}, list = []
    for (const [key, id] of Object.entries(m)) {
      const label = id.label || ''; const active = id.active !== false
      for (const e of [id.primary_email, ...(id.alias_emails || [])].map(norm).filter(Boolean)) emails[e] = { key, label, active }
      if (id.tech_id) techs[String(id.tech_id)] = { key, label, active }
      list.push({ key, label, primary_email: norm(id.primary_email), alias_emails: (id.alias_emails || []).map(norm), tech_id: id.tech_id || '', employee: id.employee || '', active, kind: id.kind || 'employee' })
    }
    return json(res, 200, { emails, techs, list })
  }
  if (path === '/identity/resolve' && method === 'GET') {
    const q = url.searchParams
    return json(res, 200, { identity: resolveIdentity(q.get('email') || q.get('tech') || q.get('name') || '') })
  }

  // ── Écriture : admins seulement ──
  if (method !== 'GET' && !isAdmin(req)) return json(res, 403, { error: 'réservé aux administrateurs' })

  // Créer / mettre à jour une identité. Body: { key?, label, primary_email, alias_emails?, tech_id?, employee? }
  if (path === '/identity' && method === 'POST') {
    const b = await parseBody(req); const primary = norm(b.primary_email)
    if (!primary || !b.label) return json(res, 400, { error: 'label + primary_email requis' })
    const m = load(); const key = b.key || primary
    m[key] = {
      label: String(b.label).slice(0, 120), primary_email: primary,
      alias_emails: [...new Set((b.alias_emails || []).map(norm).filter(e => e && e !== primary))],
      tech_id: b.tech_id || '', employee: b.employee || '',
      active: b.active !== false, // départ = false → masqué des sélecteurs, identité gardée pour l'historique
      kind: b.kind || 'employee', // employee | subcontractor
    }
    save(m); log('[identity] upsert ' + key + ' by ' + meOf(req))
    return json(res, 200, { ok: true, key, identity: m[key] })
  }
  // Ajouter / retirer un alias courriel. Body: { email, remove? }
  const mAlias = path.match(/^\/identity\/([^/]+)\/alias$/)
  if (mAlias && method === 'POST') {
    const key = decodeURIComponent(mAlias[1]); const b = await parseBody(req); const e = norm(b.email); const m = load()
    if (!m[key]) return json(res, 404, { error: 'identité introuvable' })
    const set = new Set(m[key].alias_emails || [])
    if (b.remove) set.delete(e); else if (e && e !== norm(m[key].primary_email)) set.add(e)
    m[key].alias_emails = [...set]; save(m)
    return json(res, 200, { ok: true, identity: m[key] })
  }
  // Fusionner deux identités (réconcilier user système ↔ tech en double). Body: { into, from }
  if (path === '/identity/merge' && method === 'POST') {
    const b = await parseBody(req); const m = load()
    if (!m[b.into] || !m[b.from]) return json(res, 404, { error: 'clé(s) introuvable(s)' })
    const set = new Set([...(m[b.into].alias_emails || []), norm(m[b.from].primary_email), ...(m[b.from].alias_emails || [])]
      .filter(e => e && e !== norm(m[b.into].primary_email)))
    m[b.into].alias_emails = [...set]
    if (!m[b.into].tech_id && m[b.from].tech_id) m[b.into].tech_id = m[b.from].tech_id
    if (!m[b.into].employee && m[b.from].employee) m[b.into].employee = m[b.from].employee
    delete m[b.from]; save(m); log('[identity] merge ' + b.from + ' → ' + b.into + ' by ' + meOf(req))
    return json(res, 200, { ok: true, identity: m[b.into] })
  }
  const mDel = path.match(/^\/identity\/([^/]+)$/)
  if (mDel && method === 'DELETE') { const key = decodeURIComponent(mDel[1]); const m = load(); delete m[key]; save(m); return json(res, 200, { ok: true }) }

  return json(res, 404, { error: 'identity endpoint not found' })
}

module.exports = { handle, resolveIdentity, labelForEmail, canonicalEmail, identityKey, sameIdentity, isActive, upsert, setActive }
