use rig::completion::Message;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Default Z.AI coding plan endpoint used by the app.
pub const DEFAULT_REMOTE_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4";
/// Default model used for plain-text terminal chat.
pub const DEFAULT_REMOTE_MODEL: &str = "glm-5.1";
/// Default local Mistral HTTP sidecar URL.
pub const DEFAULT_LOCAL_BASE_URL: &str = "http://127.0.0.1:1234";
/// Store file used for agent settings and plaintext API key storage.
pub const AGENT_SETTINGS_STORE_PATH: &str = "agent-settings.json";

/// Supported runtime provider modes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ProviderMode {
    Remote,
    Local,
}

/// Stable harness task profiles that can be routed independently.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TaskProfile {
    InteractiveChat,
    Analysis,
    Summarization,
    ToolUse,
}

/// Request payload for an interactive chat turn.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatPromptRequest {
    /// Workspace identifier associated with the request.
    pub workspace_id: String,
    /// Existing session identifier for a continued conversation.
    pub session_id: Option<String>,
    /// User-entered prompt content.
    pub prompt: String,
    /// Optional task profile used to resolve provider and model defaults.
    pub agent_profile: Option<TaskProfile>,
    /// Optional one-off model override for the current request.
    pub model_override: Option<String>,
    /// Optional one-off provider override for the current request.
    pub provider_override: Option<ProviderMode>,
}

/// Runtime provider configuration after settings resolution.
#[derive(Debug, Clone)]
pub struct AgentRuntimeConfig {
    /// Resolved provider mode for this turn.
    pub provider_mode: ProviderMode,
    /// OpenAI-compatible base URL for remote or local Mistral HTTP.
    pub base_url: String,
    /// Upstream model identifier.
    pub model: String,
    /// Optional runtime API key loaded from plaintext application storage.
    pub api_key: Option<String>,
    /// Task profile used to resolve this target.
    pub task_profile: TaskProfile,
}

/// Prepared chat turn after validation and session history lookup.
#[derive(Debug, Clone)]
pub struct PreparedChatTurn {
    /// Workspace identifier associated with the turn.
    pub workspace_id: String,
    /// Stable backend session reused across conversational turns.
    pub session_id: String,
    /// Prompt content after validation and normalization.
    pub prompt: String,
    /// History to send upstream before the new prompt.
    pub history: Vec<Message>,
    /// Resolved provider config for this turn.
    pub runtime: AgentRuntimeConfig,
}

/// Immediate response returned when a chat stream starts.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatStreamStart {
    /// Correlation id for the in-flight stream.
    pub request_id: String,
    /// Session used for this request.
    pub session_id: String,
}

/// Incremental delta emitted while the backend streams a reply.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentDeltaEvent {
    /// Workspace that originated the request.
    pub workspace_id: String,
    /// Correlation id matching the original stream request.
    pub request_id: String,
    /// Session used for this request.
    pub session_id: String,
    /// Incremental text delta to append in the UI.
    pub delta: String,
}

/// Final reply emitted when the backend completes a stream.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentResultEvent {
    /// Workspace that originated the request.
    pub workspace_id: String,
    /// Correlation id matching the original stream request.
    pub request_id: String,
    /// Session used for this request.
    pub session_id: String,
    /// Final reply content for the completed stream.
    pub reply: String,
}

/// Error event emitted when the backend cannot complete a stream.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentErrorEvent {
    /// Workspace that originated the request.
    pub workspace_id: String,
    /// Correlation id matching the original stream request.
    pub request_id: String,
    /// Session used for this request.
    pub session_id: String,
    /// User-visible error message for the failed stream.
    pub message: String,
}

/// Persisted settings for the chat provider, including the plaintext API key.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AgentStoredSettings {
    /// Remote OpenAI-compatible provider configuration.
    pub remote: RemoteProviderSettings,
    /// Local Mistral HTTP provider configuration.
    pub local: LocalProviderSettings,
    /// Default remote model used when a task route does not override it.
    pub default_remote_model: String,
    /// Default route per task profile.
    pub task_defaults: HashMap<TaskProfile, AgentTaskRoute>,
}

impl Default for AgentStoredSettings {
    fn default() -> Self {
        Self {
            remote: RemoteProviderSettings::default(),
            local: LocalProviderSettings::default(),
            default_remote_model: DEFAULT_REMOTE_MODEL.to_string(),
            task_defaults: default_task_defaults(DEFAULT_REMOTE_MODEL),
        }
    }
}

/// Public configuration status returned to the webview.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentConfigStatus {
    /// Whether the app has everything needed to start chat immediately.
    pub configured: bool,
    /// Whether the remote provider has enough config for a routed request.
    pub remote_configured: bool,
    /// Whether the local provider has enough config for a routed request.
    pub local_configured: bool,
    /// Whether the remote provider is enabled in settings.
    pub remote_enabled: bool,
    /// Whether the local provider is enabled in settings.
    pub local_enabled: bool,
    /// Whether a remote API key is currently stored.
    pub has_remote_api_key: bool,
    /// Current remote provider base URL.
    pub remote_base_url: String,
    /// Current local provider base URL.
    pub local_base_url: String,
    /// Current default remote model.
    pub default_remote_model: String,
    /// Current available local model suggestions.
    pub local_available_models: Vec<String>,
    /// Current route defaults per task profile.
    pub task_defaults: HashMap<TaskProfile, AgentTaskRoute>,
}

/// Request payload for updating persisted non-secret settings.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveAgentRuntimeConfigRequest {
    /// Whether the remote provider is enabled.
    pub remote_enabled: bool,
    /// Remote OpenAI-compatible base URL.
    pub remote_base_url: String,
    /// Default model used for remote-routed tasks.
    pub default_remote_model: String,
    /// Whether the local provider is enabled.
    pub local_enabled: bool,
    /// Local Mistral HTTP base URL.
    pub local_base_url: String,
    /// User-provided local model suggestions.
    pub local_available_models: Vec<String>,
    /// Default task routes.
    pub task_defaults: HashMap<TaskProfile, AgentTaskRoute>,
}

/// Request payload for rotating the stored remote API key.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateRemoteApiKeyRequest {
    /// Replacement plaintext API key to store.
    pub api_key: String,
}

/// Remote provider settings persisted in the application store.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RemoteProviderSettings {
    /// Whether the provider can be selected by task routing.
    pub enabled: bool,
    /// OpenAI-compatible base URL.
    pub base_url: String,
    /// Plaintext API key saved in the application store.
    pub api_key: String,
}

impl Default for RemoteProviderSettings {
    fn default() -> Self {
        Self {
            enabled: true,
            base_url: DEFAULT_REMOTE_BASE_URL.to_string(),
            api_key: String::new(),
        }
    }
}

/// Local provider settings persisted in the application store.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LocalProviderSettings {
    /// Whether the local provider can be selected by task routing.
    pub enabled: bool,
    /// Local Mistral HTTP base URL.
    pub base_url: String,
    /// User-provided local model suggestions.
    pub available_models: Vec<String>,
}

impl Default for LocalProviderSettings {
    fn default() -> Self {
        Self {
            enabled: false,
            base_url: DEFAULT_LOCAL_BASE_URL.to_string(),
            available_models: Vec::new(),
        }
    }
}

/// Default provider/model assignment for a task profile.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AgentTaskRoute {
    /// Provider selected for the task by default.
    pub provider_mode: ProviderMode,
    /// Model selected for the task by default.
    pub model: String,
}

/// Response payload for local provider health checks.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalProviderHealthStatus {
    /// Whether the local provider responded successfully.
    pub reachable: bool,
    /// Optional user-visible status detail.
    pub message: String,
}

/// Response payload for local model listing.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalModelList {
    /// Whether model discovery reached the local provider.
    pub reachable: bool,
    /// Unique model names combined from discovery and stored settings.
    pub models: Vec<String>,
}

pub fn default_task_defaults(default_remote_model: &str) -> HashMap<TaskProfile, AgentTaskRoute> {
    let mut defaults = HashMap::new();
    for task in TaskProfile::all() {
        defaults.insert(
            task,
            AgentTaskRoute {
                provider_mode: ProviderMode::Remote,
                model: default_remote_model.to_string(),
            },
        );
    }
    defaults
}

impl TaskProfile {
    pub const fn all() -> [TaskProfile; 4] {
        [
            TaskProfile::InteractiveChat,
            TaskProfile::Analysis,
            TaskProfile::Summarization,
            TaskProfile::ToolUse,
        ]
    }
}
