// ── Find optimal technician for emergency mid-day job insertion ───────────────
// Scores each tech based on: GPS proximity, current load, skills match, queue fit
// Uses real-time GPS when available, falls back to last job coords or home base
// ─────────────────────────────────────────────────────────────────────────────

import { MAPBOX_TOKEN } from 'src/config/erpnext'

// Euclidean distance approximation in km (Montreal latitude)
function distKm (a, b) {
  if (!a || !b || (!a[0] && !a[1]) || (!b[0] && !b[1])) return 999
  const dx = (a[0] - b[0]) * 80   // 1° lng ≈ 80 km at 45°N
  const dy = (a[1] - b[1]) * 111   // 1° lat ≈ 111 km
  return Math.sqrt(dx * dx + dy * dy)
}

// Get tech's effective current position: GPS > last queued job > home
function techCurrentPos (tech, dateStr) {
  if (tech.gpsCoords && tech.gpsOnline) return { coords: tech.gpsCoords, source: 'gps' }
  const todayJobs = tech.queue.filter(j => j.scheduledDate === dateStr)
  // Find last completed or in-progress job
  const done = todayJobs.filter(j => j.status === 'completed' || j.status === 'en-route')
  if (done.length) {
    const last = done[done.length - 1]
    if (last.coords && (last.coords[0] || last.coords[1])) return { coords: last.coords, source: 'lastJob' }
  }
  // Or next job in queue
  if (todayJobs.length) {
    const next = todayJobs.find(j => j.coords && (j.coords[0] || j.coords[1]))
    if (next) return { coords: next.coords, source: 'nextJob' }
  }
  return { coords: tech.coords, source: 'home' }
}

// Compute load: total hours assigned today
function techDayLoad (tech, dateStr) {
  return tech.queue
    .filter(j => j.scheduledDate === dateStr)
    .reduce((sum, j) => sum + (parseFloat(j.duration) || 1), 0)
}

// Best insertion point in tech's queue (minimizes detour)
function bestInsertionIdx (tech, jobCoords, dateStr) {
  const dayJobs = tech.queue.filter(j => j.scheduledDate === dateStr)
  if (!dayJobs.length) return 0
  if (!jobCoords || (!jobCoords[0] && !jobCoords[1])) return dayJobs.length

  // Try each insertion point, pick the one with least total detour
  let bestIdx = dayJobs.length, bestDetour = Infinity
  for (let i = 0; i <= dayJobs.length; i++) {
    const prev = i === 0 ? (tech.gpsCoords || tech.coords) : dayJobs[i - 1].coords
    const next = i < dayJobs.length ? dayJobs[i].coords : null
    const directDist = next ? distKm(prev, next) : 0
    const detour = distKm(prev, jobCoords) + (next ? distKm(jobCoords, next) : 0) - directDist
    if (detour < bestDetour) { bestDetour = detour; bestIdx = i }
  }
  return bestIdx
}

/**
 * Score and rank all technicians for an emergency job.
 *
 * @param {Object} params
 * @param {Array} params.technicians - All available techs
 * @param {Array} params.jobCoords - [lng, lat] of the emergency job
 * @param {number} params.jobDuration - Hours needed
 * @param {Array} params.jobTags - Required skill tags
 * @param {string} params.dateStr - YYYY-MM-DD (today)
 * @returns {Array} Ranked techs: [{ tech, score, distance, load, insertIdx, reasons }]
 */
export function rankTechs ({ technicians, jobCoords, jobDuration = 1, jobTags = [], dateStr }) {
  const hasCoords = jobCoords && (jobCoords[0] || jobCoords[1])
  const candidates = technicians.filter(t => t.status !== 'off' && t.status !== 'unavailable')

  const scored = candidates.map(tech => {
    const pos = techCurrentPos(tech, dateStr)
    const distance = hasCoords ? distKm(pos.coords, jobCoords) : 999
    const load = techDayLoad(tech, dateStr)
    const remainingCap = Math.max(0, 8 - load)
    const insertIdx = hasCoords ? bestInsertionIdx(tech, jobCoords, dateStr) : tech.queue.filter(j => j.scheduledDate === dateStr).length

    // ── Scoring (lower = better) ──
    let score = 0
    const reasons = []

    // 1. Proximity (weight: 40%) — distance in km, normalized to ~0-100
    const proxScore = Math.min(distance, 100)
    score += proxScore * 4
    if (distance < 5) reasons.push(`📍 ${distance.toFixed(1)} km (très proche)`)
    else if (distance < 15) reasons.push(`📍 ${distance.toFixed(1)} km`)
    else reasons.push(`📍 ${distance.toFixed(1)} km (loin)`)

    // 2. Load balance (weight: 30%) — prefer techs with capacity
    const loadScore = load * 10  // 0-80 range
    score += loadScore * 3
    if (remainingCap < jobDuration) {
      score += 500  // Heavy penalty: can't fit the job
      reasons.push(`⚠ Surchargé (${load.toFixed(1)}h/${8}h)`)
    } else if (load < 4) {
      reasons.push(`✓ Dispo (${load.toFixed(1)}h/${8}h)`)
    } else {
      reasons.push(`◐ Chargé (${load.toFixed(1)}h/${8}h)`)
    }

    // 3. Skills match (weight: 20%)
    if (jobTags.length) {
      const techTags = tech.tags || []
      const missing = jobTags.filter(t => !techTags.includes(t))
      score += missing.length * 200
      if (missing.length) reasons.push(`⚠ Manque: ${missing.join(', ')}`)
      else reasons.push('✓ Skills OK')
    }

    // 4. GPS freshness bonus (weight: 10%) — trust live GPS more
    if (pos.source === 'gps') {
      score -= 20  // Bonus for having live GPS
      reasons.push('🛰 GPS en direct')
    } else {
      reasons.push(`📌 Position: ${pos.source === 'home' ? 'domicile' : 'estimée'}`)
    }

    return { tech, score, distance, load, remainingCap, insertIdx, posSource: pos.source, reasons }
  })

  return scored.sort((a, b) => a.score - b.score)
}

/**
 * Get real driving times from Mapbox for top N candidates (optional refinement).
 * Updates the distance/score with actual driving duration.
 */
export async function refineWithDrivingTimes (ranked, jobCoords, topN = 3) {
  if (!jobCoords || (!jobCoords[0] && !jobCoords[1])) return ranked
  const top = ranked.slice(0, topN)
  const rest = ranked.slice(topN)

  const refined = await Promise.all(top.map(async (r) => {
    const techPos = r.tech.gpsCoords || r.tech.coords
    if (!techPos || (!techPos[0] && !techPos[1])) return r
    try {
      const url = `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${techPos[0]},${techPos[1]};${jobCoords[0]},${jobCoords[1]}?overview=false&access_token=${MAPBOX_TOKEN}`
      const res = await fetch(url)
      const data = await res.json()
      if (data.routes?.[0]) {
        const mins = Math.round(data.routes[0].duration / 60)
        const km = (data.routes[0].distance / 1000).toFixed(1)
        r.drivingMins = mins
        r.drivingKm = km
        // Replace proximity score with actual driving time
        r.score = mins * 4 + r.load * 30 + (r.reasons.some(r => r.includes('Manque')) ? 200 : 0)
        r.reasons[0] = `🚗 ${mins} min (${km} km)`
      }
    } catch { /* keep euclidean estimate */ }
    return r
  }))

  return [...refined.sort((a, b) => a.score - b.score), ...rest]
}
