import { ConvexError, v } from "convex/values"

import type { Doc, Id } from "./_generated/dataModel"
import { query } from "./_generated/server"
import type { QueryCtx } from "./_generated/server"
import {
  OPEN_STATUSES,
  ONE_DAY_MS,
  fetchOpenScopedTickets,
  fetchScopedTicketsByCreatedRange,
  fetchScopedTicketsByResolvedRange,
  fetchScopedTicketsByResolvedRangeSnapshot,
  normalizeStatus,
} from "./reports"
import { requireStaff } from "./rbac"

type Viewer = Awaited<ReturnType<typeof requireStaff>>

type MetricResolverInput = {
  tenantId: string
  viewer: Viewer
  viewerId: Id<"users">
  params?: Record<string, unknown> | undefined
}

type MetricRunPayload = {
  meta: { kind: string; key: string } & Record<string, unknown>
  data: unknown
}

type MetricResolver = (ctx: QueryCtx, input: MetricResolverInput) => Promise<MetricRunPayload>

function parseRange(params?: Record<string, unknown>): number {
  const value = params?.range
  if (typeof value === "string") {
    const normalized = value.toLowerCase()
    if (normalized === "7d") return 7
    if (normalized === "90d") return 90
  }
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
    return Math.min(365, Math.max(1, Math.round(value)))
  }
  return 30
}

function parseLimit(params?: Record<string, unknown>, fallback = 20) {
  const value = params?.limit
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
    return Math.min(200, Math.round(value))
  }
  return fallback
}

function parseCompanyId(params?: Record<string, unknown>): Id<"companies"> | undefined {
  const value = params?.companyId
  if (typeof value === "string" && value.length > 0) {
    return value as Id<"companies">
  }
  return undefined
}

function parseQueueIds(params?: Record<string, unknown>): string[] | undefined {
  const value = params?.queueIds ?? params?.queueId
  if (Array.isArray(value)) {
    const clean = value
      .map((entry) => (typeof entry === "string" ? entry.trim() : null))
      .filter((entry): entry is string => Boolean(entry && entry.length > 0))
    return clean.length > 0 ? clean : undefined
  }
  if (typeof value === "string") {
    const trimmed = value.trim()
    return trimmed.length > 0 ? [trimmed] : undefined
  }
  return undefined
}

function filterTicketsByQueue<T extends { queueId?: Id<"queues"> | null }>(
  tickets: T[],
  queueIds?: string[],
): T[] {
  if (!queueIds || queueIds.length === 0) {
    return tickets
  }
  const normalized = new Set(queueIds.map((id) => id.trim()))
  const includesNull = normalized.has("sem-fila") || normalized.has("null")
  return tickets.filter((ticket) => {
    if (!ticket.queueId) {
      return includesNull
    }
    return normalized.has(String(ticket.queueId))
  })
}

const QUEUE_RENAME_LOOKUP: Record<string, string> = {
  "Suporte N1": "Chamados",
  "suporte-n1": "Chamados",
  chamados: "Chamados",
  "Suporte N2": "Laboratório",
  "suporte-n2": "Laboratório",
  laboratorio: "Laboratório",
  Laboratorio: "Laboratório",
  visitas: "Visitas",
}

function renameQueueName(value: string) {
  const direct = QUEUE_RENAME_LOOKUP[value]
  if (direct) return direct
  const normalizedKey = value.replace(/\s+/g, "-").toLowerCase()
  return QUEUE_RENAME_LOOKUP[normalizedKey] ?? value
}

type AgentStatsRaw = {
  agentId: Id<"users">
  name: string | null
  email: string | null
  open: number
  paused: number
  resolved: number
  totalSla: number
  compliantSla: number
  resolutionMinutes: number[]
  firstResponseMinutes: number[]
}

type AgentStatsComputed = {
  agentId: string
  name: string | null
  email: string | null
  open: number
  paused: number
  resolved: number
  slaRate: number | null
  avgResolutionMinutes: number | null
  avgFirstResponseMinutes: number | null
  totalSla: number
  compliantSla: number
}

function average(values: number[]): number | null {
  if (!values || values.length === 0) return null
  const sum = values.reduce((acc, value) => acc + value, 0)
  return sum / values.length
}

function isTicketCompliant(ticket: Doc<"tickets">, now: number) {
  const dueAt = typeof ticket.dueAt === "number" ? ticket.dueAt : null
  const resolvedAt = typeof ticket.resolvedAt === "number" ? ticket.resolvedAt : null
  if (dueAt) {
    if (resolvedAt) {
      return resolvedAt <= dueAt
    }
    return dueAt >= now
  }
  return resolvedAt !== null
}

function ensureAgentStats(map: Map<string, AgentStatsRaw>, ticket: Doc<"tickets">): AgentStatsRaw | null {
  const assigneeId = ticket.assigneeId
  if (!assigneeId) return null
  const key = String(assigneeId)
  let stats = map.get(key)
  const snapshot = ticket.assigneeSnapshot as { name?: string | null; email?: string | null } | undefined
  const snapshotName = snapshot?.name ?? null
  const snapshotEmail = snapshot?.email ?? null
  if (!stats) {
    stats = {
      agentId: assigneeId,
      name: snapshotName,
      email: snapshotEmail,
      open: 0,
      paused: 0,
      resolved: 0,
      totalSla: 0,
      compliantSla: 0,
      resolutionMinutes: [],
      firstResponseMinutes: [],
    }
    map.set(key, stats)
  } else {
    if (!stats.name && snapshotName) stats.name = snapshotName
    if (!stats.email && snapshotEmail) stats.email = snapshotEmail
  }
  return stats
}

async function computeAgentStats(
  ctx: QueryCtx,
  tenantId: string,
  viewer: Viewer,
  rangeDays: number,
  agentFilter?: Id<"users">,
) {
  const end = new Date()
  end.setUTCHours(0, 0, 0, 0)
  const endMs = end.getTime() + ONE_DAY_MS
  const startMs = endMs - rangeDays * ONE_DAY_MS

  const statsMap = new Map<string, AgentStatsRaw>()
  const openTickets = await fetchOpenScopedTickets(ctx, tenantId, viewer)
  const scopedTickets = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs)

  const matchesFilter = (ticket: Doc<"tickets">) => {
    if (!ticket.assigneeId) return false
    if (agentFilter && ticket.assigneeId !== agentFilter) return false
    return true
  }

  for (const ticket of openTickets) {
    if (!matchesFilter(ticket)) continue
    const stats = ensureAgentStats(statsMap, ticket)
    if (!stats) continue
    const status = normalizeStatus(ticket.status)
    if (status === "PAUSED") {
      stats.paused += 1
    } else if (OPEN_STATUSES.has(status)) {
      stats.open += 1
    }
  }

  const inRange = scopedTickets.filter((ticket) => matchesFilter(ticket))
  const now = Date.now()
  for (const ticket of inRange) {
    const stats = ensureAgentStats(statsMap, ticket)
    if (!stats) continue
    stats.totalSla += 1
    if (isTicketCompliant(ticket, now)) {
      stats.compliantSla += 1
    }
    const status = normalizeStatus(ticket.status)
    if (
      status === "RESOLVED" &&
      typeof ticket.resolvedAt === "number" &&
      ticket.resolvedAt >= startMs &&
      ticket.resolvedAt < endMs
    ) {
      stats.resolved += 1
      stats.resolutionMinutes.push((ticket.resolvedAt - ticket.createdAt) / 60000)
    }
    if (
      typeof ticket.firstResponseAt === "number" &&
      ticket.firstResponseAt >= startMs &&
      ticket.firstResponseAt < endMs
    ) {
      stats.firstResponseMinutes.push((ticket.firstResponseAt - ticket.createdAt) / 60000)
    }
  }

  const agentIds = Array.from(statsMap.keys()) as string[]
  if (agentIds.length > 0) {
    const docs = await Promise.all(agentIds.map((id) => ctx.db.get(id as Id<"users">)))
    docs.forEach((doc, index) => {
      const stats = statsMap.get(agentIds[index])
      if (!stats || !doc) return
      if (!stats.name && doc.name) stats.name = doc.name
      if (!stats.email && doc.email) stats.email = doc.email
    })
  }

  const computed = new Map<string, AgentStatsComputed>()
  for (const [key, raw] of statsMap.entries()) {
    const avgResolution = average(raw.resolutionMinutes)
    const avgFirstResponse = average(raw.firstResponseMinutes)
    const slaRate =
      raw.totalSla > 0 ? Math.min(1, Math.max(0, raw.compliantSla / raw.totalSla)) : null
    computed.set(key, {
      agentId: key,
      name: raw.name ?? raw.email ?? null,
      email: raw.email ?? null,
      open: raw.open,
      paused: raw.paused,
      resolved: raw.resolved,
      slaRate,
      avgResolutionMinutes: avgResolution,
      avgFirstResponseMinutes: avgFirstResponse,
      totalSla: raw.totalSla,
      compliantSla: raw.compliantSla,
    })
  }

  return computed
}

const metricResolvers: Record<string, MetricResolver> = {
  "tickets.opened_resolved_by_day": async (ctx, { tenantId, viewer, params }) => {
    const rangeDays = parseRange(params)
    const companyId = parseCompanyId(params)
    const queueIds = parseQueueIds(params)
    const end = new Date()
    end.setUTCHours(0, 0, 0, 0)
    const endMs = end.getTime() + ONE_DAY_MS
    const startMs = endMs - rangeDays * ONE_DAY_MS

    const openedTickets = filterTicketsByQueue(
      await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId),
      queueIds,
    )
    const resolvedTickets = filterTicketsByQueue(
      await fetchScopedTicketsByResolvedRangeSnapshot(ctx, tenantId, viewer, startMs, endMs, companyId),
      queueIds,
    )

    const opened: Record<string, number> = {}
    const resolved: Record<string, number> = {}

    for (let offset = rangeDays - 1; offset >= 0; offset -= 1) {
      const d = new Date(endMs - (offset + 1) * ONE_DAY_MS)
      const key = formatDateKey(d.getTime())
      opened[key] = 0
      resolved[key] = 0
    }

    for (const ticket of openedTickets) {
      if (ticket.createdAt >= startMs && ticket.createdAt < endMs) {
        const key = formatDateKey(ticket.createdAt)
        opened[key] = (opened[key] ?? 0) + 1
      }
    }

    for (const ticket of resolvedTickets) {
      if (typeof ticket.resolvedAt !== "number") continue
      if (ticket.resolvedAt < startMs || ticket.resolvedAt >= endMs) continue
      const key = formatDateKey(ticket.resolvedAt)
      resolved[key] = (resolved[key] ?? 0) + 1
    }

    const series = []
    for (let offset = rangeDays - 1; offset >= 0; offset -= 1) {
      const d = new Date(endMs - (offset + 1) * ONE_DAY_MS)
      const key = formatDateKey(d.getTime())
      series.push({ date: key, opened: opened[key] ?? 0, resolved: resolved[key] ?? 0 })
    }

    return {
      meta: { kind: "series", key: "tickets.opened_resolved_by_day", rangeDays },
      data: series,
    }
  },
  "tickets.waiting_action_now": async (ctx, { tenantId, viewer, params }) => {
    const tickets = filterTicketsByQueue(
      await fetchOpenScopedTickets(ctx, tenantId, viewer),
      parseQueueIds(params),
    )
    const now = Date.now()
    let total = 0
    let atRisk = 0

    for (const ticket of tickets) {
      const status = normalizeStatus(ticket.status)
      if (!OPEN_STATUSES.has(status)) continue
      total += 1
      if (ticket.dueAt && ticket.dueAt < now) {
        atRisk += 1
      }
    }

    return {
      meta: { kind: "single", key: "tickets.waiting_action_now", unit: "tickets" },
      data: { value: total, atRisk },
    }
  },
  "tickets.waiting_action_last_7d": async (ctx, { tenantId, viewer, params }) => {
    const rangeDays = 7
    const end = new Date()
    end.setUTCHours(0, 0, 0, 0)
    const endMs = end.getTime() + ONE_DAY_MS
    const startMs = endMs - rangeDays * ONE_DAY_MS
    const tickets = filterTicketsByQueue(
      await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs),
      parseQueueIds(params),
    )

    const daily: Record<string, { total: number; atRisk: number }> = {}
    for (let offset = rangeDays - 1; offset >= 0; offset -= 1) {
      const d = new Date(endMs - (offset + 1) * ONE_DAY_MS)
      const key = formatDateKey(d.getTime())
      daily[key] = { total: 0, atRisk: 0 }
    }

    for (const ticket of tickets) {
      if (ticket.createdAt < startMs) continue
      const key = formatDateKey(ticket.createdAt)
      const bucket = daily[key]
      if (!bucket) continue
      if (OPEN_STATUSES.has(normalizeStatus(ticket.status))) {
        bucket.total += 1
        if (ticket.dueAt && ticket.dueAt < Date.now()) {
          bucket.atRisk += 1
        }
      }
    }

    const values = Object.values(daily)
    const total = values.reduce((sum, item) => sum + item.total, 0)
    const atRisk = values.reduce((sum, item) => sum + item.atRisk, 0)

    return {
      meta: { kind: "single", key: "tickets.waiting_action_last_7d", aggregation: "sum", rangeDays },
      data: { value: total, atRisk },
    }
  },
  "tickets.open_by_priority": async (ctx, { tenantId, viewer, params }) => {
    const rangeDays = parseRange(params)
    const companyId = parseCompanyId(params)
    const end = new Date()
    end.setUTCHours(0, 0, 0, 0)
    const endMs = end.getTime() + ONE_DAY_MS
    const startMs = endMs - rangeDays * ONE_DAY_MS
    const tickets = filterTicketsByQueue(
      await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId),
      parseQueueIds(params),
    )

    const counts: Record<string, number> = {}
    for (const ticket of tickets) {
      if (!OPEN_STATUSES.has(normalizeStatus(ticket.status))) continue
      const key = (ticket.priority ?? "MEDIUM").toUpperCase()
      counts[key] = (counts[key] ?? 0) + 1
    }

    const data = Object.entries(counts).map(([priority, total]) => ({ priority, total }))
    data.sort((a, b) => b.total - a.total)

    return {
      meta: { kind: "collection", key: "tickets.open_by_priority", rangeDays },
      data,
    }
  },
  "tickets.open_by_queue": async (ctx, { tenantId, viewer, params }) => {
    const rangeDays = parseRange(params)
    const companyId = parseCompanyId(params)
    const end = new Date()
    end.setUTCHours(0, 0, 0, 0)
    const endMs = end.getTime() + ONE_DAY_MS
    const startMs = endMs - rangeDays * ONE_DAY_MS
    const queueFilter = parseQueueIds(params)
    const tickets = filterTicketsByQueue(
      await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId),
      parseQueueIds(params),
    )

    const queueCounts = new Map<string, number>()
    for (const ticket of tickets) {
      if (!OPEN_STATUSES.has(normalizeStatus(ticket.status))) continue
      const queueKey = ticket.queueId ? String(ticket.queueId) : "sem-fila"
      if (queueFilter && queueFilter.length > 0 && !queueFilter.includes(queueKey)) {
        continue
      }
      queueCounts.set(queueKey, (queueCounts.get(queueKey) ?? 0) + 1)
    }

    const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50)
    const data = Array.from(queueCounts.entries()).map(([queueId, total]) => {
      const queue = queues.find((q) => String(q._id) === queueId)
      return {
        queueId,
        name: queue ? queue.name : queueId === "sem-fila" ? "Sem fila" : "Fila desconhecida",
        total,
      }
    })

    data.sort((a, b) => b.total - a.total)

    return {
      meta: { kind: "collection", key: "tickets.open_by_queue", rangeDays },
      data,
    }
  },
  "queues.summary_cards": async (ctx, { tenantId, viewer, params }) => {
    const queueFilter = parseQueueIds(params)
    const filterHas = queueFilter && queueFilter.length > 0
    const normalizeKey = (id: Id<"queues"> | null) => (id ? String(id) : "sem-fila")

    const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50)
    const queueNameMap = new Map<string, string>()
    queues.forEach((queue) => {
      const key = String(queue._id)
      queueNameMap.set(key, renameQueueName(queue.name))
    })

    const now = Date.now()
    const stats = new Map<
      string,
      { id: string; name: string; pending: number; inProgress: number; paused: number; breached: number }
    >()

    const ensureEntry = (key: string, fallbackName?: string) => {
      if (!stats.has(key)) {
        const resolvedName =
          queueNameMap.get(key) ??
          (key === "sem-fila" ? "Sem fila" : fallbackName ?? "Fila desconhecida")
        stats.set(key, {
          id: key,
          name: resolvedName,
          pending: 0,
          inProgress: 0,
          paused: 0,
          breached: 0,
        })
      }
      return stats.get(key)!
    }

    for (const queue of queues) {
      const key = String(queue._id)
      if (filterHas && queueFilter && !queueFilter.includes(key)) continue
      ensureEntry(key)
    }

    const scopedTickets = await fetchOpenScopedTickets(ctx, tenantId, viewer)
    for (const ticket of scopedTickets) {
      const key = normalizeKey(ticket.queueId ?? null)
      if (filterHas && queueFilter && !queueFilter.includes(key)) continue
      const entry = ensureEntry(key)
      const status = normalizeStatus(ticket.status)
      if (status === "PENDING") {
        entry.pending += 1
      } else if (status === "AWAITING_ATTENDANCE") {
        entry.inProgress += 1
      } else if (status === "PAUSED") {
        entry.paused += 1
      }
      if (status !== "RESOLVED") {
        const dueAt = typeof ticket.dueAt === "number" ? ticket.dueAt : null
        if (dueAt && dueAt < now) {
          entry.breached += 1
        }
      }
    }

    if (!(filterHas && queueFilter && !queueFilter.includes("sem-fila"))) {
      ensureEntry("sem-fila", "Sem fila")
    } else if (filterHas) {
      stats.delete("sem-fila")
    }

    const data = Array.from(stats.values()).map((item) => ({
      id: item.id,
      name: item.name,
      pending: item.pending,
      inProgress: item.inProgress,
      paused: item.paused,
      breached: item.breached,
    }))

    data.sort((a, b) => {
      const totalA = a.pending + a.inProgress + a.paused
      const totalB = b.pending + b.inProgress + b.paused
      if (totalA === totalB) {
        return a.name.localeCompare(b.name, "pt-BR")
      }
      return totalB - totalA
    })

    return {
      meta: { kind: "collection", key: "queues.summary_cards" },
      data,
    }
  },
  "tickets.sla_compliance_by_queue": async (ctx, { tenantId, viewer, params }) => {
    const rangeDays = parseRange(params)
    const companyId = parseCompanyId(params)
    const end = new Date()
    end.setUTCHours(0, 0, 0, 0)
    const endMs = end.getTime() + ONE_DAY_MS
    const startMs = endMs - rangeDays * ONE_DAY_MS
    const queueFilter = parseQueueIds(params)
    const tickets = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId)
    const now = Date.now()
    const stats = new Map<string, { total: number; compliant: number }>()

    for (const ticket of tickets) {
      const queueKey = ticket.queueId ? String(ticket.queueId) : "sem-fila"
      if (queueFilter && queueFilter.length > 0 && !queueFilter.includes(queueKey)) {
        continue
      }
      const current = stats.get(queueKey) ?? { total: 0, compliant: 0 }
      current.total += 1
      const dueAt = typeof ticket.dueAt === "number" ? ticket.dueAt : null
      const resolvedAt = typeof ticket.resolvedAt === "number" ? ticket.resolvedAt : null
      let compliant = false
      if (dueAt) {
        if (resolvedAt) {
          compliant = resolvedAt <= dueAt
        } else {
          compliant = dueAt >= now
        }
      } else {
        compliant = resolvedAt !== null
      }
      if (compliant) {
        current.compliant += 1
      }
      stats.set(queueKey, current)
    }

    const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50)
    const data = Array.from(stats.entries()).map(([queueId, value]) => {
      const queue = queues.find((q) => String(q._id) === queueId)
      const compliance = value.total > 0 ? value.compliant / value.total : 0
      return {
        queueId,
        name: queue ? queue.name : queueId === "sem-fila" ? "Sem fila" : "Fila desconhecida",
        total: value.total,
        compliance,
      }
    })

    data.sort((a, b) => (b.compliance ?? 0) - (a.compliance ?? 0))

    return {
      meta: { kind: "collection", key: "tickets.sla_compliance_by_queue", rangeDays },
      data,
    }
  },
  "tickets.sla_rate": async (ctx, { tenantId, viewer, params }) => {
    const rangeDays = parseRange(params)
    const companyId = parseCompanyId(params)
    const end = new Date()
    end.setUTCHours(0, 0, 0, 0)
    const endMs = end.getTime() + ONE_DAY_MS
    const startMs = endMs - rangeDays * ONE_DAY_MS
    const tickets = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId)

    const total = tickets.length
    const resolved = tickets.filter((t) => normalizeStatus(t.status) === "RESOLVED").length
    const rate = total > 0 ? resolved / total : 0

    return {
      meta: { kind: "single", key: "tickets.sla_rate", rangeDays, unit: "ratio" },
      data: { value: rate, total, resolved },
    }
  },
  "tickets.awaiting_table": async (ctx, { tenantId, viewer, params }) => {
    const limit = parseLimit(params, 20)
    const tickets = filterTicketsByQueue(
      await fetchOpenScopedTickets(ctx, tenantId, viewer),
      parseQueueIds(params),
    )
    const awaiting = tickets
      .filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status)))
      .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))
      .slice(0, limit)
      .map((ticket) => ({
        id: ticket._id,
        reference: ticket.reference ?? null,
        subject: ticket.subject,
        status: normalizeStatus(ticket.status),
        priority: ticket.priority,
        updatedAt: ticket.updatedAt,
        createdAt: ticket.createdAt,
        assignee: ticket.assigneeSnapshot
          ? {
              name: (ticket.assigneeSnapshot as { name?: string })?.name ?? null,
              email: (ticket.assigneeSnapshot as { email?: string })?.email ?? null,
            }
          : null,
        queueId: ticket.queueId ?? null,
      }))

    return {
      meta: { kind: "table", key: "tickets.awaiting_table", limit },
      data: awaiting,
    }
  },
  "devices.health_summary": async (ctx, { tenantId, params }) => {
    const limit = parseLimit(params, 10)
    // Limita a 200 maquinas para evitar OOM
    const machines = await ctx.db.query("machines").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(200)
    const now = Date.now()
    const summary = machines
      .map((machine) => {
        const lastHeartbeatAt = machine.lastHeartbeatAt ?? null
        const minutesSinceHeartbeat = lastHeartbeatAt ? Math.round((now - lastHeartbeatAt) / 60000) : null
        const status = deriveMachineStatus(machine, now)
        const cpu = clampPercent(pickMachineMetric(machine, ["cpuUsagePercent", "cpu_usage_percent"]))
        const memory = clampPercent(pickMachineMetric(machine, ["memoryUsedPercent", "memory_usage_percent"]))
        const disk = clampPercent(pickMachineMetric(machine, ["diskUsedPercent", "diskUsagePercent", "storageUsedPercent"]))
        const alerts = readMachineAlertsCount(machine)
        const fallbackHostname = readString((machine as unknown as Record<string, unknown>)["computerName"])
        const hostname = machine.hostname ?? fallbackHostname ?? "Dispositivo sem nome"
        const attention =
          (cpu ?? 0) > 85 ||
          (memory ?? 0) > 90 ||
          (disk ?? 0) > 90 ||
          (minutesSinceHeartbeat ?? Infinity) > 120 ||
          alerts > 0
        return {
          id: machine._id,
          hostname,
          status,
          cpuUsagePercent: cpu,
          memoryUsedPercent: memory,
          diskUsedPercent: disk,
          lastHeartbeatAt,
          minutesSinceHeartbeat,
          alerts,
          attention,
        }
      })
      .sort((a, b) => {
        if (a.attention === b.attention) {
          return (b.cpuUsagePercent ?? 0) - (a.cpuUsagePercent ?? 0)
        }
        return a.attention ? -1 : 1
      })
      .slice(0, limit)

    return {
      meta: { kind: "collection", key: "devices.health_summary", limit },
      data: summary,
    }
  },
  "agents.self_ticket_status": async (ctx, { tenantId, viewer, viewerId, params }) => {
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays, viewerId)
    const stats = statsMap.get(String(viewerId))
    const data = [
      { status: "Abertos", total: stats?.open ?? 0 },
      { status: "Pausados", total: stats?.paused ?? 0 },
      { status: "Resolvidos", total: stats?.resolved ?? 0 },
    ]
    return {
      meta: { kind: "collection", key: "agents.self_ticket_status", rangeDays },
      data,
    }
  },
  "agents.self_open_total": async (ctx, { tenantId, viewer, viewerId, params }) => {
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays, viewerId)
    const stats = statsMap.get(String(viewerId))
    return {
      meta: { kind: "single", key: "agents.self_open_total", unit: "tickets", rangeDays },
      data: { value: stats?.open ?? 0 },
    }
  },
  "agents.self_paused_total": async (ctx, { tenantId, viewer, viewerId, params }) => {
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays, viewerId)
    const stats = statsMap.get(String(viewerId))
    return {
      meta: { kind: "single", key: "agents.self_paused_total", unit: "tickets", rangeDays },
      data: { value: stats?.paused ?? 0 },
    }
  },
  "agents.self_resolved_total": async (ctx, { tenantId, viewer, viewerId, params }) => {
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays, viewerId)
    const stats = statsMap.get(String(viewerId))
    return {
      meta: { kind: "single", key: "agents.self_resolved_total", unit: "tickets", rangeDays },
      data: { value: stats?.resolved ?? 0 },
    }
  },
  "agents.self_sla_rate": async (ctx, { tenantId, viewer, viewerId, params }) => {
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays, viewerId)
    const stats = statsMap.get(String(viewerId))
    return {
      meta: { kind: "single", key: "agents.self_sla_rate", rangeDays },
      data: {
        value: stats?.slaRate ?? 0,
        total: stats?.totalSla ?? 0,
        compliant: stats?.compliantSla ?? 0,
      },
    }
  },
  "agents.self_avg_resolution_minutes": async (ctx, { tenantId, viewer, viewerId, params }) => {
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays, viewerId)
    const stats = statsMap.get(String(viewerId))
    const raw = stats?.avgResolutionMinutes ?? null
    const value = raw !== null ? Math.round(raw * 10) / 10 : 0
    return {
      meta: { kind: "single", key: "agents.self_avg_resolution_minutes", unit: "minutes", rangeDays },
      data: { value },
    }
  },
  "agents.team_overview": async (ctx, { tenantId, viewer, params }) => {
    if (viewer.role !== "ADMIN" && viewer.role !== "MANAGER") {
      throw new ConvexError("Apenas administradores podem acessar esta métrica.")
    }
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays)
    const data = Array.from(statsMap.values())
      .map((stats) => ({
        agentId: stats.agentId,
        agentName: stats.name ?? stats.email ?? "Agente",
        open: stats.open,
        paused: stats.paused,
        resolved: stats.resolved,
        slaRate: stats.slaRate !== null ? Math.round(stats.slaRate * 1000) / 10 : null,
        avgResolutionMinutes:
          stats.avgResolutionMinutes !== null ? Math.round(stats.avgResolutionMinutes * 10) / 10 : null,
      }))
      .sort((a, b) => b.resolved - a.resolved)
    return {
      meta: { kind: "collection", key: "agents.team_overview", rangeDays },
      data,
    }
  },
  "agents.team_resolved_total": async (ctx, { tenantId, viewer, params }) => {
    if (viewer.role !== "ADMIN" && viewer.role !== "MANAGER") {
      throw new ConvexError("Apenas administradores podem acessar esta métrica.")
    }
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays)
    const data = Array.from(statsMap.values())
      .map((stats) => ({
        agentId: stats.agentId,
        agentName: stats.name ?? stats.email ?? "Agente",
        resolved: stats.resolved,
      }))
      .sort((a, b) => b.resolved - a.resolved)
    return {
      meta: { kind: "collection", key: "agents.team_resolved_total", rangeDays },
      data,
    }
  },
  "agents.team_sla_rate": async (ctx, { tenantId, viewer, params }) => {
    if (viewer.role !== "ADMIN" && viewer.role !== "MANAGER") {
      throw new ConvexError("Apenas administradores podem acessar esta métrica.")
    }
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays)
    const data = Array.from(statsMap.values())
      .map((stats) => ({
        agentId: stats.agentId,
        agentName: stats.name ?? stats.email ?? "Agente",
        compliance: stats.slaRate ?? 0,
        total: stats.totalSla,
        compliant: stats.compliantSla,
      }))
      .sort((a, b) => (b.compliance ?? 0) - (a.compliance ?? 0))
    return {
      meta: { kind: "collection", key: "agents.team_sla_rate", rangeDays },
      data,
    }
  },
  "agents.team_avg_resolution_minutes": async (ctx, { tenantId, viewer, params }) => {
    if (viewer.role !== "ADMIN" && viewer.role !== "MANAGER") {
      throw new ConvexError("Apenas administradores podem acessar esta métrica.")
    }
    const rangeDays = parseRange(params)
    const statsMap = await computeAgentStats(ctx, tenantId, viewer, rangeDays)
    const data = Array.from(statsMap.values())
      .map((stats) => ({
        agentId: stats.agentId,
        agentName: stats.name ?? stats.email ?? "Agente",
        minutes:
          stats.avgResolutionMinutes !== null ? Math.round(stats.avgResolutionMinutes * 10) / 10 : 0,
      }))
      .sort((a, b) => (a.minutes ?? 0) - (b.minutes ?? 0))
    return {
      meta: { kind: "collection", key: "agents.team_avg_resolution_minutes", rangeDays },
      data,
    }
  },
}

export const run = query({
  args: {
    tenantId: v.string(),
    viewerId: v.id("users"),
    metricKey: v.string(),
    params: v.optional(v.any()),
  },
  handler: async (ctx, { tenantId, viewerId, metricKey, params }) => {
    const viewer = await requireStaff(ctx, viewerId, tenantId)
    const resolver = metricResolvers[metricKey]
    if (!resolver) {
      return {
        meta: { kind: "error", key: metricKey, message: "Métrica não suportada" },
        data: null,
      }
    }
    const payload = await resolver(ctx, {
      tenantId,
      viewer,
      viewerId,
      params: params && typeof params === "object" ? (params as Record<string, unknown>) : undefined,
    })
    return payload
  },
})

function formatDateKey(timestamp: number) {
  const d = new Date(timestamp)
  const year = d.getUTCFullYear()
  const month = `${d.getUTCMonth() + 1}`.padStart(2, "0")
  const day = `${d.getUTCDate()}`.padStart(2, "0")
  return `${year}-${month}-${day}`
}

function deriveMachineStatus(machine: Record<string, unknown>, now: number) {
  const lastHeartbeatAt = typeof machine.lastHeartbeatAt === "number" ? machine.lastHeartbeatAt : null
  if (!lastHeartbeatAt) return "unknown"
  const diffMinutes = (now - lastHeartbeatAt) / 60000
  if (diffMinutes <= 10) return "online"
  if (diffMinutes <= 120) return "stale"
  return "offline"
}

function clampPercent(value: unknown) {
  if (typeof value !== "number" || !Number.isFinite(value)) return null
  if (value < 0) return 0
  if (value > 100) return 100
  return Math.round(value * 10) / 10
}

function readNumeric(value: unknown): number | null {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value
  }
  if (typeof value === "string") {
    const parsed = Number(value)
    if (Number.isFinite(parsed)) {
      return parsed
    }
  }
  return null
}

function pickMachineMetric(machine: Doc<"machines">, keys: string[]): number | null {
  const record = machine as unknown as Record<string, unknown>
  for (const key of keys) {
    const direct = readNumeric(record[key])
    if (direct !== null) {
      return direct
    }
  }
  const metadata = record["metadata"]
  if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
    const metrics = (metadata as Record<string, unknown>)["metrics"]
    if (metrics && typeof metrics === "object" && !Array.isArray(metrics)) {
      const metricsRecord = metrics as Record<string, unknown>
      for (const key of keys) {
        const value = readNumeric(metricsRecord[key])
        if (value !== null) {
          return value
        }
      }
    }
  }
  return null
}

function readMachineAlertsCount(machine: Doc<"machines">): number {
  const record = machine as unknown as Record<string, unknown>
  const directCount = readNumeric(record["postureAlertsCount"])
  if (directCount !== null) {
    return directCount
  }
  const directArray = record["postureAlerts"]
  if (Array.isArray(directArray)) {
    return directArray.length
  }
  const metadata = record["metadata"]
  if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
    const metadataRecord = metadata as Record<string, unknown>
    const metaCount = readNumeric(metadataRecord["postureAlertsCount"])
    if (metaCount !== null) {
      return metaCount
    }
    const metaAlerts = metadataRecord["postureAlerts"]
    if (Array.isArray(metaAlerts)) {
      return metaAlerts.length
    }
  }
  return 0
}

function readString(value: unknown): string | null {
  if (typeof value === "string" && value.trim().length > 0) {
    return value
  }
  return null
}
