/**
 * Credit accounting middleware — system-ai.
 * Checks quota, deducts tokens, logs usage. All in one DB transaction.
 * Only imports from @zync/db/queries (no raw drizzle from this module either).
 */
import {
  incrementCounterBy,
  QuotaExceededError,
  getTenantSettings,
  getTierQuotas,
  insertUsageLog,
  deductPurchasedCredit,
  getCreditPurchases,
  getGlobalConfig,
  getUsageCounter,
  getExtraSpendThisMonth,
} from '@zync/db/queries'
import type { Db } from '@zync/db/queries'
import type { AIResponse } from './adapter'
import type { AIUseCase } from './use-cases'
import { ExtraSpendLimitError } from './errors'
import { calculateCost } from './pricing'

export { QuotaExceededError }

export interface QuotaStatus {
  percentUsed: number
  tokensUsed: number
  tokensTotal: number
  tokensRemaining: number
  resetsAt: string
}

export interface AICtx {
  db: Db
  env: {
    ANTHROPIC_API_KEY: string
    OPENAI_API_KEY: string
    GOOGLE_AI_API_KEY: string
  }
}

/** Current YYYY-MM period string */
function currentPeriod(): string {
  const now = new Date()
  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
}

/** First day of next month (quota reset date) */
function nextMonthReset(): string {
  const now = new Date()
  const next = new Date(now.getFullYear(), now.getMonth() + 1, 1)
  return next.toISOString().slice(0, 10)
}

/**
 * Get the current quota status for a tenant.
 */
export async function getQuotaStatus(
  db: Db,
  tenantId: string,
  tier: string,
): Promise<QuotaStatus> {
  const period = currentPeriod()
  const allQuotas = await getTierQuotas(db)
  const tierQuota = allQuotas.find((q) => q.tier === tier)
  const tokensTotal = tierQuota?.monthlyTokens ?? 0

  const tokensUsed = await getUsageCounter(db, tenantId, 'ai_tokens_quota', period)
  const tokensRemaining = Math.max(0, tokensTotal - tokensUsed)
  const percentUsed =
    tokensTotal > 0 ? Math.min(100, Math.round((tokensUsed / tokensTotal) * 100)) : 0

  return {
    percentUsed,
    tokensUsed,
    tokensTotal,
    tokensRemaining,
    resetsAt: nextMonthReset(),
  }
}

/**
 * True when tier has extra_allowed=true AND tenant has extra_usage_enabled=true.
 */
export async function isExtraUsageAllowed(
  db: Db,
  tenantId: string,
  tier: string,
): Promise<boolean> {
  const allQuotas = await getTierQuotas(db)
  const tierQuota = allQuotas.find((q) => q.tier === tier)
  if (!tierQuota?.extraAllowed) return false
  const settings = await getTenantSettings(db, tenantId)
  return settings.extraUsageEnabled
}

/**
 * Throws ExtraSpendLimitError if tenant's extra spend this month >= effective limit.
 */
export async function checkExtraSpendLimit(
  db: Db,
  tenantId: string,
  tier: string,
): Promise<void> {
  const allQuotas = await getTierQuotas(db)
  const tierQuota = allQuotas.find((q) => q.tier === tier)
  const settings = await getTenantSettings(db, tenantId)

  const tierMaxUsd = tierQuota?.extraMaxUsd ? parseFloat(tierQuota.extraMaxUsd) : null
  const tenantLimitUsd = settings.extraSpendLimitUsd
    ? parseFloat(settings.extraSpendLimitUsd)
    : null

  let effectiveLimitUsd: number | null = null
  if (tierMaxUsd !== null && tenantLimitUsd !== null) {
    effectiveLimitUsd = Math.min(tierMaxUsd, tenantLimitUsd)
  } else if (tierMaxUsd !== null) {
    effectiveLimitUsd = tierMaxUsd
  } else if (tenantLimitUsd !== null) {
    effectiveLimitUsd = tenantLimitUsd
  }

  if (effectiveLimitUsd === null) return

  const period = currentPeriod()
  const spentUsd = await getExtraSpendThisMonth(db, tenantId, period)
  if (spentUsd >= effectiveLimitUsd) {
    throw new ExtraSpendLimitError(
      `Extra spend limit of $${effectiveLimitUsd} exceeded (spent $${spentUsd.toFixed(2)})`,
    )
  }
}

/**
 * Deduct tokens and insert usage log in ONE DB transaction.
 */
export async function deductAndLog(
  db: Db,
  tenantId: string,
  info: {
    tokens: number
    inputTokens: number
    outputTokens: number
    costUsd: number
    useCase: AIUseCase
    model: string
    provider: string
    durationMs: number
    userId?: string | null
    entityType?: string | null
    entityId?: string | null
    billedFrom: 'quota' | 'extra' | 'purchased'
    error?: string | null
  },
): Promise<void> {
  const period = currentPeriod()

  await db.transaction(async (tx) => {
    const counterKey =
      info.billedFrom === 'purchased'
        ? 'ai_tokens_purchased'
        : info.billedFrom === 'extra'
          ? 'ai_tokens_extra'
          : 'ai_tokens_quota'

    if (!info.error && info.tokens > 0) {
      await incrementCounterBy(tx, tenantId, counterKey, period, info.tokens)
    }

    if (info.billedFrom === 'purchased' && !info.error && info.tokens > 0) {
      await deductPurchasedCredit(tx, tenantId, info.tokens)
    }

    await insertUsageLog(tx, {
      tenantId,
      userId: info.userId ?? null,
      useCase: info.useCase,
      modelId: info.model,
      provider: info.provider,
      inputTokens: info.inputTokens,
      outputTokens: info.outputTokens,
      totalTokens: info.tokens,
      costUsd: info.costUsd.toFixed(6),
      billedFrom: info.billedFrom,
      durationMs: info.durationMs,
      entityType: info.entityType ?? null,
      entityId: info.entityId ?? null,
      error: info.error ?? null,
    })
  })
}

/**
 * Main credit accounting wrapper.
 */
export async function withCreditAccounting(
  ctx: AICtx,
  tenantId: string,
  useCase: AIUseCase,
  tier: string,
  fn: () => Promise<AIResponse>,
  opts?: {
    userId?: string | null
    entityType?: string | null
    entityId?: string | null
  },
): Promise<AIResponse> {
  const db = ctx.db
  const period = currentPeriod()

  const allQuotas = await getTierQuotas(db)
  const tierQuota = allQuotas.find((q) => q.tier === tier)
  const tokensTotal = tierQuota?.monthlyTokens ?? 0

  const tokensUsed = await getUsageCounter(db, tenantId, 'ai_tokens_quota', period)
  const quotaExhausted = tokensTotal > 0 && tokensUsed >= tokensTotal

  let billedFrom: 'quota' | 'extra' | 'purchased' = 'quota'

  if (quotaExhausted) {
    const extraAllowed = await isExtraUsageAllowed(db, tenantId, tier)
    if (!extraAllowed) {
      throw new QuotaExceededError('ai_tokens_quota', tokensTotal, tokensUsed)
    }
    await checkExtraSpendLimit(db, tenantId, tier)

    const purchases = await getCreditPurchases(db, tenantId)
    const hasActiveCredits = purchases.some(
      (p) =>
        p.tokensRemaining > 0 &&
        (!p.expiresAt || new Date(p.expiresAt) > new Date()),
    )
    billedFrom = hasActiveCredits ? 'purchased' : 'extra'
  }

  let response: AIResponse
  try {
    response = await fn()
  } catch (callErr) {
    const globalConfig = await getGlobalConfig(db)
    await deductAndLog(db, tenantId, {
      tokens: 0,
      inputTokens: 0,
      outputTokens: 0,
      costUsd: 0,
      useCase,
      model: globalConfig.mainModel.model,
      provider: globalConfig.mainModel.provider,
      durationMs: 0,
      userId: opts?.userId ?? null,
      entityType: opts?.entityType ?? null,
      entityId: opts?.entityId ?? null,
      billedFrom,
      error: callErr instanceof Error ? callErr.message : String(callErr),
    })
    throw callErr
  }

  const costUsd = await calculateCost(
    db,
    { inputTokens: response.inputTokens, outputTokens: response.outputTokens },
    response.model,
  )

  await deductAndLog(db, tenantId, {
    tokens: response.inputTokens + response.outputTokens,
    inputTokens: response.inputTokens,
    outputTokens: response.outputTokens,
    costUsd,
    useCase,
    model: response.model,
    provider: response.provider,
    durationMs: response.durationMs,
    userId: opts?.userId ?? null,
    entityType: opts?.entityType ?? null,
    entityId: opts?.entityId ?? null,
    billedFrom,
  })

  return response
}
