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

use crate::agent::{
    default_task_defaults, AgentStoredSettings, LocalProviderSettings, RemoteProviderSettings,
    AGENT_SETTINGS_STORE_PATH, DEFAULT_LOCAL_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 DEFAULT_REMOTE_MODEL_KEY: &str = "defaultRemoteModel";
const LOCAL_ENABLED_KEY: &str = "localEnabled";
const LOCAL_BASE_URL_KEY: &str = "localBaseUrl";
const LOCAL_AVAILABLE_MODELS_KEY: &str = "localAvailableModels";
const TASK_DEFAULTS_KEY: &str = "taskDefaults";
const LEGACY_BASE_URL_KEY: &str = "baseUrl";
const LEGACY_MODEL_KEY: &str = "model";
const LEGACY_API_KEY_KEY: &str = "apiKey";

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

impl<R: Runtime> AgentSettingsService<R> {
    /// Create a new settings service for the provided application handle.
    pub fn new(app_handle: &AppHandle<R>) -> Self {
        Self {
            app_handle: app_handle.clone(),
        }
    }

    /// Load the current agent settings, falling back to app defaults when unset.
    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 default_remote_model = store
            .get(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))
            })
            .unwrap_or_else(|| DEFAULT_REMOTE_MODEL.to_string());

        let task_defaults = store
            .get(TASK_DEFAULTS_KEY)
            .and_then(|value| serde_json::from_value(value.clone()).ok())
            .unwrap_or_else(|| default_task_defaults(&default_remote_model));

        Ok(AgentStoredSettings {
            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))
                    .or_else(|| {
                        store
                            .get(LEGACY_BASE_URL_KEY)
                            .and_then(|value| value.as_str().map(ToOwned::to_owned))
                    })
                    .unwrap_or_else(|| DEFAULT_REMOTE_BASE_URL.to_string()),
                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(),
            },
            local: LocalProviderSettings {
                enabled: store
                    .get(LOCAL_ENABLED_KEY)
                    .and_then(|value| value.as_bool())
                    .unwrap_or(false),
                base_url: store
                    .get(LOCAL_BASE_URL_KEY)
                    .and_then(|value| value.as_str().map(ToOwned::to_owned))
                    .unwrap_or_else(|| DEFAULT_LOCAL_BASE_URL.to_string()),
                available_models: store
                    .get(LOCAL_AVAILABLE_MODELS_KEY)
                    .and_then(|value| serde_json::from_value(value.clone()).ok())
                    .unwrap_or_default(),
            },
            default_remote_model,
            task_defaults,
        })
    }

    /// Persist the current settings, including the plaintext API key.
    pub fn save(&self, settings: AgentStoredSettings) -> Result<AgentStoredSettings, AppError> {
        self.save_inner(&settings)?;
        Ok(settings)
    }

    /// Update only the plaintext remote API key.
    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.to_string(),
            json!(settings.remote.enabled),
        );
        store.set(
            REMOTE_BASE_URL_KEY.to_string(),
            json!(settings.remote.base_url),
        );
        store.set(
            REMOTE_API_KEY_KEY.to_string(),
            json!(settings.remote.api_key),
        );
        store.set(
            DEFAULT_REMOTE_MODEL_KEY.to_string(),
            json!(settings.default_remote_model),
        );
        store.set(LOCAL_ENABLED_KEY.to_string(), json!(settings.local.enabled));
        store.set(
            LOCAL_BASE_URL_KEY.to_string(),
            json!(settings.local.base_url),
        );
        store.set(
            LOCAL_AVAILABLE_MODELS_KEY.to_string(),
            json!(settings.local.available_models),
        );
        store.set(TASK_DEFAULTS_KEY.to_string(), json!(settings.task_defaults));

        // Remove legacy flat keys after writing the new schema.
        store.delete(LEGACY_BASE_URL_KEY);
        store.delete(LEGACY_MODEL_KEY);
        store.delete(LEGACY_API_KEY_KEY);
        store
            .save()
            .map_err(|error| AppError::SettingsStore(error.to_string()))
    }
}
