import { fetchSaeeolModels, type SaeeolModelsResult } from "@saeeol/gateway"
import { TTLCache } from "@/boxes/ttl-cache"
import { Config } from "../config/config"
import { Auth } from "../auth"
import * as Log from "@saeeol/core/util/log"

export namespace ModelCache {
  const log = Log.create({ service: "model-cache" })

  const cache = new TTLCache<string, Record<string, any>>({ defaultTtl: 5 * 60 * 1000 })
  const inFlightRefresh = new Map<string, Promise<Record<string, any>>>()

  // Per-provider failure tracking
  const failures = new Map<string, SaeeolModelsResult["error"]>()

  /**
   * Get the failure state for a provider (undefined = no failure)
   */
  export function getFailure(providerID: string): SaeeolModelsResult["error"] | undefined {
    return failures.get(providerID)
  }

  /**
   * Get all provider IDs that have a failure state
   */
  export function failedProviders(): string[] {
    return [...failures.keys()]
  }

  /**
   * Get cached models if available and not expired
   * @param providerID - Provider identifier (e.g., "saeeol")
   * @returns Cached models or undefined if cache miss or expired
   */
  export function get(providerID: string): Record<string, any> | undefined {
    const cached = cache.get(providerID)

    if (!cached) {
      log.debug("cache miss", { providerID })
      return undefined
    }

    log.debug("cache hit", { providerID })
    return cached
  }

  /**
   * Fetch models with cache-first approach
   * @param providerID - Provider identifier
   * @param options - Provider options
   * @returns Models from cache or freshly fetched
   */
  export async function fetch(providerID: string, options?: any): Promise<Record<string, any>> {
    // Check cache first
    const cached = get(providerID)
    if (cached) {
      return cached
    }

    // Cache miss - fetch models
    log.info("fetching models", { providerID })

    const authOptions = await getAuthOptions(providerID).catch((err) => {
      log.warn("getAuthOptions failed", { providerID, err })
      return {}
    })
    const mergedOptions = { ...authOptions, ...options }

    const result = await fetchModels(providerID, mergedOptions)
    const { models } = result

    if (result.error) {
      failures.set(providerID, result.error)
      log.warn("model fetch error", { providerID, error: result.error })
    } else {
      failures.delete(providerID)
    }

    // Store in cache (even on error, to avoid hammering the API)
    cache.set(providerID, models)

    log.info("models fetched and cached", { providerID, count: Object.keys(models).length })
    return models
  }

  /**
   * Force refresh models (bypass cache)
   * Uses atomic refresh pattern to prevent race conditions
   * @param providerID - Provider identifier
   * @param options - Provider options
   * @returns Freshly fetched models
   */
  export async function refresh(providerID: string, options?: any): Promise<Record<string, any>> {
    // Check if refresh already in progress
    const existing = inFlightRefresh.get(providerID)
    if (existing) {
      log.debug("refresh already in progress, returning existing promise", { providerID })
      return existing
    }

    // Create new refresh promise
    const refreshPromise = (async () => {
      log.info("refreshing models", { providerID })

      const authOptions = await getAuthOptions(providerID).catch((err) => {
        log.warn("getAuthOptions failed during refresh", { providerID, err })
        return {}
      })
      const mergedOptions = { ...authOptions, ...options }

      const result = await fetchModels(providerID, mergedOptions)
      const { models } = result

      if (result.error) {
        failures.set(providerID, result.error)
        log.warn("model refresh error", { providerID, error: result.error })
      } else {
        failures.delete(providerID)
      }

      cache.set(providerID, models)

      log.info("models refreshed", { providerID, count: Object.keys(models).length })
      return models
    })()

    // Track in-flight refresh
    inFlightRefresh.set(providerID, refreshPromise)

    try {
      return await refreshPromise
    } finally {
      // Clean up in-flight tracking
      inFlightRefresh.delete(providerID)
    }
  }

  /**
   * Clear cached models for a provider
   * @param providerID - Provider identifier
   */
  export function clear(providerID: string): void {
    const had = cache.get(providerID)
    cache.delete(providerID)
    failures.delete(providerID)
    if (had) {
      log.info("cache cleared", { providerID })
    } else {
      log.debug("no cache to clear", { providerID })
    }
  }

  /**
   * Fetch models based on provider type
   * @param providerID - Provider identifier
   * @param options - Provider options
   * @returns Fetched models
   */
  async function fetchModels(providerID: string, options: any): Promise<SaeeolModelsResult> {
    if (providerID === "saeeol") {
      return fetchSaeeolModels(options)
    }
    if (providerID === "apertis") {
      const models = await fetchApertisModels(options)
      return { models }
    }

    // Other providers not implemented yet
    log.debug("provider not implemented", { providerID })
    return { models: {} }
  }
  const APERTIS_BASE_URL = "https://api.apertis.ai/v1"

  async function fetchApertisModels(options: any): Promise<Record<string, any>> {
    const baseURL = options.baseURL ?? APERTIS_BASE_URL
    const apiKey = options.apiKey

    if (!apiKey) {
      log.debug("no API key for apertis, skipping model fetch")
      return {}
    }

    const url = `${baseURL.replace(/\/+$/, "")}/models`
    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
      signal: AbortSignal.timeout(10_000),
    })

    if (!response.ok) {
      log.error("apertis model fetch failed", { status: response.status })
      return {}
    }

    const json = (await response.json()) as { data?: Array<{ id: string; owned_by?: string }> }
    const models: Record<string, any> = {}

    for (const model of json.data ?? []) {
      models[model.id] = {
        id: model.id,
        name: model.id,
        family: model.owned_by ?? "",
        release_date: "",
        attachment: true,
        reasoning: false,
        temperature: true,
        tool_call: true,
        cost: { input: 0, output: 0 },
        limit: { context: 128000, output: 4096 },
        options: {},
        modalities: {
          input: ["text", "image"],
          output: ["text"],
        },
      }
    }

    return models
  }

  /**
   * Get authentication options from multiple sources
   * Priority: Config > Auth > Env
   * @param providerID - Provider identifier
   * @returns Options object with authentication credentials
   */
  async function getAuthOptions(providerID: string): Promise<any> {
    const options: any = {}

    if (providerID === "saeeol") {
      // Get from Config
      const config = await Config.get()
      const providerConfig = config.provider?.[providerID]
      if (providerConfig?.options?.apiKey) {
        options.saeeolToken = providerConfig.options.apiKey
      }
      if (providerConfig?.options?.saeeolOrganizationId) {
        options.saeeolOrganizationId = providerConfig.options.saeeolOrganizationId
      }

      // Get from Auth
      const auth = await Auth.get(providerID)
      if (auth) {
        if (auth.type === "api") {
          options.saeeolToken = auth.key
        } else if (auth.type === "oauth") {
          options.saeeolToken = auth.access
          if (auth.accountId) {
            options.saeeolOrganizationId = auth.accountId
          }
        }
      }

      // Get from Env (process.env — matches upstream's pattern for sync async helpers)
      const env = process.env
      if (env.SAEEOL_API_KEY) {
        options.saeeolToken = env.SAEEOL_API_KEY
      }
      if (env.SAEEOL_ORG_ID) {
        options.saeeolOrganizationId = env.SAEEOL_ORG_ID
      }

      log.debug("auth options resolved", {
        providerID,
        hasToken: !!options.saeeolToken,
        hasOrganizationId: !!options.saeeolOrganizationId,
      })
    }
    if (providerID === "apertis") {
      const config = await Config.get()
      const providerConfig = config.provider?.[providerID]
      if (providerConfig?.options?.apiKey) {
        options.apiKey = providerConfig.options.apiKey
      }
      if (providerConfig?.options?.baseURL) {
        options.baseURL = providerConfig.options.baseURL
      }

      const auth = await Auth.get(providerID)
      if (auth && auth.type === "api") {
        options.apiKey = auth.key
      }

      const env = process.env
      if (env.APERTIS_API_KEY) {
        options.apiKey = env.APERTIS_API_KEY
      }
      if (env.APERTIS_BASE_URL) {
        options.baseURL = env.APERTIS_BASE_URL
      }

      log.debug("apertis auth options resolved", {
        providerID,
        hasKey: !!options.apiKey,
        hasBaseURL: !!options.baseURL,
      })
    }

    return options
  }
}
