use crate::agent::{
    AgentProviderKind, AgentRuntimeConfig, AgentStoredSettings, AgentTaskRoute, TaskProfile,
    DEFAULT_OLLAMA_COMPAT_API_KEY,
};
use crate::error::AppError;

pub fn resolve_runtime(
    settings: &AgentStoredSettings,
    task_profile: TaskProfile,
    model_override: Option<String>,
) -> Result<AgentRuntimeConfig, AppError> {
    let route = settings
        .task_defaults
        .get(&task_profile)
        .ok_or(AppError::TaskRouteMissing(task_profile))?;
    let provider = provider_settings(settings, route.provider);

    if !provider.enabled() {
        return Err(AppError::ProviderNotConfigured);
    }

    let model = resolve_model(settings, task_profile, route, model_override)?;
    let api_key = match route.provider {
        AgentProviderKind::Remote => {
            let api_key = settings.remote.api_key.trim().to_string();
            if api_key.is_empty() {
                return Err(AppError::RemoteApiKeyMissing);
            }
            Some(api_key)
        }
        AgentProviderKind::Ollama => Some(DEFAULT_OLLAMA_COMPAT_API_KEY.to_string()),
    };

    Ok(AgentRuntimeConfig {
        provider_kind: route.provider,
        base_url: provider.base_url().to_string(),
        model,
        api_key,
    })
}

pub fn validate_settings(settings: &AgentStoredSettings) -> Result<(), AppError> {
    for task in TaskProfile::all() {
        let route = settings
            .task_defaults
            .get(&task)
            .ok_or(AppError::TaskRouteMissing(task))?;
        let provider = provider_settings(settings, route.provider);

        if !provider.enabled() {
            return Err(AppError::InvalidSettings(format!(
                "{} is routed to disabled provider {}",
                task_label(task),
                provider.label()
            )));
        }

        if provider.default_model().trim().is_empty() && route.model.trim().is_empty() {
            return Err(AppError::InvalidSettings(format!(
                "{} is missing a model. Set a task model or a default model for {}.",
                task_label(task),
                provider.label()
            )));
        }

        if route.provider == AgentProviderKind::Remote && settings.remote.api_key.trim().is_empty()
        {
            return Err(AppError::InvalidSettings(format!(
                "{} is routed to Remote, but the Remote API key is missing.",
                task_label(task)
            )));
        }
    }

    Ok(())
}

pub fn normalize_routes(settings: &mut AgentStoredSettings) -> Result<(), AppError> {
    if settings.task_defaults.is_empty() {
        settings.task_defaults = crate::agent::default_task_defaults();
    }

    for task in TaskProfile::all() {
        let route = settings
            .task_defaults
            .entry(task)
            .or_insert_with(|| AgentTaskRoute {
                provider: AgentProviderKind::Remote,
                model: String::new(),
            });
        route.model = route.model.trim().to_string();
    }

    settings.remote.base_url = settings.remote.base_url.trim().to_string();
    settings.remote.default_model = settings.remote.default_model.trim().to_string();
    settings.ollama.base_url = settings.ollama.base_url.trim().to_string();
    settings.ollama.default_model = settings.ollama.default_model.trim().to_string();

    Ok(())
}

pub fn compute_provider_configured(
    settings: &AgentStoredSettings,
    provider_kind: AgentProviderKind,
) -> bool {
    let provider = provider_settings(settings, provider_kind);

    provider.enabled()
        && !provider.base_url().trim().is_empty()
        && !provider.default_model().trim().is_empty()
        && (!provider_kind.uses_api_key() || !settings.remote.api_key.trim().is_empty())
}

pub fn compute_overall_configured(settings: &AgentStoredSettings) -> bool {
    TaskProfile::all()
        .iter()
        .copied()
        .all(|task| resolve_runtime(settings, task, None).is_ok())
}

fn resolve_model(
    settings: &AgentStoredSettings,
    task_profile: TaskProfile,
    route: &AgentTaskRoute,
    model_override: Option<String>,
) -> Result<String, AppError> {
    if let Some(model) = model_override {
        let trimmed = model.trim();
        if trimmed.is_empty() {
            return Err(AppError::ModelMissing(task_profile));
        }
        return Ok(trimmed.to_string());
    }

    let route_model = route.model.trim();
    if !route_model.is_empty() {
        return Ok(route_model.to_string());
    }

    let provider = provider_settings(settings, route.provider);
    let provider_model = provider.default_model().trim();
    if provider_model.is_empty() {
        return Err(AppError::ModelMissing(task_profile));
    }

    Ok(provider_model.to_string())
}

fn task_label(task: TaskProfile) -> &'static str {
    match task {
        TaskProfile::InteractiveChat => "Interactive Chat",
        TaskProfile::Analysis => "Analysis",
        TaskProfile::Summarization => "Summarization",
        TaskProfile::ToolUse => "Tool Use",
        TaskProfile::NoteEnrichment => "Note Enrichment",
        TaskProfile::RelationshipInference => "Relationship Inference",
        TaskProfile::GhostSynthesis => "Ghost Synthesis",
        TaskProfile::MemoStructuring => "Memo Structuring",
    }
}

enum ProviderSettingsRef<'a> {
    Remote(&'a crate::agent::RemoteProviderSettings),
    Ollama(&'a crate::agent::OllamaProviderSettings),
}

impl ProviderSettingsRef<'_> {
    fn enabled(&self) -> bool {
        match self {
            Self::Remote(settings) => settings.enabled,
            Self::Ollama(settings) => settings.enabled,
        }
    }

    fn base_url(&self) -> &str {
        match self {
            Self::Remote(settings) => &settings.base_url,
            Self::Ollama(settings) => &settings.base_url,
        }
    }

    fn default_model(&self) -> &str {
        match self {
            Self::Remote(settings) => &settings.default_model,
            Self::Ollama(settings) => &settings.default_model,
        }
    }
    fn label(&self) -> &'static str {
        match self {
            Self::Remote(_) => "Remote",
            Self::Ollama(_) => "Local (Ollama)",
        }
    }
}

fn provider_settings<'a>(
    settings: &'a AgentStoredSettings,
    provider: AgentProviderKind,
) -> ProviderSettingsRef<'a> {
    match provider {
        AgentProviderKind::Remote => ProviderSettingsRef::Remote(&settings.remote),
        AgentProviderKind::Ollama => ProviderSettingsRef::Ollama(&settings.ollama),
    }
}
