use std::collections::HashMap;

use serde::Deserialize;
use serde_json::json;
use tauri::{AppHandle, Runtime};
use tauri_plugin_store::StoreExt;

use crate::agent::{
    default_task_defaults, AgentProviderKind, AgentStoredSettings, AgentTaskRoute,
    OllamaProviderSettings, RemoteProviderSettings, AGENT_SETTINGS_STORE_PATH,
    DEFAULT_OLLAMA_BASE_URL, DEFAULT_REMOTE_BASE_URL, DEFAULT_REMOTE_MODEL,
};
use crate::error::AppError;

const REMOTE_ENABLED_KEY: &str = "remoteEnabled";
const REMOTE_BASE_URL_KEY: &str = "remoteBaseUrl";
const REMOTE_API_KEY_KEY: &str = "remoteApiKey";
const REMOTE_DEFAULT_MODEL_KEY: &str = "remoteDefaultModel";
const OLLAMA_ENABLED_KEY: &str = "ollamaEnabled";
const OLLAMA_BASE_URL_KEY: &str = "ollamaBaseUrl";
const OLLAMA_DEFAULT_MODEL_KEY: &str = "ollamaDefaultModel";
const TASK_DEFAULTS_KEY: &str = "taskDefaults";
const SEC_EDGAR_USER_AGENT_KEY: &str = "secEdgarUserAgent";

const LEGACY_PROVIDER_KIND_KEY: &str = "providerKind";
const LEGACY_DEFAULT_REMOTE_MODEL_KEY: &str = "defaultRemoteModel";
const LEGACY_BASE_URL_KEY: &str = "baseUrl";
const LEGACY_MODEL_KEY: &str = "model";
const LEGACY_API_KEY_KEY: &str = "apiKey";
const LOCAL_ENABLED_KEY: &str = "localEnabled";
const LOCAL_BASE_URL_KEY: &str = "localBaseUrl";
const LOCAL_AVAILABLE_MODELS_KEY: &str = "localAvailableModels";

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LegacyAgentTaskRoute {
    model: String,
}

/// Manages the provider settings and plaintext API key stored through the Tauri store plugin.
#[derive(Debug)]
pub struct AgentSettingsService<R: Runtime> {
    app_handle: AppHandle<R>,
}

impl<R: Runtime> Clone for AgentSettingsService<R> {
    fn clone(&self) -> Self {
        Self {
            app_handle: self.app_handle.clone(),
        }
    }
}

impl<R: Runtime> AgentSettingsService<R> {
    pub fn new(app_handle: &AppHandle<R>) -> Self {
        Self {
            app_handle: app_handle.clone(),
        }
    }

    pub fn load(&self) -> Result<AgentStoredSettings, AppError> {
        let store = self
            .app_handle
            .store(AGENT_SETTINGS_STORE_PATH)
            .map_err(|error| AppError::SettingsStore(error.to_string()))?;

        let has_new_schema = store.get(REMOTE_DEFAULT_MODEL_KEY).is_some()
            || store.get(OLLAMA_ENABLED_KEY).is_some()
            || store.get(OLLAMA_BASE_URL_KEY).is_some()
            || store.get(OLLAMA_DEFAULT_MODEL_KEY).is_some();

        let legacy_provider = store
            .get(LEGACY_PROVIDER_KIND_KEY)
            .and_then(|value| serde_json::from_value(value.clone()).ok())
            .unwrap_or(AgentProviderKind::Remote);

        let remote_api_key = store
            .get(REMOTE_API_KEY_KEY)
            .and_then(|value| value.as_str().map(ToOwned::to_owned))
            .or_else(|| {
                store
                    .get(LEGACY_API_KEY_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
            })
            .unwrap_or_default();

        let legacy_base_url = store
            .get(REMOTE_BASE_URL_KEY)
            .and_then(|value| value.as_str().map(ToOwned::to_owned))
            .or_else(|| {
                store
                    .get(LEGACY_BASE_URL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
            });

        let legacy_default_model = store
            .get(LEGACY_DEFAULT_REMOTE_MODEL_KEY)
            .and_then(|value| value.as_str().map(ToOwned::to_owned))
            .or_else(|| {
                store
                    .get(LEGACY_MODEL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
            });

        let (remote, ollama, migrated_provider) = if has_new_schema {
            let remote = RemoteProviderSettings {
                enabled: store
                    .get(REMOTE_ENABLED_KEY)
                    .and_then(|value| value.as_bool())
                    .unwrap_or(true),
                base_url: store
                    .get(REMOTE_BASE_URL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
                    .unwrap_or_else(|| DEFAULT_REMOTE_BASE_URL.to_string()),
                api_key: remote_api_key,
                default_model: store
                    .get(REMOTE_DEFAULT_MODEL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
                    .unwrap_or_else(|| DEFAULT_REMOTE_MODEL.to_string()),
            };
            let ollama = OllamaProviderSettings {
                enabled: store
                    .get(OLLAMA_ENABLED_KEY)
                    .and_then(|value| value.as_bool())
                    .unwrap_or(false),
                base_url: store
                    .get(OLLAMA_BASE_URL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
                    .unwrap_or_else(|| DEFAULT_OLLAMA_BASE_URL.to_string()),
                default_model: store
                    .get(OLLAMA_DEFAULT_MODEL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
                    .unwrap_or_default(),
            };
            (remote, ollama, AgentProviderKind::Remote)
        } else {
            let legacy_enabled = store
                .get(REMOTE_ENABLED_KEY)
                .and_then(|value| value.as_bool())
                .unwrap_or(true);
            let legacy_base_url =
                legacy_base_url.unwrap_or_else(|| DEFAULT_REMOTE_BASE_URL.to_string());
            let legacy_default_model =
                legacy_default_model.unwrap_or_else(|| DEFAULT_REMOTE_MODEL.to_string());

            match legacy_provider {
                AgentProviderKind::Remote => (
                    RemoteProviderSettings {
                        enabled: legacy_enabled,
                        base_url: legacy_base_url,
                        api_key: remote_api_key,
                        default_model: legacy_default_model,
                    },
                    OllamaProviderSettings::default(),
                    AgentProviderKind::Remote,
                ),
                AgentProviderKind::Ollama => (
                    RemoteProviderSettings {
                        api_key: remote_api_key,
                        ..RemoteProviderSettings::default()
                    },
                    OllamaProviderSettings {
                        enabled: legacy_enabled,
                        base_url: normalize_legacy_ollama_base_url(&legacy_base_url),
                        default_model: legacy_default_model,
                    },
                    AgentProviderKind::Ollama,
                ),
            }
        };

        let task_defaults = load_task_defaults(
            store.get(TASK_DEFAULTS_KEY).as_ref(),
            migrated_provider,
            has_new_schema,
        );

        Ok(AgentStoredSettings {
            remote,
            ollama,
            task_defaults,
            sec_edgar_user_agent: store
                .get(SEC_EDGAR_USER_AGENT_KEY)
                .and_then(|value| value.as_str().map(ToOwned::to_owned))
                .unwrap_or_default(),
            require_mapping_approval: store
                .get("requireMappingApproval")
                .and_then(|v| v.as_bool())
                .unwrap_or(false),
        })
    }

    pub fn save(&self, settings: AgentStoredSettings) -> Result<AgentStoredSettings, AppError> {
        self.save_inner(&settings)?;
        Ok(settings)
    }

    pub fn set_remote_api_key(&self, api_key: String) -> Result<AgentStoredSettings, AppError> {
        let mut settings = self.load()?;
        settings.remote.api_key = api_key;
        self.save_inner(&settings)?;
        Ok(settings)
    }

    fn save_inner(&self, settings: &AgentStoredSettings) -> Result<(), AppError> {
        let store = self
            .app_handle
            .store(AGENT_SETTINGS_STORE_PATH)
            .map_err(|error| AppError::SettingsStore(error.to_string()))?;

        store.set(REMOTE_ENABLED_KEY, json!(settings.remote.enabled));
        store.set(REMOTE_BASE_URL_KEY, json!(settings.remote.base_url));
        store.set(REMOTE_API_KEY_KEY, json!(settings.remote.api_key));
        store.set(
            REMOTE_DEFAULT_MODEL_KEY,
            json!(settings.remote.default_model),
        );

        store.set(OLLAMA_ENABLED_KEY, json!(settings.ollama.enabled));
        store.set(OLLAMA_BASE_URL_KEY, json!(settings.ollama.base_url));
        store.set(
            OLLAMA_DEFAULT_MODEL_KEY,
            json!(settings.ollama.default_model),
        );

        store.set(TASK_DEFAULTS_KEY, json!(settings.task_defaults));
        store.set(
            SEC_EDGAR_USER_AGENT_KEY,
            json!(settings.sec_edgar_user_agent),
        );

        store.delete(LEGACY_PROVIDER_KIND_KEY);
        store.delete(LEGACY_BASE_URL_KEY);
        store.delete(LEGACY_MODEL_KEY);
        store.delete(LEGACY_API_KEY_KEY);
        store.delete(LEGACY_DEFAULT_REMOTE_MODEL_KEY);
        store.delete(LOCAL_ENABLED_KEY);
        store.delete(LOCAL_BASE_URL_KEY);
        store.delete(LOCAL_AVAILABLE_MODELS_KEY);

        store
            .save()
            .map_err(|error| AppError::SettingsStore(error.to_string()))
    }
}

fn load_task_defaults(
    raw_value: Option<&serde_json::Value>,
    migrated_provider: AgentProviderKind,
    has_new_schema: bool,
) -> HashMap<crate::agent::TaskProfile, AgentTaskRoute> {
    let Some(value) = raw_value else {
        return if has_new_schema {
            default_task_defaults()
        } else {
            super::types::default_task_defaults_for(migrated_provider)
        };
    };

    if let Ok(routes) =
        serde_json::from_value::<HashMap<crate::agent::TaskProfile, AgentTaskRoute>>(value.clone())
    {
        return merge_task_defaults(default_task_defaults(), routes);
    }

    if let Ok(legacy_routes) = serde_json::from_value::<
        HashMap<crate::agent::TaskProfile, LegacyAgentTaskRoute>,
    >(value.clone())
    {
        let routes = legacy_routes
            .into_iter()
            .map(|(task, route)| {
                (
                    task,
                    AgentTaskRoute {
                        provider: migrated_provider,
                        model: route.model,
                    },
                )
            })
            .collect::<HashMap<_, _>>();
        return merge_task_defaults(
            super::types::default_task_defaults_for(migrated_provider),
            routes,
        );
    }

    if has_new_schema {
        default_task_defaults()
    } else {
        super::types::default_task_defaults_for(migrated_provider)
    }
}

fn merge_task_defaults(
    mut defaults: HashMap<crate::agent::TaskProfile, AgentTaskRoute>,
    overrides: HashMap<crate::agent::TaskProfile, AgentTaskRoute>,
) -> HashMap<crate::agent::TaskProfile, AgentTaskRoute> {
    defaults.extend(overrides);
    defaults
}

fn normalize_legacy_ollama_base_url(base_url: &str) -> String {
    let trimmed = base_url.trim();
    if trimmed.is_empty() {
        return DEFAULT_OLLAMA_BASE_URL.to_string();
    }

    if trimmed.ends_with("/v1") {
        trimmed.to_string()
    } else {
        format!("{}/v1", trimmed.trim_end_matches('/'))
    }
}
