import { defineStore } from 'pinia'
import { ref } from 'vue'
import { fetchTechnicians, loadTechTags, fetchJobsFast, fetchJobFull, updateJob, createJob as apiCreateJob, fetchTags, createTech as apiCreateTech, deleteTech as apiDeleteTech } from 'src/api/dispatch'
import { TECH_COLORS } from 'src/config/erpnext'
import { serializeAssistants, normalizeStatus, parseWeeklySchedule } from 'src/composables/useHelpers'
import { useGpsTracking } from 'src/composables/useGpsTracking'

function nextWeekday () {
  const d = new Date()
  const day = d.getDay()
  if (day === 6) d.setDate(d.getDate() + 2)
  else if (day === 0) d.setDate(d.getDate() + 1)
  return d.toISOString().slice(0, 10)
}

export const useDispatchStore = defineStore('dispatch', () => {
  const technicians = ref([])
  const jobs        = ref([])
  const allTags     = ref([])
  const loading     = ref(false)
  const erpStatus   = ref('pending')
  const jobVersion  = ref(0)   // Incremented on any job/queue mutation to bust caches

  const { traccarDevices, pollGps, startGpsTracking, stopGpsTracking } = useGpsTracking(technicians)

  function _mapJob (j) {
    return {
      id:          j.ticket_id || j.name,
      name:        j.name,
      subject:     j.subject    || 'Job sans titre',
      address:     j.address    || 'Adresse inconnue',
      coords:      [j.longitude || 0, j.latitude || 0],
      // Persisted ERPNext links — surfaced as clickable shortcuts in
      // the RightPanel so the dispatcher can jump to the client's
      // full record (or the Service Location in ERPNext) without
      // leaving the dispatch board. The Mapbox marker still uses
      // the cached coords above; for source-of-truth verification
      // (geocode mismatch) the rep clicks through to /clients/:id.
      customer:        j.customer || null,
      serviceLocation: j.service_location || null,
      priority:    j.priority   || 'low',
      duration:    j.duration_h || 1,
      status:      j.status     || 'open',
      assignedTech: j.assigned_tech || null,
      routeOrder:  j.route_order || 0,
      legDist:     j.leg_distance || null,
      legDur:      j.leg_duration || null,
      scheduledDate: j.scheduled_date || null,
      endDate:     j.end_date || null,
      startTime:   j.start_time || null,
      assistants:  (j.assistants || []).map(a => ({ techId: a.tech_id, techName: a.tech_name, duration: a.duration_h || 0, note: a.note || '', pinned: !!a.pinned })),
      tags: (j.tags || []).map(t => t.tag),
      tagsWithLevel: (j.tags || []).map(t => ({ tag: t.tag, level: t.level || 0, required: t.required || 0 })),
      customer: j.customer || null,
      serviceLocation: j.service_location || null,
      sourceIssue: j.source_issue || null,
      dependsOn: j.depends_on || null,
      jobType: j.job_type || null,
      parentJob: j.parent_job || null,
      stepOrder: j.step_order || 0,
      onOpenWebhook: j.on_open_webhook || null,
      onCloseWebhook: j.on_close_webhook || null,
      salesOrder: j.sales_order || null,
      orderSource: j.order_source || 'Manual',
      published: j.published === undefined ? true : !!j.published,
      continuous: !!j.continuous,  // Emergency: span weekends/off-days
      // Recurrence fields
      isRecurring: !!j.is_recurring,
      recurrenceRule: j.recurrence_rule || null,    // RRULE string
      recurrenceEnd: j.recurrence_end || null,      // YYYY-MM-DD or null = indefinite
      pausePeriods: j.pause_periods ? (typeof j.pause_periods === 'string' ? JSON.parse(j.pause_periods) : j.pause_periods) : [],
      templateId: j.template_id || null,            // For materialized instances → parent template
    }
  }

  function _mapTech (t, idx) {
    const status = normalizeStatus(t.status)
    return {
      id:       t.technician_id || t.name,
      name:     t.name,
      fullName: t.full_name || t.name,
      status,
      active:   status !== 'inactive' && status !== 'off',
      group:    t.tech_group || '',
      resourceType: t.resource_type || 'human',           // 'human' | 'material'
      resourceCategory: t.resource_category || '',         // 'Véhicule' | 'Outil' | 'Salle' | etc.
      user:     t.user   || null,
      colorIdx: idx % TECH_COLORS.length,
      // Default departure point = Gigafibre HQ (1867 chemin de la Rivière,
      // Sainte-Clotilde, QC). Used when a tech has no explicit longitude
      // /latitude saved in ERPNext, so route optimization has a sensible
      // starting point even before the dispatcher sets it.
      coords:   [t.longitude || -73.6756177, t.latitude || 45.1599145],
      gpsCoords: null,
      gpsSpeed: 0,
      gpsTime: null,
      gpsOnline: false,
      traccarDeviceId: t.traccar_device_id || null,
      phone:    t.phone || '',
      email:    t.email || '',
      absenceReason: t.absence_reason || '',
      absenceFrom:   t.absence_from || null,
      absenceUntil:  t.absence_until || null,
      absenceStartTime: t.absence_start_time || null,
      absenceEndTime: t.absence_end_time || null,
      weeklySchedule: parseWeeklySchedule(t.weekly_schedule),
      queue:    [],
      tags: (t.tags || []).map(tg => tg.tag),
      tagsWithLevel: (t.tags || []).map(tg => ({ tag: tg.tag, level: tg.level || 0 })),
      extraShifts: t.extra_shifts ? (typeof t.extra_shifts === 'string' ? JSON.parse(t.extra_shifts) : t.extra_shifts) : [],
    }
  }

  function _rebuildQueues () {
    technicians.value.forEach(tech => {
      tech.queue = jobs.value.filter(j => j.assignedTech === tech.id).sort((a, b) => a.routeOrder - b.routeOrder)
      tech.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === tech.id))
    })
  }

  async function loadAll (dateRange = null) {
    loading.value = true
    erpStatus.value = 'pending'
    const t0 = performance.now()
    try {
      // All 3 fetches in parallel — techs, tags, jobs
      const [rawTechs, rawTags, rawJobs] = await Promise.all([
        fetchTechnicians(),
        fetchTags(),
        fetchJobsFast([['status', 'in', ['open', 'assigned', 'in_progress']]]),
      ])
      allTags.value = rawTags
      technicians.value = rawTechs.map(_mapTech)
      jobs.value = rawJobs.map(_mapJob)
      _rebuildQueues()
      erpStatus.value = 'ok'

      // Background: load tech tags (child tables) without blocking render
      loadTechTags(rawTechs.map(t => t.name)).then(fullDocs => {
        for (const doc of fullDocs) {
          const tech = technicians.value.find(t => t.name === doc.name)
          if (tech && doc.tags?.length) {
            tech.tags = doc.tags.map(tg => tg.tag)
            tech.tagsWithLevel = doc.tags.map(tg => ({ tag: tg.tag, level: tg.level || 0 }))
          }
        }
        // Tech tags loaded in background
      }).catch(() => {})
    } catch (e) {
      console.error('[dispatch] loadAll failed:', e)
      erpStatus.value = e.message?.includes('session') ? 'session_expired' : 'error'
    } finally {
      loading.value = false
    }
  }

  // Load full doc with child tables for a specific job (on demand)
  async function loadJobDetails (jobId) {
    const job = jobs.value.find(j => j.id === jobId || j.name === jobId)
    if (!job) return
    try {
      const full = await fetchJobFull(job.name)
      const mapped = _mapJob(full)
      Object.assign(job, mapped)
      _rebuildQueues()
    } catch {}
  }

  async function loadJobsForTech (techId) {
    loading.value = true
    try {
      const raw = await fetchJobsFast([['assigned_tech', '=', techId]])
      jobs.value = raw.map(_mapJob)
    } finally {
      loading.value = false
    }
  }

  async function setJobStatus (jobId, status) {
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    const prevStatus = job.status
    job.status = status
    await updateJob(job.id, { status })
    // Fire n8n webhooks on status transitions
    _fireWebhookIfNeeded(job, prevStatus, status)
  }

  function _fireWebhookIfNeeded (job, prevStatus, newStatus) {
    const prev = (prevStatus || '').toLowerCase()
    const next = (newStatus || '').toLowerCase()
    let url = null
    if (next === 'assigned' && prev === 'open' && job.onOpenWebhook) {
      url = job.onOpenWebhook
    } else if (next === 'completed' && job.onCloseWebhook) {
      url = job.onCloseWebhook
    }
    if (url) {
      fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          event: next === 'completed' ? 'job_closed' : 'job_opened',
          job_name: job.name, job_subject: job.subject,
          job_status: newStatus, job_type: job.jobType,
          customer: job.customer, timestamp: new Date().toISOString(),
        }),
      }).catch(err => console.warn('[n8n webhook]', err))
    }
  }

  async function assignJobToTech (jobId, techId, routeOrder, scheduledDate) {
    jobVersion.value++
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    technicians.value.forEach(t => { t.queue = t.queue.filter(q => q.id !== jobId) })
    const tech = technicians.value.find(t => t.id === techId)
    if (tech) {
      job.assignedTech = techId
      job.routeOrder   = routeOrder
      job.status       = 'assigned'
      if (scheduledDate !== undefined) job.scheduledDate = scheduledDate
      tech.queue.splice(routeOrder, 0, job)
      tech.queue.forEach((q, i) => { q.routeOrder = i })
    }
    const payload = { assigned_tech: techId, route_order: routeOrder, status: 'assigned' }
    if (scheduledDate !== undefined) payload.scheduled_date = scheduledDate || ''
    await updateJob(job.id, payload)
  }

  async function unassignJob (jobId) {
    jobVersion.value++
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    technicians.value.forEach(t => { t.queue = t.queue.filter(q => q.id !== jobId) })
    job.assignedTech = null
    job.status       = 'open'
    try { await updateJob(job.name || job.id, { assigned_tech: null, status: 'open' }) } catch (_) {}
  }

  async function createJob (fields) {
    const localId = 'WO-' + Date.now().toString(36).toUpperCase()
    const job = _mapJob({
      ticket_id: localId, name: localId,
      subject:   fields.subject   || 'Nouveau travail',
      address:   fields.address   || '',
      longitude: fields.longitude || 0,
      latitude:  fields.latitude  || 0,
      duration_h: parseFloat(fields.duration_h) || 1,
      priority:  fields.priority  || 'low',
      status:    fields.assigned_tech ? 'assigned' : 'open',
      assigned_tech: fields.assigned_tech || null,
      scheduled_date: fields.scheduled_date || (fields.assigned_tech ? nextWeekday() : null),
      start_time: fields.start_time || null,
      route_order: 0,
      published: 0,
    })
    jobs.value.push(job)
    if (fields.assigned_tech) {
      const tech = technicians.value.find(t => t.id === fields.assigned_tech)
      if (tech) { job.routeOrder = tech.queue.length; tech.queue.push(job) }
    }
    try {
      const created = await apiCreateJob({
        subject: job.subject, address: job.address,
        longitude: job.coords?.[0] || '', latitude: job.coords?.[1] || '',
        duration_h: job.duration, priority: job.priority, status: job.status,
        assigned_tech: job.assignedTech || '', scheduled_date: job.scheduledDate || '',
        start_time: job.startTime || '',
        customer: fields.customer || '', service_location: fields.service_location || '',
        source_issue: fields.source_issue || '', depends_on: fields.depends_on || '',
        job_type: fields.job_type || '',
        parent_job: fields.parent_job || '', step_order: fields.step_order || 0,
        on_open_webhook: fields.on_open_webhook || '', on_close_webhook: fields.on_close_webhook || '',
      })
      if (created?.name) { job.id = created.name; job.name = created.name }
    } catch (_) {}
    return job
  }

  function publishJobsLocal (jobNames) {
    for (const name of jobNames) {
      const job = jobs.value.find(j => j.id === name || j.name === name)
      if (job) job.published = true
    }
  }

  async function setJobSchedule (jobId, scheduledDate, startTime) {
    jobVersion.value++
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    job.scheduledDate = scheduledDate || null
    job.startTime = startTime !== undefined ? startTime : job.startTime
    const payload = { scheduled_date: job.scheduledDate || '' }
    if (startTime !== undefined) payload.start_time = startTime || ''
    try { await updateJob(job.name || job.id, payload) } catch (_) {}
  }

  async function updateJobCoords (jobId, lng, lat) {
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    job.coords = [lng, lat]
    try { await updateJob(job.name || job.id, { longitude: lng, latitude: lat }) } catch (_) {}
  }

  async function addAssistant (jobId, techId) {
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    if (job.assignedTech === techId) return
    if (job.assistants.some(a => a.techId === techId)) return
    const tech = technicians.value.find(t => t.id === techId)
    const entry = { techId, techName: tech?.fullName || techId, duration: job.duration, note: '', pinned: false }
    job.assistants = [...job.assistants, entry]
    if (tech) tech.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === tech.id))
    try { await updateJob(job.name || job.id, { assistants: serializeAssistants(job.assistants) }) } catch (_) {}
  }

  async function removeAssistant (jobId, techId) {
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    job.assistants = job.assistants.filter(a => a.techId !== techId)
    const tech = technicians.value.find(t => t.id === techId)
    if (tech) tech.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === tech.id))
    try { await updateJob(job.name || job.id, { assistants: serializeAssistants(job.assistants) }) } catch (_) {}
  }

  async function reorderTechQueue (techId, fromIdx, toIdx) {
    const tech = technicians.value.find(t => t.id === techId)
    if (!tech) return
    const [moved] = tech.queue.splice(fromIdx, 1)
    tech.queue.splice(toIdx, 0, moved)
    tech.queue.forEach((q, i) => { q.routeOrder = i })
    await Promise.all(tech.queue.map((q, i) => updateJob(q.id, { route_order: i })))
  }

  function smartAssign (jobId, newTechId, dateStr) {
    jobVersion.value++
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    if (job.assistants.some(a => a.techId === newTechId)) {
      job.assistants = job.assistants.filter(a => a.techId !== newTechId)
      updateJob(job.name || job.id, { assistants: serializeAssistants(job.assistants) }).catch(() => {})
    }
    assignJobToTech(jobId, newTechId, technicians.value.find(t => t.id === newTechId)?.queue.length || 0, dateStr)
    _rebuildAssistJobs()
  }

  function fullUnassign (jobId) {
    jobVersion.value++
    const job = jobs.value.find(j => j.id === jobId)
    if (!job) return
    if (job.assistants.length) { job.assistants = []; updateJob(job.name || job.id, { assistants: [] }).catch(() => {}) }
    unassignJob(jobId)
    _rebuildAssistJobs()
  }

  function _rebuildAssistJobs () {
    technicians.value.forEach(t => { t.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === t.id)) })
  }

  async function createTechnician (fields) {
    const maxNum = technicians.value.reduce((max, t) => {
      const m = (t.id || '').match(/TECH-(\d+)/)
      return m ? Math.max(max, parseInt(m[1])) : max
    }, 0)
    fields.technician_id = 'TECH-' + (maxNum + 1)
    const doc = await apiCreateTech(fields)
    const tech = _mapTech(doc, technicians.value.length)
    technicians.value.push(tech)
    return tech
  }

  async function deleteTechnician (techId) {
    const tech = technicians.value.find(t => t.id === techId)
    if (!tech) return
    await apiDeleteTech(tech.name)
    technicians.value = technicians.value.filter(t => t.id !== techId)
  }

  const startGpsPolling = startGpsTracking
  const stopGpsPolling  = stopGpsTracking

  return {
    technicians, jobs, allTags, loading, erpStatus, jobVersion, traccarDevices,
    loadAll, loadJobsForTech, loadJobDetails,
    setJobStatus, assignJobToTech, unassignJob, createJob, reorderTechQueue, updateJobCoords, setJobSchedule, addAssistant, removeAssistant,
    smartAssign, fullUnassign, publishJobsLocal,
    pollGps, startGpsTracking, stopGpsTracking, startGpsPolling, stopGpsPolling,
    createTechnician, deleteTechnician,
  }
})
