use std::collections::HashMap;
use std::sync::Arc;

use a3s_boot::{BootError, Result as BootResult};
use a3s_code_core::mcp::{McpServerConfig, McpTransportConfig};
use a3s_code_core::{ModelConfig, ProviderConfig};
use serde::Serialize;
use serde_json::{json, Value};

use crate::api::code_web::state::CodeWebState;
use crate::config;

pub(in crate::api::code_web) struct ConfigService {
    state: Arc<CodeWebState>,
}

impl ConfigService {
    pub(in crate::api::code_web) fn new(state: Arc<CodeWebState>) -> Self {
        Self { state }
    }

    pub(in crate::api::code_web) fn system_info(&self) -> serde_json::Value {
        json!({
            "appName": "书小安",
            "logoUrl": "/logo.png",
            "version": env!("CARGO_PKG_VERSION"),
        })
    }

    pub(in crate::api::code_web) fn assistant_settings(&self) -> serde_json::Value {
        let code_config = self.state.code_config_snapshot();
        json!({
            "name": "书小安",
            "avatar": "",
            "description": "A3S Code local assistant",
            "model": code_config.default_model,
        })
    }

    pub(in crate::api::code_web) fn app_settings(&self) -> serde_json::Value {
        let code_config = self.state.code_config_snapshot();
        let workspace = self.state.default_workspace.display().to_string();
        let storage_path = config::memory_dir().display().to_string();
        json!({
            "general": {
                "appName": "书小安",
                "language": "zh-CN",
                "splashScreen": true,
                "restoreWorkspace": true,
                "workspacePath": workspace,
            },
            "appearance": {
                "theme": "system",
                "sideBarPosition": "left",
                "statusBar": true,
                "activityBar": true,
                "zoomLevel": 1,
            },
            "editor": {},
            "llm": Self::llm_settings_from_config(&code_config),
            "ocr": {
                "defaultBackend": "",
                "backends": [],
            },
            "security": {
                "allowTelemetry": false,
                "checkUpdates": true,
            },
            "network": {
                "connectionTimeout": 30000,
                "readTimeout": 30000,
                "proxyPool": [],
            },
            "search": {
                "enabledEngines": ["ddg"],
                "language": "zh-CN",
                "safesearch": "moderate",
                "timeout": 10,
                "limit": 8,
            },
            "storage": {
                "defaultProvider": "local",
                "localStoragePath": storage_path,
            },
        })
    }

    pub(in crate::api::code_web) fn config_category(
        &self,
        name: &str,
    ) -> BootResult<serde_json::Value> {
        let normalized = normalize_category_name(name);
        if normalized == "llm" || normalized == "ai" {
            let code_config = self.state.code_config_snapshot();
            return Ok(Self::llm_settings_from_config(&code_config));
        }

        let settings = self.app_settings();
        Ok(settings
            .get(normalized)
            .cloned()
            .unwrap_or_else(|| json!({})))
    }

    pub(in crate::api::code_web) fn update_app_settings(
        &self,
        patch: Value,
    ) -> BootResult<serde_json::Value> {
        if let Some(llm) = patch.get("llm").or_else(|| patch.get("ai")) {
            self.update_llm_settings(llm)?;
        }
        Ok(self.app_settings())
    }

    pub(in crate::api::code_web) fn update_config_category(
        &self,
        name: &str,
        patch: Value,
    ) -> BootResult<serde_json::Value> {
        let normalized = normalize_category_name(name);
        if normalized == "llm" || normalized == "ai" {
            self.update_llm_settings(&patch)?;
        }
        self.config_category(normalized)
    }

    pub(in crate::api::code_web) fn llm_diagnostics(&self) -> serde_json::Value {
        let code_config = self.state.code_config_snapshot();
        let default_model = code_config.default_model.clone().unwrap_or_default();
        let default_ref = split_model_ref(&default_model);
        let mut issues = Vec::new();

        if code_config.providers.is_empty() {
            issues.push("no providers are configured".to_string());
        }
        if default_model.is_empty() {
            issues.push("defaultModel is not configured".to_string());
        } else if let Some((provider_name, model_id)) = default_ref.as_ref() {
            match code_config
                .providers
                .iter()
                .find(|provider| provider.name == *provider_name)
            {
                Some(provider) if provider.models.iter().any(|model| model.id == *model_id) => {}
                Some(_) => issues.push(format!(
                    "default model `{model_id}` was not found in provider `{provider_name}`"
                )),
                None => issues.push(format!("default provider `{provider_name}` was not found")),
            }
        }

        let providers = code_config
            .providers
            .iter()
            .map(|provider| {
                json!({
                    "name": provider.name,
                    "modelCount": provider.models.len(),
                    "apiKeyConfigured": has_secret(provider.api_key.as_deref()),
                    "baseUrlConfigured": provider.base_url.as_deref().is_some_and(|value| !value.trim().is_empty()),
                    "isDefault": default_ref
                        .as_ref()
                        .is_some_and(|(provider_name, _)| provider_name == &provider.name),
                })
            })
            .collect::<Vec<_>>();

        json!({
            "ok": issues.is_empty(),
            "defaultModel": default_model,
            "providers": providers,
            "issues": issues,
        })
    }

    pub(in crate::api::code_web) fn fetch_provider_models(
        &self,
        request: Value,
    ) -> BootResult<serde_json::Value> {
        let provider_name = request
            .get("providerName")
            .or_else(|| request.get("provider_name"))
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .ok_or_else(|| BootError::BadRequest("providerName is required".to_string()))?;

        let code_config = self.state.code_config_snapshot();
        let provider = code_config
            .providers
            .iter()
            .find(|provider| provider.name == provider_name);
        let base_url = request
            .get("baseUrl")
            .or_else(|| request.get("base_url"))
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(str::to_string)
            .or_else(|| provider.and_then(|provider| provider.base_url.clone()))
            .unwrap_or_default();
        let models = provider
            .map(|provider| {
                provider
                    .models
                    .iter()
                    .map(|model| {
                        json!({
                            "id": model.id,
                            "name": display_model_name(model),
                        })
                    })
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default();

        Ok(json!({
            "providerName": provider_name,
            "baseUrl": base_url,
            "models": models,
        }))
    }

    fn llm_settings_from_config(config: &a3s_code_core::CodeConfig) -> serde_json::Value {
        json!({
            "defaultModel": config.default_model.clone().unwrap_or_default(),
            "providers": config
                .providers
                .iter()
                .map(provider_settings)
                .collect::<Vec<_>>(),
            "mcpServers": sanitized_json_value(&config.mcp_servers),
            "maxToolRounds": config.max_tool_rounds,
            "maxParallelTasks": config.max_parallel_tasks,
            "autoParallel": config.auto_parallel,
            "thinkingBudget": config.thinking_budget,
            "llmApiTimeoutMs": config.llm_api_timeout_ms,
        })
    }

    fn update_llm_settings(&self, value: &Value) -> BootResult<()> {
        let mut config = self
            .state
            .code_config
            .write()
            .map_err(|_| BootError::Internal("code config lock was poisoned".to_string()))?;

        if let Some(default_model) = value
            .get("defaultModel")
            .or_else(|| value.get("default_model"))
            .and_then(Value::as_str)
            .map(str::trim)
        {
            config.default_model = (!default_model.is_empty()).then(|| default_model.to_string());
        }

        if let Some(providers_value) = value.get("providers") {
            let mut providers: Vec<ProviderConfig> =
                serde_json::from_value(providers_value.clone()).map_err(|error| {
                    BootError::BadRequest(format!("invalid llm.providers: {error}"))
                })?;
            preserve_provider_secrets(&mut providers, &config.providers);
            config.providers = providers;
        }

        if let Some(mcp_servers_value) =
            value.get("mcpServers").or_else(|| value.get("mcp_servers"))
        {
            let mut servers: Vec<McpServerConfig> =
                serde_json::from_value(mcp_servers_value.clone()).map_err(|error| {
                    BootError::BadRequest(format!("invalid llm.mcpServers: {error}"))
                })?;
            preserve_mcp_secrets(&mut servers, &config.mcp_servers);
            config.mcp_servers = servers;
        }

        if let Some(max_tool_rounds) = optional_usize(
            value
                .get("maxToolRounds")
                .or_else(|| value.get("max_tool_rounds")),
        ) {
            config.max_tool_rounds = Some(max_tool_rounds);
        }
        if let Some(max_parallel_tasks) = optional_usize(
            value
                .get("maxParallelTasks")
                .or_else(|| value.get("max_parallel_tasks")),
        ) {
            config.max_parallel_tasks = Some(max_parallel_tasks);
        }
        if let Some(thinking_budget) = optional_usize(
            value
                .get("thinkingBudget")
                .or_else(|| value.get("thinking_budget")),
        ) {
            config.thinking_budget = Some(thinking_budget);
        }
        if let Some(timeout_ms) = optional_u64(
            value
                .get("llmApiTimeoutMs")
                .or_else(|| value.get("llm_api_timeout_ms")),
        ) {
            config.llm_api_timeout_ms = Some(timeout_ms);
        }
        if let Some(auto_parallel) = optional_bool(
            value
                .get("autoParallel")
                .or_else(|| value.get("auto_parallel")),
        ) {
            config.auto_parallel = Some(auto_parallel);
        }

        Ok(())
    }
}

const REDACTED_SECRET: &str = "[configured]";

fn normalize_category_name(name: &str) -> &str {
    name.trim()
}

fn provider_settings(provider: &ProviderConfig) -> serde_json::Value {
    json!({
        "name": provider.name,
        "apiKey": redact_secret(provider.api_key.as_deref()),
        "baseUrl": provider.base_url,
        "headers": redact_headers(&provider.headers),
        "sessionIdHeader": provider.session_id_header,
        "models": provider
            .models
            .iter()
            .map(model_settings)
            .collect::<Vec<_>>(),
    })
}

fn model_settings(model: &ModelConfig) -> serde_json::Value {
    json!({
        "id": model.id,
        "name": display_model_name(model),
        "family": optional_text(&model.family),
        "apiKey": redact_secret(model.api_key.as_deref()),
        "baseUrl": model.base_url,
        "headers": redact_headers(&model.headers),
        "sessionIdHeader": model.session_id_header,
        "attachment": model.attachment,
        "reasoning": model.reasoning,
        "toolCall": model.tool_call,
        "temperature": model.temperature,
        "releaseDate": model.release_date,
        "modalities": {
            "input": model.modalities.input,
            "output": model.modalities.output,
        },
        "cost": {
            "input": model.cost.input,
            "output": model.cost.output,
            "cacheRead": model.cost.cache_read,
            "cacheWrite": model.cost.cache_write,
        },
        "limit": {
            "context": model.limit.context,
            "output": model.limit.output,
        },
    })
}

fn display_model_name(model: &ModelConfig) -> &str {
    optional_text(&model.name).unwrap_or(model.id.as_str())
}

fn optional_text(value: &str) -> Option<&str> {
    let trimmed = value.trim();
    (!trimmed.is_empty()).then_some(trimmed)
}

fn redact_secret(value: Option<&str>) -> Option<&'static str> {
    value
        .filter(|secret| has_secret(Some(secret)))
        .map(|_| REDACTED_SECRET)
}

fn has_secret(value: Option<&str>) -> bool {
    value.is_some_and(|secret| !secret.trim().is_empty())
}

fn redact_headers(headers: &HashMap<String, String>) -> HashMap<String, String> {
    headers
        .iter()
        .map(|(key, value)| {
            if is_sensitive_key(key) && !value.trim().is_empty() {
                (key.clone(), REDACTED_SECRET.to_string())
            } else {
                (key.clone(), value.clone())
            }
        })
        .collect()
}

fn sanitized_json_value<T: Serialize>(value: &T) -> serde_json::Value {
    let mut value = serde_json::to_value(value).unwrap_or_else(|_| json!([]));
    redact_secrets_in_value(&mut value);
    value
}

fn redact_secrets_in_value(value: &mut Value) {
    match value {
        Value::Object(object) => {
            for (key, child) in object.iter_mut() {
                if is_sensitive_key(key) {
                    if child.as_str().is_some_and(|text| !text.trim().is_empty()) {
                        *child = Value::String(REDACTED_SECRET.to_string());
                    } else {
                        redact_secrets_in_value(child);
                    }
                } else {
                    redact_secrets_in_value(child);
                }
            }
        }
        Value::Array(items) => {
            for child in items {
                redact_secrets_in_value(child);
            }
        }
        _ => {}
    }
}

fn is_sensitive_key(key: &str) -> bool {
    let normalized = key
        .chars()
        .filter(|ch| ch.is_ascii_alphanumeric())
        .flat_map(|ch| ch.to_lowercase())
        .collect::<String>();
    normalized.contains("apikey")
        || normalized.contains("authorization")
        || normalized.contains("accesstoken")
        || normalized.contains("refreshtoken")
        || normalized.contains("clientsecret")
        || normalized.contains("secretkey")
        || normalized == "token"
        || normalized == "password"
        || normalized.ends_with("token")
        || normalized.ends_with("secret")
}

fn split_model_ref(value: &str) -> Option<(String, String)> {
    let (provider, model) = value.split_once('/')?;
    let provider = provider.trim();
    let model = model.trim();
    if provider.is_empty() || model.is_empty() {
        None
    } else {
        Some((provider.to_string(), model.to_string()))
    }
}

fn optional_usize(value: Option<&Value>) -> Option<usize> {
    match value {
        Some(Value::Number(number)) => number.as_u64().map(|value| value as usize),
        Some(Value::String(text)) => text.trim().parse().ok(),
        _ => None,
    }
}

fn optional_u64(value: Option<&Value>) -> Option<u64> {
    match value {
        Some(Value::Number(number)) => number.as_u64(),
        Some(Value::String(text)) => text.trim().parse().ok(),
        _ => None,
    }
}

fn optional_bool(value: Option<&Value>) -> Option<bool> {
    match value {
        Some(Value::Bool(value)) => Some(*value),
        Some(Value::String(text)) => match text.trim().to_ascii_lowercase().as_str() {
            "true" | "yes" | "1" => Some(true),
            "false" | "no" | "0" => Some(false),
            _ => None,
        },
        _ => None,
    }
}

fn preserve_provider_secrets(providers: &mut [ProviderConfig], existing: &[ProviderConfig]) {
    for provider in providers {
        let Some(previous) = existing.iter().find(|item| item.name == provider.name) else {
            continue;
        };
        preserve_secret(&mut provider.api_key, previous.api_key.as_deref());
        preserve_headers(&mut provider.headers, &previous.headers);
        for model in &mut provider.models {
            let Some(previous_model) = previous.models.iter().find(|item| item.id == model.id)
            else {
                continue;
            };
            preserve_secret(&mut model.api_key, previous_model.api_key.as_deref());
            preserve_headers(&mut model.headers, &previous_model.headers);
        }
    }
}

fn preserve_mcp_secrets(servers: &mut [McpServerConfig], existing: &[McpServerConfig]) {
    for server in servers {
        let Some(previous) = existing.iter().find(|item| item.name == server.name) else {
            continue;
        };
        preserve_headers(&mut server.env, &previous.env);
        preserve_mcp_transport_secrets(&mut server.transport, &previous.transport);
    }
}

fn preserve_mcp_transport_secrets(
    transport: &mut McpTransportConfig,
    previous: &McpTransportConfig,
) {
    match (transport, previous) {
        (
            McpTransportConfig::Http { headers, .. },
            McpTransportConfig::Http {
                headers: previous_headers,
                ..
            },
        )
        | (
            McpTransportConfig::StreamableHttp { headers, .. },
            McpTransportConfig::StreamableHttp {
                headers: previous_headers,
                ..
            },
        ) => preserve_headers(headers, previous_headers),
        _ => {}
    }
}

fn preserve_secret(value: &mut Option<String>, previous: Option<&str>) {
    let should_restore = match value.as_deref().map(str::trim) {
        None | Some("") => previous.is_some_and(|text| !text.trim().is_empty()),
        Some(REDACTED_SECRET) => true,
        Some(_) => false,
    };
    if should_restore {
        *value = previous.map(str::to_string);
    }
}

fn preserve_headers(headers: &mut HashMap<String, String>, previous: &HashMap<String, String>) {
    for (key, previous_value) in previous {
        match headers.get_mut(key) {
            Some(value) if value.trim() == REDACTED_SECRET => {
                *value = previous_value.clone();
            }
            Some(_) => {}
            None if !previous_value.trim().is_empty() => {
                headers.insert(key.clone(), previous_value.clone());
            }
            None => {}
        }
    }
}
