import type { Provider } from "../../types/session"
import { DEFAULT_MODEL_OUTPUT_LIMIT } from "../session-models"
import { providers, sessions, sessionInfoByInstance, setSessionInfoByInstance } from "../session-state"
import { messageStoreBus } from "./bus"
import type { SessionUsageState } from "./types"

function getLatestUsageEntry(usage?: SessionUsageState) {
  if (!usage?.latestMessageId) return undefined
  return usage.entries[usage.latestMessageId]
}

function resolveSelectedModel(instanceProviders: Provider[], providerId?: string, modelId?: string) {
  if (!providerId || !modelId) return undefined
  const provider = instanceProviders.find((p) => p.id === providerId)
  return provider?.models.find((m) => m.id === modelId)
}

export function updateSessionInfo(instanceId: string, sessionId: string): void {
  const instanceSessions = sessions().get(instanceId)
  if (!instanceSessions) return
  const session = instanceSessions.get(sessionId)
  if (!session) return

  const store = messageStoreBus.getOrCreate(instanceId)
  const usage = store.getSessionUsage(sessionId)
  const hasUsageEntries = Boolean(usage && Object.keys(usage.entries).length > 0)

  let totalInputTokens = usage?.totalInputTokens ?? 0
  let totalOutputTokens = usage?.totalOutputTokens ?? 0
  let totalReasoningTokens = usage?.totalReasoningTokens ?? 0
  let totalCacheReadTokens = usage?.totalCacheReadTokens ?? 0
  let totalCacheWriteTokens = usage?.totalCacheWriteTokens ?? 0
  let totalCost = usage?.totalCost ?? 0
  let actualUsageTokens = usage?.actualUsageTokens ?? 0

  const latestEntry = getLatestUsageEntry(usage)
  let latestHasContextUsage = latestEntry?.hasContextUsage ?? false

  const previousInfo = sessionInfoByInstance().get(instanceId)?.get(sessionId)
  let contextWindow = 0
  let contextAvailableTokens: number | null = null
  let contextAvailableFromPrevious = false
  let isSubscriptionModel = false

  if (!hasUsageEntries && previousInfo) {
    totalInputTokens = previousInfo.inputTokens
    totalOutputTokens = previousInfo.outputTokens
    totalReasoningTokens = previousInfo.reasoningTokens
    totalCacheReadTokens = previousInfo.cacheReadTokens
    totalCacheWriteTokens = previousInfo.cacheWriteTokens
    totalCost = previousInfo.cost
    actualUsageTokens = previousInfo.actualUsageTokens
  }

  const instanceProviders = providers().get(instanceId) || []

  const sessionModel = session.model
  const sessionProviderId = sessionModel?.providerId
  const sessionModelId = sessionModel?.modelId

  const latestInfo = latestEntry?.messageId ? store.getMessageInfo(latestEntry.messageId) : undefined
  const latestProviderId = (latestInfo as any)?.providerID || (latestInfo as any)?.providerId || ""
  const latestModelId = (latestInfo as any)?.modelID || (latestInfo as any)?.modelId || ""

  const selectedModel =
    resolveSelectedModel(instanceProviders, sessionProviderId, sessionModelId) ??
    resolveSelectedModel(instanceProviders, latestProviderId, latestModelId)

  let modelOutputLimit = DEFAULT_MODEL_OUTPUT_LIMIT

  if (selectedModel) {
    contextWindow = selectedModel.limit?.context ?? 0
    const outputLimit = selectedModel.limit?.output
    if (typeof outputLimit === "number" && outputLimit > 0) {
      modelOutputLimit = Math.min(outputLimit, DEFAULT_MODEL_OUTPUT_LIMIT)
    }
    if ((selectedModel.cost?.input ?? 0) === 0 && (selectedModel.cost?.output ?? 0) === 0) {
      isSubscriptionModel = true
    }
  }

  if (contextWindow === 0 && previousInfo) {
    contextWindow = previousInfo.contextWindow
  }

  modelOutputLimit = Math.min(modelOutputLimit, DEFAULT_MODEL_OUTPUT_LIMIT)

  if (previousInfo) {
    const previousContextWindow = previousInfo.contextWindow
    const previousContextAvailable = previousInfo.contextAvailableTokens ?? null
    const previousHasContextUsage = previousContextAvailable !== null && previousContextWindow > 0
      ? previousContextAvailable < previousContextWindow
      : false

    if (contextWindow !== previousContextWindow) {
      contextAvailableTokens = null
      contextAvailableFromPrevious = false
      latestHasContextUsage = previousHasContextUsage
    } else {
      contextAvailableTokens = previousContextAvailable
      contextAvailableFromPrevious = true
      latestHasContextUsage = previousHasContextUsage
    }

    if (!hasUsageEntries) {
      isSubscriptionModel = previousInfo.isSubscriptionModel
    } else if (!isSubscriptionModel) {
      isSubscriptionModel = previousInfo.isSubscriptionModel
    }
  }

  const outputBudget = Math.min(modelOutputLimit, DEFAULT_MODEL_OUTPUT_LIMIT)

  if (!contextAvailableFromPrevious) {
    if (contextWindow > 0) {
      if (latestHasContextUsage && actualUsageTokens > 0) {
        contextAvailableTokens = Math.max(contextWindow - (actualUsageTokens + outputBudget), 0)
      } else {
        contextAvailableTokens = contextWindow
      }
    } else {
      contextAvailableTokens = null
    }
  }

  setSessionInfoByInstance((prev) => {
    const next = new Map(prev)
    const instanceInfo = new Map(prev.get(instanceId))
    instanceInfo.set(sessionId, {
      cost: totalCost,
      contextWindow,
      isSubscriptionModel,
      inputTokens: totalInputTokens,
      outputTokens: totalOutputTokens,
      reasoningTokens: totalReasoningTokens,
      cacheReadTokens: totalCacheReadTokens,
      cacheWriteTokens: totalCacheWriteTokens,
      actualUsageTokens,
      modelOutputLimit,
      contextAvailableTokens,
    })
    next.set(instanceId, instanceInfo)
    return next
  })
}
