import { getDb } from '../db/client.js'
import { publish } from '../plugins/valkey.js'
import { sendToSessionParticipant } from '../plugins/websocket.js'
import { sendPushNotification } from './notification.service.js'
import { UserType, SessionStatus, WsMessage, EndedBy } from '../constants.js'

const sql = getDb()

// Active session timers: sessionId → { warningTimeout, expiryTimeout }
const sessionTimers = new Map()

export const startSessionTimer = (sessionId, expiresAt) => {
  const now = Date.now()
  const expiresMs = new Date(expiresAt).getTime()
  const warningMs = expiresMs - 60_000 // 1 minute before expiry

  // Clear any existing timers
  clearSessionTimer(sessionId)

  const timers = {}

  // Warning timer (1 min before expiry)
  if (warningMs > now) {
    timers.warningTimeout = setTimeout(() => {
      onSessionWarning(sessionId)
    }, warningMs - now)
  }

  // Expiry timer
  if (expiresMs > now) {
    timers.expiryTimeout = setTimeout(() => {
      onSessionExpired(sessionId)
    }, expiresMs - now)
  } else {
    // Already expired
    onSessionExpired(sessionId)
    return
  }

  sessionTimers.set(sessionId, timers)
}

export const clearSessionTimer = (sessionId) => {
  const timers = sessionTimers.get(sessionId)
  if (timers) {
    if (timers.warningTimeout) clearTimeout(timers.warningTimeout)
    if (timers.expiryTimeout) clearTimeout(timers.expiryTimeout)
    sessionTimers.delete(sessionId)
  }
}

export const extendSessionTimer = async (sessionId, additionalMinutes) => {
  const [session] = await sql`
    UPDATE chat_sessions
    SET expires_at = expires_at + ${additionalMinutes + ' minutes'}::interval,
        extended_minutes = extended_minutes + ${additionalMinutes}
    WHERE id = ${sessionId}
    RETURNING id, expires_at
  `
  if (session) {
    startSessionTimer(sessionId, session.expires_at)
  }
  return session
}

const onSessionWarning = (sessionId) => {
  const data = { type: WsMessage.SESSION_TIMER, remaining_seconds: 60, session_id: sessionId }
  sendToSessionParticipant(sessionId, UserType.CUSTOMER, data)
  sendToSessionParticipant(sessionId, UserType.MITRA, data)
}

// Grace period timers for auto-completing abandoned sessions
const closureGraceTimers = new Map()

const CLOSURE_GRACE_PERIOD_MS = 5 * 60_000 // 5 minutes

const onSessionExpired = async (sessionId) => {
  clearSessionTimer(sessionId)

  // Move session to closing status
  const [session] = await sql`
    UPDATE chat_sessions
    SET status = ${SessionStatus.CLOSING}
    WHERE id = ${sessionId} AND status = ${SessionStatus.ACTIVE}
    RETURNING id, customer_id, mitra_id
  `
  if (!session) return

  // Notify customer — sees extend/close dialog; FCM fallback if WebSocket is down
  const expiredData = { type: WsMessage.SESSION_EXPIRED, session_id: sessionId }
  const customerSent = sendToSessionParticipant(sessionId, UserType.CUSTOMER, expiredData)
  if (!customerSent) {
    await sendPushNotification(UserType.CUSTOMER, session.customer_id, {
      title: 'Waktu Sesi Habis',
      body: 'Sesi curhat kamu telah habis. Ketuk untuk memperpanjang atau mengakhiri.',
      data: { type: WsMessage.SESSION_CLOSING, session_id: sessionId },
    })
  }

  // Notify mitra — sees expired + closing (waits for customer's decision or goodbye)
  const mitraSent = sendToSessionParticipant(sessionId, UserType.MITRA, expiredData)
  sendToSessionParticipant(sessionId, UserType.MITRA, {
    type: WsMessage.SESSION_CLOSING, session_id: sessionId,
  })
  if (!mitraSent) {
    await sendPushNotification(UserType.MITRA, session.mitra_id, {
      title: 'Sesi Berakhir',
      body: 'Sesi curhat telah berakhir. Ketuk untuk menulis pesan penutup.',
      data: { type: WsMessage.SESSION_CLOSING, session_id: sessionId },
    })
  }

  // Also publish via Valkey for any listeners
  await publish(`session:${sessionId}:status`, expiredData)

  // Start grace period — auto-complete if closing messages aren't submitted
  startClosureGraceTimer(sessionId)
}

// Schedule auto-completion if session stays in `closing` past the grace window.
// Idempotent: clears any existing grace timer before scheduling a new one.
export const startClosureGraceTimer = (sessionId) => {
  clearClosureGraceTimer(sessionId)
  const graceTimerId = setTimeout(() => autoCompleteIfStillClosing(sessionId), CLOSURE_GRACE_PERIOD_MS)
  closureGraceTimers.set(sessionId, graceTimerId)
}

const autoCompleteIfStillClosing = async (sessionId) => {
  closureGraceTimers.delete(sessionId)
  try {
    const [stale] = await sql`
      SELECT id FROM chat_sessions
      WHERE id = ${sessionId} AND status = ${SessionStatus.CLOSING}
    `
    if (!stale) return // customer submitted goodbye, or another path already finalized

    const [updated] = await sql`
      UPDATE chat_sessions
      SET status = ${SessionStatus.COMPLETED},
          ended_at = COALESCE(ended_at, NOW()),
          ended_by = ${EndedBy.SYSTEM_AUTO_CLOSE}
      WHERE id = ${sessionId} AND status = ${SessionStatus.CLOSING}
      RETURNING id
    `
    if (!updated) return

    const data = { type: WsMessage.SESSION_COMPLETED, session_id: sessionId }
    sendToSessionParticipant(sessionId, UserType.CUSTOMER, data)
    sendToSessionParticipant(sessionId, UserType.MITRA, data)
    await publish(`session:${sessionId}:status`, data)
    console.log(`Auto-completed abandoned session ${sessionId} after grace period`)
  } catch (err) {
    console.error(`Closure grace auto-complete failed for ${sessionId}:`, err)
  }
}

export const clearClosureGraceTimer = (sessionId) => {
  const timerId = closureGraceTimers.get(sessionId)
  if (timerId) {
    clearTimeout(timerId)
    closureGraceTimers.delete(sessionId)
  }
}

// Restore timers for active sessions on server restart
export const restoreActiveTimers = async () => {
  // Expire sessions that already passed their expires_at while server was down
  const staleSessions = await sql`
    UPDATE chat_sessions
    SET status = ${SessionStatus.COMPLETED}, ended_at = expires_at, ended_by = 'system'
    WHERE status = ${SessionStatus.ACTIVE} AND expires_at IS NOT NULL AND expires_at <= NOW()
    RETURNING id
  `
  if (staleSessions.length > 0) {
    console.log(`Auto-completed ${staleSessions.length} expired session(s)`)
  }

  // Recover pending closure timers — any session still in `closing` after restart
  // has lost its in-memory grace setTimeout. We can't know how long it has been
  // closing for, so finalize them all to system_auto_close.
  const staleClosing = await sql`
    UPDATE chat_sessions
    SET status = ${SessionStatus.COMPLETED},
        ended_at = COALESCE(ended_at, NOW()),
        ended_by = ${EndedBy.SYSTEM_AUTO_CLOSE}
    WHERE status = ${SessionStatus.CLOSING}
    RETURNING id
  `
  if (staleClosing.length > 0) {
    console.log(`Auto-completed ${staleClosing.length} abandoned closing session(s)`)
  }

  // Restore timers for sessions still within their time window
  const activeSessions = await sql`
    SELECT id, expires_at FROM chat_sessions
    WHERE status = ${SessionStatus.ACTIVE} AND expires_at IS NOT NULL AND expires_at > NOW()
  `
  for (const session of activeSessions) {
    startSessionTimer(session.id, session.expires_at)
  }
  if (activeSessions.length > 0) {
    console.log(`Restored ${activeSessions.length} session timer(s)`)
  }
}
