'use strict'
const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const cfg = require('./config')
const { log, json, parseBody, lookupCustomerByPhone, createCommunication } = require('./helpers')
const erp = require('./erp')
const sse = require('./sse')

const DATA_DIR = path.join(__dirname, '..', 'data')
const CONV_FILE = path.join(DATA_DIR, 'conversations.json')
const conversations = new Map()
const pushSubscriptions = new Map()
const agentTimers = new Map()

function loadFromDisk () {
  try {
    if (!fs.existsSync(CONV_FILE)) return
    for (const conv of JSON.parse(fs.readFileSync(CONV_FILE, 'utf8'))) conversations.set(conv.token, conv)
    log(`Loaded ${conversations.size} conversations from disk`)
  } catch (e) { log('Failed to load conversations:', e.message) }
}

let saveTimer = null
function saveToDisk () {
  if (saveTimer) return
  saveTimer = setTimeout(() => {
    saveTimer = null
    try {
      if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true })
      fs.writeFileSync(CONV_FILE + '.tmp', JSON.stringify([...conversations.values()], null, 0))
      fs.renameSync(CONV_FILE + '.tmp', CONV_FILE)
    } catch (e) { log('Failed to save conversations:', e.message) }
  }, 500)
}

loadFromDisk()

function generateToken () { return crypto.randomBytes(4).toString('base64url') }

function createConversation ({ phone, customer, customerName, agentEmail, subject }) {
  const token = generateToken()
  const now = new Date().toISOString()
  const conv = {
    token, phone, customer: customer || null, customerName: customerName || 'Client',
    agentEmail: agentEmail || null, subject: subject || 'Conversation',
    status: 'active', messages: [], createdAt: now, lastActivity: now,
    expiresAt: new Date(Date.now() + 48 * 3600000).toISOString(),
    smsCount: 0, pushSub: null,
  }
  conversations.set(token, conv)
  saveToDisk()
  log(`Conversation created: ${token} for ${phone} (${customerName})`)
  return conv
}

function getConversation (token) {
  const conv = conversations.get(token)
  if (!conv) return null
  if (new Date(conv.expiresAt) < new Date()) { conv.status = 'expired'; return null }
  return conv
}

function addMessage (conv, { from, text, type = 'text', via = 'web' }) {
  const msg = { id: crypto.randomUUID(), from, text, type, via, ts: new Date().toISOString() }
  conv.messages.push(msg)
  conv.lastActivity = msg.ts
  saveToDisk()
  sse.broadcast('conv:' + conv.token, 'conv-message', { token: conv.token, message: msg })
  sse.broadcast('conv-client:' + conv.token, 'conv-message', { message: msg })
  sse.broadcast('conversations', 'conv-message', { token: conv.token, customer: conv.customer, customerName: conv.customerName, message: msg })
  return msg
}

function registerPush (token, subscription) {
  const conv = getConversation(token)
  if (!conv) return false
  conv.pushSub = subscription
  pushSubscriptions.set(token, subscription)
  saveToDisk()
  log(`Push subscription registered for conv ${token}`)
  return true
}

async function sendPushNotification (conv, message) {
  if (!conv.pushSub || !cfg.VAPID_PUBLIC_KEY || !cfg.VAPID_PRIVATE_KEY) return false
  try {
    const webpush = require('web-push')
    webpush.setVapidDetails('mailto:support@gigafibre.ca', cfg.VAPID_PUBLIC_KEY, cfg.VAPID_PRIVATE_KEY)
    await webpush.sendNotification(conv.pushSub, JSON.stringify({
      title: 'Gigafibre', body: message.text?.substring(0, 100) || 'Nouveau message',
      url: `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`, tag: 'conv-' + conv.token,
    }))
    log(`Push sent for conv ${conv.token}`)
    return true
  } catch (e) {
    log(`Push failed for conv ${conv.token}:`, e.message)
    if (e.statusCode === 410 || e.statusCode === 404) {
      conv.pushSub = null; pushSubscriptions.delete(conv.token); saveToDisk()
    }
    return false
  }
}

async function sendSmsFallback (conv, text) {
  if (conv.smsCount >= 2) { log(`SMS limit reached for conv ${conv.token}, skipping`); return false }
  const { sendSmsInternal } = require('./twilio')
  const success = await sendSmsInternal(conv.phone, text, conv.customer)
  if (success) { conv.smsCount++; saveToDisk() }
  return success
}

async function notifyCustomer (conv, message) {
  const pushed = await sendPushNotification(conv, message)
  if (pushed) return 'push'
  const link = `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`
  const smsText = `${message.text?.substring(0, 100) || 'Nouveau message'}\n\nRépondre: ${link}`
  return (await sendSmsFallback(conv, smsText)) ? 'sms' : 'none'
}

function findConversationByPhone (phone) {
  const digits = phone.replace(/\D/g, '').slice(-10)
  for (const [, conv] of conversations) {
    if (conv.status !== 'active') continue
    if (conv.phone.replace(/\D/g, '').slice(-10) === digits) return conv
  }
  return null
}

async function autoLinkIncomingSms (phone, text) {
  const existing = findConversationByPhone(phone)
  if (existing) return existing

  const customer = await lookupCustomerByPhone(phone)
  const { sendSmsInternal } = require('./twilio')

  if (!customer) {
    const conv = createConversation({ phone, customer: null, customerName: phone, agentEmail: null, subject: 'SMS entrant (inconnu)' })
    conv.smsCount = 0
    addMessage(conv, { from: 'customer', text, via: 'sms' })
    const link = `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`
    const visibleText = 'Bonjour, merci pour votre message! Un agent Gigafibre vous répondra sous peu.'
    // Always add the greeting to the conversation (even if SMS fails)
    addMessage(conv, { from: 'agent', text: visibleText, via: 'sms' })
    sendSmsInternal(phone, `${visibleText}\n\nPour continuer en ligne:\n${link}`, null).then(sid => {
      if (sid) conv.smsCount++
    }).catch(e => log('Auto-reply SMS error:', e.message))
    log(`Auto-created conv ${conv.token} for unknown caller ${phone}`)
    return conv
  }

  const conv = createConversation({
    phone, customer: customer.name, customerName: customer.customer_name || customer.name,
    agentEmail: null, subject: 'SMS entrant',
  })
  conv.smsCount = 0
  addMessage(conv, { from: 'customer', text, via: 'sms' })
  const link = `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`
  const firstName = (customer.customer_name || '').split(' ')[0] || ''
  const greeting = firstName ? `Bonjour ${firstName}, merci` : 'Bonjour, merci'
  const visibleText = `${greeting} pour votre message! Un agent vous répondra sous peu.`
  // Always add the greeting to the conversation (even if SMS fails)
  addMessage(conv, { from: 'agent', text: visibleText, via: 'sms' })
  sendSmsInternal(phone, `${visibleText}\n\nPour continuer la conversation en ligne:\n${link}`, customer.name).then(sid => {
    if (sid) {
      conv.smsCount++
      log(`Auto-reply sent to ${phone} (${sid})`)
    }
  }).catch(e => log('Auto-reply SMS error:', e.message))
  log(`Auto-linked SMS from ${phone} → customer ${customer.name}, conv ${conv.token}`)
  return conv
}

function triggerAgent (conv) {
  if (agentTimers.has(conv.token)) clearTimeout(agentTimers.get(conv.token))
  agentTimers.set(conv.token, setTimeout(() => {
    agentTimers.delete(conv.token)
    runAgentForConv(conv)
  }, 1500))
}

async function runAgentForConv (conv) {
  if (!cfg.AI_API_KEY || !conv.customer || conv.status !== 'active') return
  sse.broadcast('conv-client:' + conv.token, 'conv-typing', { typing: true })
  try {
    const { runAgent } = require('./agent')
    const recentMessages = conv.messages.slice(-20).filter(m => m.from === 'customer' || m.from === 'agent')
    const reply = await runAgent(conv.customer, conv.customerName, recentMessages, { token: conv.token })
    if (reply && conv.status === 'active') {
      addMessage(conv, { from: 'agent', text: reply, type: 'text', via: 'ai' })

      // If the last customer message came via SMS, send AI reply back via SMS + append web chat link
      const lastCustomerMsg = [...conv.messages].reverse().find(m => m.from === 'customer')
      if (lastCustomerMsg?.via === 'sms' && conv.phone) {
        const chatLink = `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`
        const smsBody = `${reply}\n\nContinuer en ligne: ${chatLink}`
        const { sendSmsInternal } = require('./twilio')
        sendSmsInternal(conv.phone, smsBody, conv.customer).then(sid => {
          if (sid) { conv.smsCount = (conv.smsCount || 0) + 1; log(`AI SMS reply sent to ${conv.phone} (${sid})`) }
        }).catch(e => log('AI SMS reply error:', e.message))
      }

      createCommunication({
        communication_type: 'Communication', communication_medium: 'Chat',
        sent_or_received: 'Sent', sender: 'ai@gigafibre.ca', sender_full_name: 'Gigafibre Assistant',
        phone_no: conv.phone, content: reply, subject: `Chat AI: ${conv.subject}`,
        reference_doctype: 'Customer', reference_name: conv.customer, status: 'Linked',
      }).catch(() => {})
    }
  } catch (e) { log('Agent error for conv ' + conv.token + ':', e.message) }
  sse.broadcast('conv-client:' + conv.token, 'conv-typing', { typing: false })
}

function listConversations ({ includeAll = false } = {}) {
  const out = []
  for (const [, conv] of conversations) {
    const expired = new Date(conv.expiresAt) < new Date()
    if (!includeAll && (conv.status !== 'active' || expired)) continue
    out.push({
      token: conv.token, phone: conv.phone, customer: conv.customer,
      customerName: conv.customerName, agentEmail: conv.agentEmail, subject: conv.subject,
      status: expired ? 'expired' : conv.status, messageCount: conv.messages.length,
      lastActivity: conv.lastActivity, createdAt: conv.createdAt,
      lastMessage: conv.messages.length ? conv.messages[conv.messages.length - 1] : null,
    })
  }
  out.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity))

  const discussions = {}
  for (const c of out) {
    const phoneKey = c.phone.replace(/\D/g, '').slice(-10)
    const key = c.status === 'active' ? `${phoneKey}:active` : `${phoneKey}:${c.createdAt.slice(0, 10)}`
    if (!discussions[key]) {
      const day = c.status === 'active' ? c.lastActivity.slice(0, 10) : c.createdAt.slice(0, 10)
      discussions[key] = {
        id: key, phone: c.phone, customer: c.customer, customerName: c.customerName,
        date: day, conversations: [], messages: [], lastActivity: c.lastActivity, status: 'closed',
      }
    }
    discussions[key].conversations.push(c)
    if (c.status === 'active') discussions[key].status = 'active'
    if (c.lastActivity > discussions[key].lastActivity) {
      discussions[key].lastActivity = c.lastActivity
      if (discussions[key].status === 'active') discussions[key].date = c.lastActivity.slice(0, 10)
    }
  }

  for (const d of Object.values(discussions)) {
    const allMsgs = []
    for (const c of d.conversations) {
      const conv = conversations.get(c.token)
      if (!conv) continue
      for (const m of conv.messages) allMsgs.push({ ...m, convToken: c.token })
    }
    allMsgs.sort((a, b) => a.ts.localeCompare(b.ts))
    d.messages = allMsgs
    d.messageCount = allMsgs.length
    d.lastMessage = allMsgs.length ? allMsgs[allMsgs.length - 1] : null
  }

  return { conversations: out, discussions: Object.values(discussions).sort((a, b) => b.lastActivity.localeCompare(a.lastActivity)) }
}

function deleteConversation (token) {
  const existed = conversations.delete(token)
  if (existed) saveToDisk()
  return existed
}

function deleteDiscussionByTokens (tokens) {
  let count = 0
  for (const t of tokens) if (conversations.delete(t)) count++
  if (count) saveToDisk()
  return count
}

function deleteDiscussion (phone, date) {
  const phoneDigits = phone.replace(/\D/g, '').slice(-10)
  const tokensToDelete = []
  for (const [token, conv] of conversations) {
    if (conv.phone.replace(/\D/g, '').slice(-10) !== phoneDigits) continue
    if (date === 'active') { if (conv.status === 'active') tokensToDelete.push(token) }
    else if (conv.createdAt.slice(0, 10) === date && conv.status !== 'active') tokensToDelete.push(token)
  }
  for (const t of tokensToDelete) conversations.delete(t)
  if (tokensToDelete.length) saveToDisk()
  return tokensToDelete.length
}

async function archiveDiscussion (tokens) {
  const allMsgs = []
  let customer = null, customerName = null, phone = null, subject = null
  for (const token of tokens) {
    const conv = conversations.get(token)
    if (!conv) continue
    if (!customer && conv.customer) customer = conv.customer
    if (!customerName && conv.customerName) customerName = conv.customerName
    if (!phone) phone = conv.phone
    if (!subject && conv.subject) subject = conv.subject
    for (const m of conv.messages) allMsgs.push(m)
  }
  if (!allMsgs.length) return null

  // If no customer linked, try to lookup by phone number
  if (!customer && phone) {
    try {
      const found = await lookupCustomerByPhone(phone)
      if (found) {
        customer = found.name
        if (!customerName || customerName === phone) customerName = found.customer_name || found.name
      }
    } catch {}
  }

  allMsgs.sort((a, b) => a.ts.localeCompare(b.ts))

  const lines = allMsgs.map(m => {
    if (m.from === 'system') return `--- ${m.text} ---`
    const sender = m.from === 'customer' ? (customerName || phone || 'Client')
      : m.via === 'ai' ? 'AI Assistant' : 'Agent'
    const time = new Date(m.ts).toLocaleString('fr-CA', { dateStyle: 'short', timeStyle: 'short' })
    const via = m.via === 'sms' ? ' [SMS]' : m.via === 'ai' ? ' [AI]' : ''
    return `**${sender}**${via} (${time}):\n${m.text}`
  })

  const issueSubject = `[Archivé] ${subject || 'Discussion'} — ${customerName || phone}`
  const issueData = {
    subject: issueSubject, status: 'Closed', priority: 'Low', issue_type: 'SMS/Chat',
    raised_by: phone || '', description: lines.join('\n\n'),
  }
  // Only set customer if we have a valid one — avoid sending null/undefined to ERPNext
  if (customer) issueData.customer = customer

  try {
    const result = await erp.create('Issue', issueData)
    if (!result.ok) throw new Error(result.error || 'Issue create failed')
    const issueName = result.name
    log(`Archived ${tokens.length} conversations → Issue ${issueName} (customer: ${customer || 'none'})`)
    deleteDiscussionByTokens(tokens)
    return { issue: issueName, messagesArchived: allMsgs.length, customer: customer || null }
  } catch (e) { log('Archive error:', e.message); throw e }
}

const TK = /^\/conversations\/([A-Za-z0-9_-]{5,10})$/
const TK_SUB = /^\/conversations\/([A-Za-z0-9_-]{5,10})\//

async function handle (req, res, method, p, url) {
  if (p === '/conversations' && method === 'POST') {
    const body = await parseBody(req)
    const { phone, customer, customerName, subject } = body
    if (!phone) return json(res, 400, { error: 'Missing phone' })
    const agentEmail = req.headers['x-authentik-email'] || null
    const conv = createConversation({ phone, customer, customerName, agentEmail, subject })
    const link = `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`
    const smsText = subject
      ? `Gigafibre — ${subject}\n\nContinuez la conversation ici:\n${link}`
      : `Gigafibre — Vous avez un nouveau message.\n\nRépondre ici:\n${link}`
    try {
      const { sendSmsInternal } = require('./twilio')
      await sendSmsInternal(phone, smsText, customer)
      conv.smsCount++; saveToDisk()
    } catch (e) { log('Failed to send magic link SMS:', e.message) }
    if (customer) {
      await createCommunication({
        communication_type: 'Communication', communication_medium: 'SMS',
        sent_or_received: 'Sent', sender: 'sms@gigafibre.ca', sender_full_name: 'Targo Ops',
        phone_no: phone, content: smsText, subject: 'Conversation link sent',
        reference_doctype: 'Customer', reference_name: customer, status: 'Linked',
      }).catch(() => {})
    }
    return json(res, 201, { ok: true, token: conv.token, link })
  }

  if (p === '/conversations' && method === 'GET')
    return json(res, 200, listConversations({ includeAll: url.searchParams.get('all') === '1' }))

  if (p === '/conversations/archive' && method === 'POST') {
    const { tokens } = await parseBody(req)
    if (!tokens || !tokens.length) return json(res, 400, { error: 'Missing tokens array' })
    try {
      const result = await archiveDiscussion(tokens)
      if (!result) return json(res, 404, { error: 'No messages found' })
      sse.broadcast('conversations', 'conv-deleted', { archived: true, issue: result.issue })
      return json(res, 200, { ok: true, ...result })
    } catch (e) { return json(res, 500, { error: 'Archive failed: ' + e.message }) }
  }

  if (p === '/conversations/bulk' && method === 'DELETE') {
    const { discussions: discs } = await parseBody(req)
    if (!discs || !discs.length) return json(res, 400, { error: 'Missing discussions array' })
    let totalDeleted = 0
    for (const d of discs) {
      totalDeleted += d.tokens ? deleteDiscussionByTokens(d.tokens) : (d.phone && d.date ? deleteDiscussion(d.phone, d.date) : 0)
    }
    sse.broadcast('conversations', 'conv-deleted', { bulk: true, count: totalDeleted })
    return json(res, 200, { ok: true, deleted: totalDeleted })
  }

  if (p === '/conversations/discussion' && method === 'DELETE') {
    const { phone, date } = await parseBody(req)
    if (!phone || !date) return json(res, 400, { error: 'Missing phone or date' })
    const count = deleteDiscussion(phone, date)
    log(`Deleted discussion: ${phone} on ${date} (${count} conversations)`)
    sse.broadcast('conversations', 'conv-deleted', { phone, date, count })
    return json(res, 200, { ok: true, deleted: count })
  }

  let m
  if ((m = p.match(TK)) && method === 'GET') {
    const conv = getConversation(m[1])
    if (!conv) return json(res, 404, { error: 'Conversation not found or expired' })
    return json(res, 200, {
      token: conv.token, customerName: conv.customerName, subject: conv.subject,
      status: conv.status, messages: conv.messages, createdAt: conv.createdAt,
      vapidPublicKey: cfg.VAPID_PUBLIC_KEY || null,
    })
  }

  if ((m = p.match(TK)) && method === 'DELETE') {
    const existed = deleteConversation(m[1])
    if (!existed) return json(res, 404, { error: 'Conversation not found' })
    log(`Deleted conversation: ${m[1]}`)
    sse.broadcast('conversations', 'conv-deleted', { token: m[1] })
    return json(res, 200, { ok: true })
  }

  if ((m = p.match(TK_SUB))) {
    const sub = p.slice(p.indexOf(m[1]) + m[1].length + 1)
    const token = m[1]

    if (sub === 'messages' && method === 'POST') {
      const conv = getConversation(token)
      if (!conv) return json(res, 404, { error: 'Conversation not found or expired' })
      const { text } = await parseBody(req)
      if (!text || !text.trim()) return json(res, 400, { error: 'Missing text' })
      const agentEmail = req.headers['x-authentik-email']
      const from = agentEmail ? 'agent' : 'customer'
      const msg = addMessage(conv, { from, text: text.trim(), via: 'web' })
      if (from === 'agent') msg.notifiedVia = await notifyCustomer(conv, msg)
      if (from === 'customer' && conv.customer) triggerAgent(conv)
      if (conv.customer) {
        createCommunication({
          communication_type: 'Communication', communication_medium: 'Chat',
          sent_or_received: from === 'agent' ? 'Sent' : 'Received',
          sender: from === 'agent' ? (agentEmail || 'sms@gigafibre.ca') : conv.phone,
          sender_full_name: from === 'agent' ? 'Targo Ops' : conv.customerName,
          phone_no: conv.phone, content: text.trim(), subject: `Chat: ${conv.subject}`,
          reference_doctype: 'Customer', reference_name: conv.customer, status: 'Linked',
        }).catch(() => {})
      }
      return json(res, 201, { ok: true, message: msg })
    }

    if (sub === 'push' && method === 'POST') {
      const body = await parseBody(req)
      if (!body.endpoint || !body.keys) return json(res, 400, { error: 'Invalid push subscription' })
      const ok = registerPush(token, body)
      return json(res, ok ? 200 : 404, ok ? { ok: true } : { error: 'Conversation not found' })
    }

    if (sub === 'close' && method === 'POST') {
      const conv = getConversation(token)
      if (!conv) return json(res, 404, { error: 'Conversation not found' })
      conv.status = 'closed'
      addMessage(conv, { from: 'system', text: 'Conversation terminée', via: 'web' })
      saveToDisk()
      sse.broadcast('conversations', 'conv-closed', { token: conv.token })
      return json(res, 200, { ok: true })
    }

    if (sub === 'sse' && method === 'GET') {
      const conv = getConversation(token)
      if (!conv) return json(res, 404, { error: 'Conversation not found' })
      res.writeHead(200, {
        'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache',
        Connection: 'keep-alive', 'X-Accel-Buffering': 'no',
      })
      res.write(': connected\n\n')
      sse.addClient(['conv-client:' + conv.token], res, 'customer:' + conv.phone)
      const keepalive = setInterval(() => { try { res.write(': ping\n\n') } catch { clearInterval(keepalive) } }, 25000)
      res.on('close', () => clearInterval(keepalive))
      return
    }
  }

  return json(res, 404, { error: 'Not found' })
}

module.exports = {
  handle,
  createConversation,
  getConversation,
  addMessage,
  findConversationByPhone,
  autoLinkIncomingSms,
  triggerAgent,
  notifyCustomer,
  deleteConversation,
  deleteDiscussion,
  archiveDiscussion,
}
