use std::collections::HashMap;
use std::fmt;
use std::time::Duration;

use anyhow::{Result, bail};
use axum::http::{HeaderName, HeaderValue, Method};
use educe::Educe;
use serde::Serialize;
use serde_json::Value;
use vllm_chat::{ChatTemplateContentFormatOption, ParserSelection, RendererSelection};
use vllm_engine_core_client::{CoordinatorMode as EngineCoreCoordinatorMode, TransportMode};

/// How the HTTP server obtains its listening socket.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum HttpListenerMode {
    /// Bind a fresh TCP listener on the given host/port.
    BindTcp { host: String, port: u16 },
    /// Bind a fresh Unix domain listener on the given filesystem path.
    BindUnix { path: String },
    /// Adopt an already-open listening socket inherited from a supervisor
    /// process.
    InheritedFd { fd: i32 },
}

/// Which coordinator implementation should be active when one is present for a
/// frontend client.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum CoordinatorMode {
    /// Do not run a coordinator at all.
    None,
    /// Run the Rust in-process coordinator for managed `serve` deployments, if
    /// there are multiple engines and the model is MoE.
    MaybeInProc,
    /// Connect to an external coordinator owned by another process.
    External { address: String },
}

/// HTTP/API-server behavior switches that affect route-layer responses.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Default)]
pub struct ApiServerOptions {
    /// Log a summary line for each completed request.
    pub enable_log_requests: bool,
    /// When `true`, include prompt token cache details in response usage.
    pub enable_prompt_tokens_details: bool,
    /// When `true`, set `X-Request-Id` on every HTTP response.
    pub enable_request_id_headers: bool,
}

/// CORS settings mirroring Python's `CORSMiddleware`; the default is permissive.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct CorsConfig {
    /// Allowed origins. `["*"]` allows any origin.
    pub allow_origins: Vec<String>,
    /// Allowed methods. `["*"]` allows the standard method set.
    pub allow_methods: Vec<String>,
    /// Allowed request headers. `["*"]` mirrors the requested headers.
    pub allow_headers: Vec<String>,
    /// Whether to allow credentials (cookies, authorization headers).
    pub allow_credentials: bool,
}

impl Default for CorsConfig {
    fn default() -> Self {
        Self {
            allow_origins: vec!["*".to_string()],
            allow_methods: vec!["*".to_string()],
            allow_headers: vec!["*".to_string()],
            allow_credentials: false,
        }
    }
}

impl CorsConfig {
    /// Validate that non-wildcard values parse into HTTP types, so the CORS
    /// layer can be built infallibly after startup validation has run.
    pub fn validate(&self) -> Result<()> {
        for origin in &self.allow_origins {
            if origin != "*" {
                origin.parse::<HeaderValue>().map_err(|e| {
                    anyhow::anyhow!("invalid --allowed-origins value {origin:?}: {e}")
                })?;
            }
        }
        for method in &self.allow_methods {
            if method != "*" {
                method.parse::<Method>().map_err(|e| {
                    anyhow::anyhow!("invalid --allowed-methods value {method:?}: {e}")
                })?;
            }
        }
        for header in &self.allow_headers {
            if header != "*" {
                header.parse::<HeaderName>().map_err(|e| {
                    anyhow::anyhow!("invalid --allowed-headers value {header:?}: {e}")
                })?;
            }
        }
        Ok(())
    }
}

/// Normalized runtime configuration for the minimal OpenAI-compatible server.
#[derive(Educe, Clone, PartialEq, Eq, Serialize)]
#[educe(Debug)]
pub struct Config {
    /// Frontend-to-engine transport setup.
    pub transport_mode: TransportMode,
    /// Requested frontend-side coordinator behavior.
    pub coordinator_mode: CoordinatorMode,
    /// Backend model identifier used for engine-core loading.
    pub model: String,
    /// Model name(s) exposed to clients via the OpenAI API. When non-empty,
    /// the first entry is used as the primary ID in responses and all entries
    /// are accepted in requests. When empty, falls back to `model`.
    pub served_model_name: Vec<String>,
    /// HTTP listener setup.
    pub listener_mode: HttpListenerMode,
    /// Tool-call parser selection.
    pub tool_call_parser: ParserSelection,
    /// Reasoning parser selection.
    pub reasoning_parser: ParserSelection,
    /// Chat renderer selection.
    pub renderer: RendererSelection,
    /// Disable frontend-side multimodal preprocessing and render the model as
    /// language-only.
    pub language_model_only: bool,
    /// Server-default chat template override, as a file path or inline
    /// template.
    pub chat_template: Option<String>,
    /// Server-default keyword arguments merged into every chat-template render.
    pub default_chat_template_kwargs: Option<HashMap<String, Value>>,
    /// How to serialize `message.content` for chat-template rendering.
    pub chat_template_content_format: ChatTemplateContentFormatOption,
    /// Optional maximum number of top log probabilities accepted by the
    /// frontend. `None` delegates to the text layer default.
    pub max_logprobs: Option<i32>,
    /// HTTP/API-server behavior switches.
    pub api_server_options: ApiServerOptions,
    /// CORS settings applied to every HTTP response.
    pub cors: CorsConfig,
    /// API keys accepted as bearer tokens for guarded routes.
    #[serde(skip_serializing)]
    #[educe(Debug(method(fmt_redacted_api_keys)))]
    pub api_keys: Vec<String>,
    /// When `true`, suppress periodic stats logging (throughput, queue depth,
    /// cache usage).
    pub disable_log_stats: bool,
    /// TCP port for the gRPC Generate service. When `None`, no gRPC server is
    /// started.
    pub grpc_port: Option<u16>,
    /// Maximum time to wait for active HTTP/gRPC requests to drain on shutdown.
    pub shutdown_timeout: Duration,
}

impl Config {
    /// Validate frontend configuration that can be checked before engine
    /// startup.
    pub fn validate(&self) -> Result<()> {
        vllm_chat::validate_parser_overrides(&self.tool_call_parser, &self.reasoning_parser)?;
        self.cors.validate()?;
        if let Some(max_logprobs) = self.max_logprobs
            && max_logprobs < -1
        {
            bail!(
                "max_logprobs must be non-negative or -1, got {}",
                max_logprobs
            );
        }

        Ok(())
    }

    /// Return the number of engines implied by the configured transport mode.
    pub fn engine_count(&self) -> usize {
        match &self.transport_mode {
            TransportMode::HandshakeOwner { engine_count, .. }
            | TransportMode::Bootstrapped { engine_count, .. } => *engine_count,
        }
    }

    /// Resolve the effective coordinator mode.
    pub fn effective_coordinator_mode(
        &self,
        model_is_moe: bool,
    ) -> Option<EngineCoreCoordinatorMode> {
        match &self.coordinator_mode {
            CoordinatorMode::None => None,
            CoordinatorMode::MaybeInProc => {
                if model_is_moe && self.engine_count() > 1 {
                    Some(EngineCoreCoordinatorMode::InProc)
                } else {
                    None
                }
            }
            CoordinatorMode::External { address } => Some(EngineCoreCoordinatorMode::External {
                address: address.clone(),
            }),
        }
    }
}

struct RedactedApiKeys<'a>(&'a [String]);

impl fmt::Debug for RedactedApiKeys<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.0.is_empty() {
            f.debug_list().finish()
        } else {
            write!(f, "[<redacted>; {}]", self.0.len())
        }
    }
}

fn fmt_redacted_api_keys(api_keys: &[String], f: &mut fmt::Formatter<'_>) -> fmt::Result {
    fmt::Debug::fmt(&RedactedApiKeys(api_keys), f)
}
