use std::sync::Arc;

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

use crate::terminal::{PanelPayload, TerminalCommandResponse};

/// 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 remote model used for plain-text terminal chat.
pub const DEFAULT_REMOTE_MODEL: &str = "glm-5.1";
/// Default Ollama OpenAI compatibility endpoint used by the app.
pub const DEFAULT_OLLAMA_BASE_URL: &str = "http://localhost:11434/v1";
/// Placeholder token required by Ollama compatibility endpoints but ignored by Ollama.
pub const DEFAULT_OLLAMA_COMPAT_API_KEY: &str = "ollama";
/// Store file used for agent settings and plaintext API key storage.
pub const AGENT_SETTINGS_STORE_PATH: &str = "agent-settings.json";

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AgentProviderKind {
    Remote,
    Ollama,
}

impl AgentProviderKind {
    pub const fn uses_api_key(self) -> bool {
        matches!(self, Self::Remote)
    }
}

/// 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,
    NoteEnrichment,
    RelationshipInference,
    GhostSynthesis,
    MemoStructuring,
}

/// Request payload for an interactive chat turn.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatPromptRequest {
    pub request_id: String,
    pub workspace_id: String,
    pub session_id: Option<String>,
    pub prompt: String,
    pub agent_profile: Option<TaskProfile>,
    pub model_override: Option<String>,
    pub panel_context: Option<ChatPanelContext>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatPanelContext {
    pub source_command: Option<String>,
    pub captured_at: Option<String>,
    pub panel: PanelPayload,
}

/// Runtime provider configuration after settings resolution.
#[derive(Debug, Clone)]
pub struct AgentRuntimeConfig {
    pub provider_kind: AgentProviderKind,
    pub base_url: String,
    pub model: String,
    pub api_key: Option<String>,
}

/// Prepared chat turn after validation and session history lookup.
#[derive(Debug, Clone)]
pub struct PreparedChatTurn {
    pub workspace_id: String,
    pub session_id: String,
    pub prompt: String,
    pub history: Vec<Message>,
    pub context_messages: Vec<Message>,
    pub runtime: AgentRuntimeConfig,
    pub task_profile: TaskProfile,
}

/// Immediate response returned when a chat stream starts.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatStreamStart {
    pub request_id: String,
    pub session_id: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelChatStreamRequest {
    pub workspace_id: String,
    pub request_id: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AgentStreamItemKind {
    ReasoningDelta,
    TextDelta,
    ToolCommand,
    ToolResult,
    ApprovalRequired,
    StreamComplete,
    Cancelled,
    Error,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentStreamItemEvent {
    pub workspace_id: Arc<str>,
    pub request_id: Arc<str>,
    pub session_id: Arc<str>,
    pub sequence: u64,
    pub kind: AgentStreamItemKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub delta: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub command: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response: Option<TerminalCommandResponse>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub approval_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error_message: Option<String>,
}

/// Frontend request payload for approving or denying an agent-triggered write command.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResolveAgentToolApprovalRequest {
    pub approval_id: String,
    pub approved: bool,
}

/// Persisted settings for the remote chat provider.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AgentStoredSettings {
    pub remote: RemoteProviderSettings,
    pub ollama: OllamaProviderSettings,
    pub task_defaults: HashMap<TaskProfile, AgentTaskRoute>,
    pub sec_edgar_user_agent: String,
    pub require_mapping_approval: bool,
}

impl Default for AgentStoredSettings {
    fn default() -> Self {
        Self {
            remote: RemoteProviderSettings::default(),
            ollama: OllamaProviderSettings::default(),
            task_defaults: default_task_defaults(),
            sec_edgar_user_agent: String::new(),
            require_mapping_approval: false,
        }
    }
}

/// Public provider configuration status returned to the webview.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderConfigStatus {
    pub enabled: bool,
    pub configured: bool,
    pub base_url: String,
    pub default_model: String,
    pub has_api_key: bool,
}

/// Public AI provider status returned to the webview.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentProviderStatuses {
    pub remote: ProviderConfigStatus,
    pub ollama: ProviderConfigStatus,
}

/// Public configuration status returned to the webview.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentConfigStatus {
    pub configured: bool,
    pub providers: AgentProviderStatuses,
    pub task_defaults: HashMap<TaskProfile, AgentTaskRoute>,
    pub has_sec_edgar_user_agent: bool,
    pub sec_edgar_user_agent: String,
    pub require_mapping_approval: bool,
}

/// Request payload for updating persisted non-secret settings.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveAgentRuntimeConfigRequest {
    pub remote: SaveRemoteProviderSettings,
    pub ollama: SaveOllamaProviderSettings,
    pub task_defaults: HashMap<TaskProfile, AgentTaskRoute>,
    pub sec_edgar_user_agent: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveRemoteProviderSettings {
    pub enabled: bool,
    pub base_url: String,
    pub default_model: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveOllamaProviderSettings {
    pub enabled: bool,
    pub base_url: String,
    pub default_model: String,
}

/// Request payload for rotating the stored remote API key.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateRemoteApiKeyRequest {
    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 {
    pub enabled: bool,
    pub base_url: String,
    pub api_key: String,
    pub default_model: String,
}

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

/// Local Ollama provider settings persisted in the application store.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OllamaProviderSettings {
    pub enabled: bool,
    pub base_url: String,
    pub default_model: String,
}

impl Default for OllamaProviderSettings {
    fn default() -> Self {
        Self {
            enabled: false,
            base_url: DEFAULT_OLLAMA_BASE_URL.to_string(),
            default_model: String::new(),
        }
    }
}

/// Default model assignment for a task profile.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AgentTaskRoute {
    pub provider: AgentProviderKind,
    pub model: String,
}

pub fn default_task_defaults() -> HashMap<TaskProfile, AgentTaskRoute> {
    default_task_defaults_for(AgentProviderKind::Remote)
}

pub(crate) fn default_task_defaults_for(
    provider: AgentProviderKind,
) -> HashMap<TaskProfile, AgentTaskRoute> {
    let mut defaults = HashMap::new();
    for task in TaskProfile::all() {
        defaults.insert(
            task,
            AgentTaskRoute {
                provider,
                model: String::new(),
            },
        );
    }
    defaults
}

impl TaskProfile {
    pub const fn all() -> [TaskProfile; 8] {
        [
            TaskProfile::InteractiveChat,
            TaskProfile::Analysis,
            TaskProfile::Summarization,
            TaskProfile::ToolUse,
            TaskProfile::NoteEnrichment,
            TaskProfile::RelationshipInference,
            TaskProfile::GhostSynthesis,
            TaskProfile::MemoStructuring,
        ]
    }
}
