import { Log } from "../util/log"
import type { Provider } from "../provider/provider"

export namespace ProviderSwitch {
  const log = Log.create({ service: "provider-switch" })

  export interface ProviderState {
    providerID: string
    modelID: string
    consecutiveFailures: number
    firstFailureAt: Date | null
    backoffUntil: Date | null
  }

  const providerStates = new Map<string, ProviderState>()
  const STATE_KEY_SEPARATOR = "::"

  function getStateKey(providerID: string, modelID: string): string {
    return `${providerID}${STATE_KEY_SEPARATOR}${modelID}`
  }

  function parseStateKey(key: string): { providerID: string; modelID: string } | null {
    const [providerID, ...rest] = key.split(STATE_KEY_SEPARATOR)
    if (!rest.length) return null
    return { providerID, modelID: rest.join(STATE_KEY_SEPARATOR) }
  }

  function getState(providerID: string, modelID: string): ProviderState {
    const key = getStateKey(providerID, modelID)
    let state = providerStates.get(key)

    if (!state) {
      state = {
        providerID,
        modelID,
        consecutiveFailures: 0,
        firstFailureAt: null,
        backoffUntil: null,
      }
      providerStates.set(key, state)
    }

    return state
  }

  export function recordFailure(providerID: string, modelID: string): void {
    const state = getState(providerID, modelID)
    state.consecutiveFailures++
    
    if (!state.firstFailureAt) {
      state.firstFailureAt = new Date()
    }

    log.info("Provider failure recorded", {
      providerID,
      modelID,
      consecutiveFailures: state.consecutiveFailures,
      firstFailureAt: state.firstFailureAt,
    })
  }

  export function recordSuccess(providerID: string, modelID: string): void {
    const state = getState(providerID, modelID)

    if (state.consecutiveFailures > 0) {
      log.info("Provider recovered", {
        providerID,
        modelID,
        consecutiveFailures: state.consecutiveFailures,
      })
    }

    state.consecutiveFailures = 0
    state.firstFailureAt = null
    state.backoffUntil = null
  }

  export function isBackedOff(providerID: string, modelID: string): boolean {
    const state = getState(providerID, modelID)
    
    if (!state.backoffUntil) {
      return false
    }

    if (state.backoffUntil <= new Date()) {
      log.info("Provider backoff expired", { providerID, modelID })
      state.backoffUntil = null
      return false
    }

    return true
  }

  export function setBackoff(providerID: string, modelID: string, duration: number): void {
    const state = getState(providerID, modelID)
    state.backoffUntil = new Date(Date.now() + duration)

    log.info("Provider set to backoff", {
      providerID,
      modelID,
      duration,
      backoffUntil: state.backoffUntil,
    })
  }

  export interface ProviderPriority {
    providerID: string
    priority: number
  }

  const defaultProviderPriorities: ProviderPriority[] = [
    { providerID: "anthropic", priority: 1 },
    { providerID: "openai", priority: 2 },
    { providerID: "google", priority: 3 },
    { providerID: "openrouter", priority: 4 },
    { providerID: "groq", priority: 5 },
    { providerID: "xai", priority: 6 },
    { providerID: "togetherai", priority: 7 },
    { providerID: "perplexity", priority: 8 },
    { providerID: "deepinfra", priority: 9 },
    { providerID: "cerebras", priority: 10 },
    { providerID: "mistral", priority: 11 },
    { providerID: "cohere", priority: 12 },
    { providerID: "amazon-bedrock", priority: 13 },
    { providerID: "azure", priority: 14 },
    { providerID: "github-copilot", priority: 15 },
    { providerID: "github-copilot-enterprise", priority: 16 },
    { providerID: "opencode", priority: 17 },
    { providerID: "zenmux", priority: 18 },
    { providerID: "google-vertex", priority: 19 },
    { providerID: "gitlab", priority: 20 },
  ]

  export function getNextProvider(
    currentProviderID: string,
    currentModelID: string,
    allProviders: Record<string, Provider.Info>,
  ): { providerID: string; modelID: string } | null {
    const currentIndex = defaultProviderPriorities.findIndex((p) => p.providerID === currentProviderID)

    let nextPriorityProvider: ProviderPriority | null = null

    for (let i = currentIndex + 1; i < defaultProviderPriorities.length; i++) {
      const priority = defaultProviderPriorities[i]
      const provider = allProviders[priority.providerID]

      if (!provider || !provider.models[currentModelID]) {
        continue
      }

      if (isBackedOff(priority.providerID, currentModelID)) {
        log.info("Skipping backoff provider", { providerID: priority.providerID, modelID: currentModelID })
        continue
      }

      nextPriorityProvider = priority
      break
    }

    if (!nextPriorityProvider) {
      log.info("No fallback provider available", { currentProviderID, currentModelID })
      return null
    }

    log.info("Switching to fallback provider", {
      from: currentProviderID,
      to: nextPriorityProvider.providerID,
      modelID: currentModelID,
    })

    return {
      providerID: nextPriorityProvider.providerID,
      modelID: currentModelID,
    }
  }

  export function getBackoffDuration(consecutiveFailures: number): number {
    const baseDuration = 60_000 // 1 minute
    const maxDuration = 1_800_000 // 30 minutes
    
    const duration = baseDuration * Math.pow(2, consecutiveFailures - 1)
    return Math.min(duration, maxDuration)
  }

  export async function trySwitchProvider(
    currentProviderID: string,
    currentModelID: string,
    allProviders: Record<string, Provider.Info>,
  ): Promise<{ providerID: string; modelID: string } | null> {
    const next = getNextProvider(currentProviderID, currentModelID, allProviders)

    if (next) {
      setBackoff(currentProviderID, currentModelID, getBackoffDuration(getState(currentProviderID, currentModelID).consecutiveFailures))
      return next
    }

    return null
  }

  export function clearAllStates(): void {
    providerStates.clear()
    log.info("All provider states cleared")
  }

  export function getAllStates(): ProviderState[] {
    return Array.from(providerStates.values())
  }
}
