import { getDb } from '../db/client.js'
import { SessionStatus } from '../constants.js'
import { getMitraPingConfig, getMaxCustomersPerMitra } from './config.service.js'
import { subscribe } from '../plugins/valkey.js'

const sql = getDb()

// --- Short-TTL availability cache for the 5s-poll endpoint ---
// In-memory snapshot { available, count, expiresAt }. The cache:
//   - is recomputed at most once per AVAILABILITY_TTL_MS (10s backstop)
//   - is invalidated explicitly when CC changes max_customers_per_mitra (call invalidateAvailabilityCache())
// This keeps customer polls off the DB hot path while staying close to real time.
const AVAILABILITY_TTL_MS = 10_000
let availabilityCache = null // { available, count, expiresAt }

export const invalidateAvailabilityCache = () => {
  availabilityCache = null
}

// Subscribe once at module load so other-instance config updates also bust this cache.
// Single-instance: the local mutator already invalidates, so this is a no-op extra.
let _subscribed = false
const ensureSubscribed = () => {
  if (_subscribed) return
  _subscribed = true
  try {
    subscribe('config:invalidate', (msg) => {
      if (msg?.key === 'max_customers_per_mitra') {
        invalidateAvailabilityCache()
      }
    })
  } catch (_) {
    // Valkey may not be reachable in some test contexts; non-fatal.
  }
}
ensureSubscribed()

export const ensureStatusRow = async (mitraId) => {
  await sql`
    INSERT INTO mitra_online_status (mitra_id)
    VALUES (${mitraId})
    ON CONFLICT (mitra_id) DO NOTHING
  `
}

export const setOnline = async (mitraId) => {
  await ensureStatusRow(mitraId)
  const now = new Date()
  await sql`
    UPDATE mitra_online_status
    SET is_online = true, last_online_at = ${now}, last_heartbeat_at = ${now}, updated_at = ${now}
    WHERE mitra_id = ${mitraId}
  `
  await sql`
    INSERT INTO mitra_online_logs (mitra_id, status) VALUES (${mitraId}, 'online')
  `
  invalidateAvailabilityCache()
}

export const setOffline = async (mitraId) => {
  await ensureStatusRow(mitraId)
  const now = new Date()
  const [status] = await sql`
    SELECT is_online FROM mitra_online_status WHERE mitra_id = ${mitraId}
  `
  if (!status?.is_online) return

  await sql`
    UPDATE mitra_online_status
    SET is_online = false, last_offline_at = ${now}, updated_at = ${now}
    WHERE mitra_id = ${mitraId}
  `
  await sql`
    INSERT INTO mitra_online_logs (mitra_id, status) VALUES (${mitraId}, 'offline')
  `
  invalidateAvailabilityCache()
}

export const heartbeat = async (mitraId) => {
  const now = new Date()
  await sql`
    UPDATE mitra_online_status
    SET last_heartbeat_at = ${now}, updated_at = ${now}
    WHERE mitra_id = ${mitraId} AND is_online = true
  `
}

export const getStatus = async (mitraId) => {
  await ensureStatusRow(mitraId)
  const [status] = await sql`
    SELECT is_online, last_online_at, last_offline_at, updated_at
    FROM mitra_online_status
    WHERE mitra_id = ${mitraId}
  `
  const pingConfig = await getMitraPingConfig()
  return {
    ...status,
    require_ping: pingConfig.require_ping,
    ping_interval_seconds: pingConfig.ping_interval_seconds,
  }
}

export const getOnlineMitras = async () => {
  const mitras = await sql`
    SELECT m.id, m.display_name, m.phone, s.last_online_at, s.updated_at,
      (SELECT COUNT(*) FROM chat_sessions cs
       WHERE cs.mitra_id = m.id AND cs.status IN (${SessionStatus.ACTIVE}, ${SessionStatus.PENDING_PAYMENT})) AS active_session_count
    FROM mitras m
    INNER JOIN mitra_online_status s ON s.mitra_id = m.id
    WHERE s.is_online = true AND m.is_active = true
    ORDER BY s.last_online_at DESC
  `
  return mitras
}

export const getOnlineLogs = async (mitraId, { page = 1, limit = 50 } = {}) => {
  const offset = (page - 1) * limit
  const items = await sql`
    SELECT id, status, timestamp
    FROM mitra_online_logs
    WHERE mitra_id = ${mitraId}
    ORDER BY timestamp DESC
    LIMIT ${limit} OFFSET ${offset}
  `
  const [{ count }] = await sql`
    SELECT COUNT(*) FROM mitra_online_logs WHERE mitra_id = ${mitraId}
  `
  return { items, total: Number(count), page, limit }
}

export const autoOfflineStaleMitras = async () => {
  const pingConfig = await getMitraPingConfig()

  // If ping is not required, skip the auto-offline sweep entirely
  if (!pingConfig.require_ping) return 0

  const staleSeconds = pingConfig.ping_interval_seconds * 3
  const stale = await sql`
    UPDATE mitra_online_status
    SET is_online = false, last_offline_at = NOW(), updated_at = NOW()
    WHERE is_online = true
      AND last_heartbeat_at < NOW() - ${staleSeconds + ' seconds'}::interval
    RETURNING mitra_id
  `

  for (const row of stale) {
    await sql`
      INSERT INTO mitra_online_logs (mitra_id, status) VALUES (${row.mitra_id}, 'offline')
    `
  }

  // Capacity may have changed (mitra went offline) — invalidate the customer-facing
  // availability cache so the next poll reflects reality.
  if (stale.length > 0) invalidateAvailabilityCache()

  return stale.length
}

/**
 * Customer-home availability check, cached in-memory for AVAILABILITY_TTL_MS.
 *
 * Returns { available, count } where:
 *   - available = true iff at least one mitra is online AND below max_customers_per_mitra
 *   - count is the number of qualifying mitras (CC/debug only — never expose to customer UI)
 *
 * The 5s-poll endpoint backed by this function MUST NOT issue per-poll DB queries.
 * The 10s TTL caps DB load to ~6 queries/min total regardless of poller count.
 *
 * Note: today the source of truth for online status + active session counts is Postgres
 * (mitra_online_status + chat_sessions). A future refactor can mirror these into Valkey
 * sets/hashes (matching the existing memory item "Session Timer Scaling"); the contract
 * of this function — Valkey/cache reads only on the hot path — stays the same.
 */
export const countAvailableMitrasFromCache = async () => {
  const now = Date.now()
  if (availabilityCache && availabilityCache.expiresAt > now) {
    return { available: availabilityCache.available, count: availabilityCache.count }
  }

  const { max_customers_per_mitra } = await getMaxCustomersPerMitra()
  const [{ count }] = await sql`
    SELECT COUNT(*)::int AS count
    FROM mitras m
    INNER JOIN mitra_online_status s ON s.mitra_id = m.id
    WHERE m.is_active = true
      AND s.is_online = true
      AND (
        SELECT COUNT(*) FROM chat_sessions cs
        WHERE cs.mitra_id = m.id
          AND cs.status IN (${SessionStatus.ACTIVE}, ${SessionStatus.PENDING_PAYMENT})
      ) < ${max_customers_per_mitra}
  `

  const available = count > 0
  availabilityCache = {
    available,
    count,
    expiresAt: now + AVAILABILITY_TTL_MS,
  }
  return { available, count }
}

/**
 * Mitra-online check for use during pairing/extension safeguards.
 * Combines the Valkey-mirrored online flag (Postgres mitra_online_status today) with
 * the WebSocket-connected check. Never use "in-session" as a proxy for "online".
 */
export const isMitraReachable = async (mitraId) => {
  const [row] = await sql`
    SELECT is_online FROM mitra_online_status WHERE mitra_id = ${mitraId}
  `
  return Boolean(row?.is_online)
}

/**
 * Returns active session count for a mitra (sessions that count toward max_customers_per_mitra).
 */
export const getMitraActiveSessionCount = async (mitraId) => {
  const [{ count }] = await sql`
    SELECT COUNT(*)::int AS count FROM chat_sessions
    WHERE mitra_id = ${mitraId}
      AND status IN (${SessionStatus.ACTIVE}, ${SessionStatus.PENDING_PAYMENT})
  `
  return count
}

/**
 * True iff this mitra is currently in an ACTIVE chat with this specific customer.
 * Used by targeted "Curhat lagi" pre-check: a mitra at-capacity but mid-session
 * with the requesting customer is still allowed to receive a returning-chat card.
 */
export const isMitraInActiveSessionWithCustomer = async (mitraId, customerId) => {
  const [row] = await sql`
    SELECT id FROM chat_sessions
    WHERE mitra_id = ${mitraId}
      AND customer_id = ${customerId}
      AND status IN (${SessionStatus.ACTIVE}, ${SessionStatus.EXTENDING}, ${SessionStatus.CLOSING})
    LIMIT 1
  `
  return Boolean(row)
}
