import { mutation, query, internalMutation } from "./_generated/server"
import { v } from "convex/values"
import type { Id } from "./_generated/dataModel"

// Tipo para software recebido do agente
type SoftwareInput = {
  name: string
  version?: string
  publisher?: string
  source?: string
}

// Upsert de softwares de uma maquina (chamado pelo heartbeat)
export const syncFromHeartbeat = internalMutation({
  args: {
    tenantId: v.string(),
    machineId: v.id("machines"),
    software: v.array(
      v.object({
        name: v.string(),
        version: v.optional(v.string()),
        publisher: v.optional(v.string()),
        source: v.optional(v.string()),
      })
    ),
  },
  handler: async (ctx, { tenantId, machineId, software }) => {
    const now = Date.now()

    // Busca softwares existentes da maquina
    const existing = await ctx.db
      .query("machineSoftware")
      .withIndex("by_machine", (q) => q.eq("machineId", machineId))
      .collect()

    const existingMap = new Map(existing.map((s) => [`${s.nameLower}|${s.version ?? ""}`, s]))

    // Processa cada software recebido
    const seenKeys = new Set<string>()
    for (const item of software) {
      if (!item.name || item.name.trim().length === 0) continue

      const nameLower = item.name.toLowerCase().trim()
      const key = `${nameLower}|${item.version ?? ""}`
      seenKeys.add(key)

      const existingDoc = existingMap.get(key)
      if (existingDoc) {
        // Atualiza lastSeenAt se ja existe
        await ctx.db.patch(existingDoc._id, {
          lastSeenAt: now,
          publisher: item.publisher || existingDoc.publisher,
          source: item.source || existingDoc.source,
        })
      } else {
        // Cria novo registro
        await ctx.db.insert("machineSoftware", {
          tenantId,
          machineId,
          name: item.name.trim(),
          nameLower,
          version: item.version?.trim() || undefined,
          publisher: item.publisher?.trim() || undefined,
          source: item.source?.trim() || undefined,
          detectedAt: now,
          lastSeenAt: now,
        })
      }
    }

    // Remove softwares que nao foram vistos (desinstalados)
    // So remove se o software nao foi visto nas ultimas 24 horas
    const staleThreshold = now - 24 * 60 * 60 * 1000
    for (const doc of existing) {
      const key = `${doc.nameLower}|${doc.version ?? ""}`
      if (!seenKeys.has(key) && doc.lastSeenAt < staleThreshold) {
        await ctx.db.delete(doc._id)
      }
    }

    return { processed: software.length }
  },
})

// Lista softwares de uma maquina com paginacao e filtros
export const listByMachine = query({
  args: {
    tenantId: v.string(),
    viewerId: v.id("users"),
    machineId: v.id("machines"),
    search: v.optional(v.string()),
    limit: v.optional(v.number()),
    cursor: v.optional(v.string()),
  },
  handler: async (ctx, { machineId, search, limit = 50, cursor }) => {
    const pageLimit = Math.min(limit, 100)

    let query = ctx.db
      .query("machineSoftware")
      .withIndex("by_machine", (q) => q.eq("machineId", machineId))

    // Coleta todos e filtra em memoria (Convex nao suporta LIKE)
    const all = await query.collect()

    // Filtra por search se fornecido
    let filtered = all
    if (search && search.trim().length > 0) {
      const searchLower = search.toLowerCase().trim()
      filtered = all.filter(
        (s) =>
          s.nameLower.includes(searchLower) ||
          (s.publisher && s.publisher.toLowerCase().includes(searchLower)) ||
          (s.version && s.version.toLowerCase().includes(searchLower))
      )
    }

    // Ordena por nome
    filtered.sort((a, b) => a.nameLower.localeCompare(b.nameLower))

    // Paginacao manual
    let startIndex = 0
    if (cursor) {
      const cursorIndex = filtered.findIndex((s) => s._id === cursor)
      if (cursorIndex >= 0) {
        startIndex = cursorIndex + 1
      }
    }

    const page = filtered.slice(startIndex, startIndex + pageLimit)
    const nextCursor = page.length === pageLimit ? page[page.length - 1]._id : null

    return {
      items: page.map((s) => ({
        id: s._id,
        name: s.name,
        version: s.version ?? null,
        publisher: s.publisher ?? null,
        source: s.source ?? null,
        detectedAt: s.detectedAt,
        lastSeenAt: s.lastSeenAt,
      })),
      total: filtered.length,
      nextCursor,
    }
  },
})

// Lista softwares de todas as maquinas de um tenant (para admin)
export const listByTenant = query({
  args: {
    tenantId: v.string(),
    viewerId: v.id("users"),
    search: v.optional(v.string()),
    machineId: v.optional(v.id("machines")),
    limit: v.optional(v.number()),
    cursor: v.optional(v.string()),
  },
  handler: async (ctx, { tenantId, search, machineId, limit = 50, cursor }) => {
    const pageLimit = Math.min(limit, 100)

    // Busca por tenant ou por maquina especifica
    let all: Array<{
      _id: Id<"machineSoftware">
      tenantId: string
      machineId: Id<"machines">
      name: string
      nameLower: string
      version?: string
      publisher?: string
      source?: string
      detectedAt: number
      lastSeenAt: number
    }>

    if (machineId) {
      all = await ctx.db
        .query("machineSoftware")
        .withIndex("by_tenant_machine", (q) => q.eq("tenantId", tenantId).eq("machineId", machineId))
        .collect()
    } else {
      // Busca por tenant - pode ser grande, limita
      all = await ctx.db
        .query("machineSoftware")
        .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
        .take(5000)
    }

    // Filtra por search
    let filtered = all
    if (search && search.trim().length > 0) {
      const searchLower = search.toLowerCase().trim()
      filtered = all.filter(
        (s) =>
          s.nameLower.includes(searchLower) ||
          (s.publisher && s.publisher.toLowerCase().includes(searchLower)) ||
          (s.version && s.version.toLowerCase().includes(searchLower))
      )
    }

    // Ordena por nome
    filtered.sort((a, b) => a.nameLower.localeCompare(b.nameLower))

    // Paginacao
    let startIndex = 0
    if (cursor) {
      const cursorIndex = filtered.findIndex((s) => s._id === cursor)
      if (cursorIndex >= 0) {
        startIndex = cursorIndex + 1
      }
    }

    const page = filtered.slice(startIndex, startIndex + pageLimit)
    const nextCursor = page.length === pageLimit ? page[page.length - 1]._id : null

    // Busca nomes das maquinas
    const machineIds = [...new Set(page.map((s) => s.machineId))]
    const machines = await Promise.all(machineIds.map((id) => ctx.db.get(id)))
    const machineNames = new Map(
      machines.filter(Boolean).map((m) => [m!._id, m!.displayName || m!.hostname])
    )

    return {
      items: page.map((s) => ({
        id: s._id,
        machineId: s.machineId,
        machineName: machineNames.get(s.machineId) ?? "Desconhecido",
        name: s.name,
        version: s.version ?? null,
        publisher: s.publisher ?? null,
        source: s.source ?? null,
        detectedAt: s.detectedAt,
        lastSeenAt: s.lastSeenAt,
      })),
      total: filtered.length,
      nextCursor,
    }
  },
})

// Conta softwares de uma maquina
export const countByMachine = query({
  args: {
    machineId: v.id("machines"),
  },
  handler: async (ctx, { machineId }) => {
    const software = await ctx.db
      .query("machineSoftware")
      .withIndex("by_machine", (q) => q.eq("machineId", machineId))
      .collect()

    return { count: software.length }
  },
})

// Conta softwares unicos por tenant (para relatorios)
export const stats = query({
  args: {
    tenantId: v.string(),
    viewerId: v.id("users"),
  },
  handler: async (ctx, { tenantId }) => {
    const software = await ctx.db
      .query("machineSoftware")
      .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
      .take(10000)

    const uniqueNames = new Set(software.map((s) => s.nameLower))
    const machineIds = new Set(software.map((s) => s.machineId))

    return {
      totalInstances: software.length,
      uniqueSoftware: uniqueNames.size,
      machinesWithSoftware: machineIds.size,
    }
  },
})
