use anyhow::{Context, Result, bail};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use std::collections::BTreeMap;

use crate::config::McpServerConfig;
use crate::mcp::McpServerSummary;

use super::App;

#[derive(Clone, Debug)]
pub struct McpPanelState {
    pub selected_index: usize,
    pub(crate) editor: Option<McpServerEditorState>,
}

impl McpPanelState {
    pub fn new() -> Self {
        Self {
            selected_index: 0,
            editor: None,
        }
    }

    pub fn move_selection(&mut self, items: &[McpPanelItem], delta: isize) {
        if items.is_empty() {
            self.selected_index = 0;
            return;
        }

        let len = items.len() as isize;
        let current = self.selected_index.min(items.len().saturating_sub(1)) as isize;
        let next = (current + delta).rem_euclid(len) as usize;
        self.selected_index = next;
    }

    pub fn selected_item<'a>(&self, items: &'a [McpPanelItem]) -> Option<&'a McpPanelItem> {
        items.get(self.selected_index)
    }

    pub fn begin_create_editor(&mut self, previous_query: String) {
        self.editor = Some(McpServerEditorState::new_create(previous_query));
    }

    pub fn begin_edit_editor(
        &mut self,
        previous_query: String,
        original_name: String,
        config: &McpServerConfig,
    ) {
        self.editor = Some(McpServerEditorState::new_edit(
            previous_query,
            original_name,
            config,
        ));
    }

    pub fn clear_editor(&mut self) -> Option<String> {
        self.editor.take().map(|editor| editor.previous_query)
    }
}

impl Default for McpPanelState {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Debug)]
pub struct McpPanelItem {
    pub summary: McpServerSummary,
}

impl McpPanelItem {
    pub fn is_selectable(&self) -> bool {
        true
    }
}

impl App {
    pub(crate) fn open_mcp_panel(&mut self, initial_query: String) {
        self.command_palette.clear();
        self.connect_dialog = None;
        self.theme_panel = None;
        self.model_panel = None;
        self.session_panel = None;
        self.composer.clear();
        self.composer
            .set_placeholder("Search MCP servers by name or transport");
        self.composer.set_text(initial_query);

        let mut panel = McpPanelState::new();
        let items = self.mcp_panel_items();
        panel.selected_index = first_selectable_index(&items).unwrap_or(0);
        self.mcp_panel = Some(panel);
    }

    pub(crate) fn close_mcp_panel(&mut self) {
        if self.mcp_panel.take().is_some() {
            self.composer.clear();
            self.composer
                .set_placeholder("Ask TiDev about your code, task, or question...");
        }
    }

    pub(crate) fn reset_mcp_panel_selection(&mut self) {
        let items = self.mcp_panel_items();
        if let Some(panel) = &mut self.mcp_panel {
            panel.selected_index = first_selectable_index(&items).unwrap_or(0);
        }
    }

    pub(crate) fn handle_mcp_panel_key(
        &mut self,
        key: KeyEvent,
        runtime: &tokio::runtime::Runtime,
    ) -> Result<()> {
        let Some(mut panel) = self.mcp_panel.clone() else {
            return Ok(());
        };

        if let Some(mut editor) = panel.editor.clone() {
            let input_before = self.composer.text().to_string();

            match key.code {
                KeyCode::Esc => {
                    let previous_query = editor.previous_query.clone();
                    panel.clear_editor();
                    self.mcp_panel = Some(panel);
                    self.composer.clear();
                    self.composer.set_text(previous_query);
                    self.composer
                        .set_placeholder("Search MCP servers by name or transport");
                    self.reset_mcp_panel_selection();
                }
                KeyCode::Enter | KeyCode::Tab => match editor.apply_input(self.composer.text()) {
                    Ok(Some(result)) => match self.apply_mcp_server_draft(runtime, result) {
                        Ok(()) => {
                            let previous_query = editor.previous_query.clone();
                            panel.clear_editor();
                            self.mcp_panel = Some(panel);
                            self.composer.clear();
                            self.composer.set_text(previous_query);
                            self.composer
                                .set_placeholder("Search MCP servers by name or transport");
                            self.reset_mcp_panel_selection();
                        }
                        Err(error) => {
                            self.last_notice = Some(error.to_string());
                            panel.editor = Some(editor);
                            self.mcp_panel = Some(panel);
                            self.composer.set_text(input_before);
                        }
                    },
                    Ok(None) => {
                        let next_input = editor.current_input();
                        let next_placeholder = editor.placeholder();
                        panel.editor = Some(editor);
                        self.mcp_panel = Some(panel);
                        self.composer.clear();
                        self.composer.set_text(next_input);
                        self.composer.set_placeholder(next_placeholder);
                    }
                    Err(error) => {
                        self.last_notice = Some(error.to_string());
                        panel.editor = Some(editor);
                        self.mcp_panel = Some(panel);
                        self.composer.set_text(input_before);
                    }
                },
                _ => {
                    let _ = self.composer.handle_key_with_history(key, false);
                    panel.editor = Some(editor);
                    self.mcp_panel = Some(panel);
                }
            }

            return Ok(());
        }

        let items = self.mcp_panel_items();

        match key.code {
            KeyCode::Up => {
                let mut next_panel = panel;
                next_panel.move_selection(&items, -1);
                self.mcp_panel = Some(next_panel);
            }
            KeyCode::Down => {
                let mut next_panel = panel;
                next_panel.move_selection(&items, 1);
                self.mcp_panel = Some(next_panel);
            }
            KeyCode::Enter => {
                if let Some(selected) = panel.selected_item(&items) {
                    let name = selected.summary.name.clone();
                    let result = match selected.summary.status {
                        crate::mcp::McpConnectionStatus::Connected
                        | crate::mcp::McpConnectionStatus::Connecting => {
                            runtime.block_on(self.tools.disconnect_mcp_server(&name))
                        }
                        _ => runtime.block_on(self.tools.toggle_mcp_server(&name)),
                    };

                    match result {
                        Ok(()) => {
                            self.last_notice = Some(format!("Updated MCP server '{name}'"));
                        }
                        Err(error) => {
                            self.last_notice = Some(error.to_string());
                        }
                    }
                }
            }
            KeyCode::Char('r') | KeyCode::Char('R') => {
                if let Some(selected) = panel.selected_item(&items) {
                    let name = selected.summary.name.clone();
                    if let Err(error) = runtime.block_on(self.tools.refresh_mcp_server(&name)) {
                        self.last_notice = Some(error.to_string());
                    } else {
                        self.last_notice = Some(format!("Refreshed MCP server '{name}'"));
                    }
                }
            }
            KeyCode::Char('a') | KeyCode::Char('A') => {
                let previous_query = self.composer.text().to_string();
                self.open_new_mcp_server_editor(previous_query);
            }
            KeyCode::Char('e') | KeyCode::Char('E') => {
                if let Some(selected) = panel.selected_item(&items) {
                    let previous_query = self.composer.text().to_string();
                    self.open_existing_mcp_server_editor(
                        previous_query,
                        selected.summary.name.clone(),
                    )?;
                }
            }
            KeyCode::Char('d') | KeyCode::Char('D') => {
                if let Some(selected) = panel.selected_item(&items) {
                    let name = selected.summary.name.clone();
                    self.remove_mcp_server_from_editor(runtime, &name)?;
                }
            }
            KeyCode::Esc => {
                self.close_mcp_panel();
            }
            KeyCode::Tab => {}
            KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                let mut next_panel = panel;
                next_panel.move_selection(&items, -1);
                self.mcp_panel = Some(next_panel);
            }
            KeyCode::Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                let mut next_panel = panel;
                next_panel.move_selection(&items, 1);
                self.mcp_panel = Some(next_panel);
            }
            _ => {
                let previous_query = self.composer.text().to_string();
                let _ = self.composer.handle_key_with_history(key, false);
                if self.composer.text() != previous_query {
                    self.reset_mcp_panel_selection();
                }
            }
        }

        Ok(())
    }

    pub(crate) fn mcp_panel_items(&self) -> Vec<McpPanelItem> {
        let query = self.composer.text().trim().to_ascii_lowercase();
        self.tools
            .mcp_summaries()
            .into_iter()
            .filter(|summary| mcp_panel_matches_query(&query, summary))
            .map(|summary| McpPanelItem { summary })
            .collect()
    }

    pub(crate) fn open_new_mcp_server_editor(&mut self, previous_query: String) {
        self.command_palette.clear();
        self.connect_dialog = None;
        self.theme_panel = None;
        self.model_panel = None;
        self.session_panel = None;
        self.at_mention.clear();
        self.draft_attachments.clear();

        let mut panel = self.mcp_panel.clone().unwrap_or_default();
        panel.begin_create_editor(previous_query);
        self.mcp_panel = Some(panel);
        self.composer.clear();
        if let Some(editor) = self
            .mcp_panel
            .as_ref()
            .and_then(|panel| panel.editor.as_ref())
        {
            self.composer.set_text(editor.current_input());
            self.composer.set_placeholder(editor.placeholder());
        }
    }

    pub(crate) fn open_existing_mcp_server_editor(
        &mut self,
        previous_query: String,
        server_name: String,
    ) -> Result<()> {
        let Some(config) = self.config.mcp.servers.get(&server_name).cloned() else {
            self.last_notice = Some(format!("MCP server '{server_name}' does not exist"));
            return Ok(());
        };

        self.command_palette.clear();
        self.connect_dialog = None;
        self.theme_panel = None;
        self.model_panel = None;
        self.session_panel = None;
        self.at_mention.clear();
        self.draft_attachments.clear();

        let mut panel = self.mcp_panel.clone().unwrap_or_default();
        panel.begin_edit_editor(previous_query, server_name, &config);
        self.mcp_panel = Some(panel);
        self.composer.clear();
        if let Some(editor) = self
            .mcp_panel
            .as_ref()
            .and_then(|panel| panel.editor.as_ref())
        {
            self.composer.set_text(editor.current_input());
            self.composer.set_placeholder(editor.placeholder());
        }

        Ok(())
    }

    pub(crate) fn remove_mcp_server_from_editor(
        &mut self,
        runtime: &tokio::runtime::Runtime,
        name: &str,
    ) -> Result<()> {
        if self.config.mcp.servers.remove(name).is_none() {
            self.last_notice = Some(format!("MCP server '{name}' does not exist"));
            return Ok(());
        }

        self.config.save(&self.paths)?;

        if self.tools.mcp_manager().has_server(name) {
            runtime.block_on(self.tools.mcp_manager().remove_server(name))?;
        }

        self.last_notice = Some(format!("Removed MCP server '{name}'"));
        self.reset_mcp_panel_selection();
        Ok(())
    }

    pub(crate) fn apply_mcp_server_draft(
        &mut self,
        runtime: &tokio::runtime::Runtime,
        result: McpServerDraftResult,
    ) -> Result<()> {
        let McpServerDraftResult {
            original_name,
            name,
            config,
        } = result;

        if original_name.as_deref() != Some(name.as_str())
            && self.config.mcp.servers.contains_key(&name)
        {
            bail!("MCP server '{name}' already exists");
        }

        let mut previous_name = None;
        if let Some(original_name) = original_name.clone()
            && original_name != name
        {
            previous_name = Some(original_name);
            self.config
                .mcp
                .servers
                .remove(previous_name.as_ref().expect("name set"));
        }

        self.config.mcp.servers.insert(name.clone(), config.clone());
        self.config.save(&self.paths)?;

        if let Some(previous_name) = previous_name
            && self.tools.mcp_manager().has_server(&previous_name)
        {
            runtime.block_on(self.tools.mcp_manager().remove_server(&previous_name))?;
        }

        runtime.block_on(self.tools.mcp_manager().upsert_server(name.clone(), config))?;

        self.last_notice = Some(format!("Saved MCP server '{name}'"));
        self.reset_mcp_panel_selection();
        Ok(())
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum McpServerKind {
    Stdio,
    Http,
    Sse,
}

impl McpServerKind {
    pub fn label(self) -> &'static str {
        match self {
            Self::Stdio => "stdio",
            Self::Http => "http",
            Self::Sse => "sse",
        }
    }

    pub fn from_input(value: &str) -> Result<Self> {
        match value.trim().to_ascii_lowercase().as_str() {
            "stdio" => Ok(Self::Stdio),
            "http" | "streamable_http" | "streamable-http" => Ok(Self::Http),
            "sse" => Ok(Self::Sse),
            other => bail!("unknown MCP transport '{other}'"),
        }
    }

    pub fn is_stdio(self) -> bool {
        matches!(self, Self::Stdio)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum McpEditorStep {
    Name,
    Kind,
    Endpoint,
    Args,
    Cwd,
    Env,
}

impl McpEditorStep {
    pub fn label(self) -> &'static str {
        match self {
            Self::Name => "Server name",
            Self::Kind => "Transport kind",
            Self::Endpoint => "Command or URL",
            Self::Args => "Arguments",
            Self::Cwd => "Working directory",
            Self::Env => "Environment variables",
        }
    }

    pub fn placeholder(self, kind: McpServerKind) -> String {
        match self {
            Self::Name => "server name".to_string(),
            Self::Kind => "stdio, http, or sse".to_string(),
            Self::Endpoint => {
                if kind.is_stdio() {
                    "npx".to_string()
                } else {
                    "https://example.com/mcp".to_string()
                }
            }
            Self::Args => "space-separated arguments, e.g. -y package-name".to_string(),
            Self::Cwd => "optional working directory".to_string(),
            Self::Env => "KEY=VALUE per line".to_string(),
        }
    }

    pub fn help(self, kind: McpServerKind) -> String {
        match self {
            Self::Name => "Use lowercase letters, numbers, '-', or '_' only.".to_string(),
            Self::Kind => {
                "Choose stdio for a local process or http/sse for a remote endpoint.".to_string()
            }
            Self::Endpoint => {
                if kind.is_stdio() {
                    "Command used to launch the MCP server process.".to_string()
                } else {
                    "Remote MCP URL. Use https:// for hosted servers.".to_string()
                }
            }
            Self::Args => "Use shell-style quoting if you need spaces in an argument.".to_string(),
            Self::Cwd => "Leave blank to use the workspace root.".to_string(),
            Self::Env => {
                "Leave blank if the server does not need custom environment variables.".to_string()
            }
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct McpServerDraft {
    pub name: String,
    pub kind: McpServerKind,
    pub endpoint: String,
    pub args: String,
    pub cwd: String,
    pub env: String,
}

impl McpServerDraft {
    pub fn new() -> Self {
        Self {
            name: String::new(),
            kind: McpServerKind::Stdio,
            endpoint: String::new(),
            args: String::new(),
            cwd: String::new(),
            env: String::new(),
        }
    }

    pub fn from_config(name: impl Into<String>, config: &McpServerConfig) -> Self {
        match config {
            McpServerConfig::Stdio {
                command,
                args,
                cwd,
                env,
            } => Self {
                name: name.into(),
                kind: McpServerKind::Stdio,
                endpoint: command.clone(),
                args: args.join(" "),
                cwd: cwd.clone().unwrap_or_default(),
                env: format_env_lines(env),
            },
            McpServerConfig::Http { url } => Self {
                name: name.into(),
                kind: McpServerKind::Http,
                endpoint: url.clone(),
                args: String::new(),
                cwd: String::new(),
                env: String::new(),
            },
            McpServerConfig::Sse { url } => Self {
                name: name.into(),
                kind: McpServerKind::Sse,
                endpoint: url.clone(),
                args: String::new(),
                cwd: String::new(),
                env: String::new(),
            },
        }
    }

    pub fn current_value(&self, step: McpEditorStep) -> String {
        match step {
            McpEditorStep::Name => self.name.clone(),
            McpEditorStep::Kind => self.kind.label().to_string(),
            McpEditorStep::Endpoint => self.endpoint.clone(),
            McpEditorStep::Args => self.args.clone(),
            McpEditorStep::Cwd => self.cwd.clone(),
            McpEditorStep::Env => self.env.clone(),
        }
    }

    pub fn apply_step(&mut self, step: McpEditorStep, input: &str) -> Result<()> {
        let value = input.trim();

        match step {
            McpEditorStep::Name => {
                self.name = normalize_identifier(value, "MCP server name")?;
            }
            McpEditorStep::Kind => {
                self.kind = McpServerKind::from_input(value)?;
            }
            McpEditorStep::Endpoint => {
                self.endpoint = if self.kind.is_stdio() {
                    non_empty(value, "command")?.to_string()
                } else {
                    normalize_remote_url(value)?
                };
            }
            McpEditorStep::Args => {
                self.args = value.to_string();
            }
            McpEditorStep::Cwd => {
                self.cwd = value.to_string();
            }
            McpEditorStep::Env => {
                self.env = value.to_string();
            }
        }

        Ok(())
    }

    pub fn into_config(self) -> Result<McpServerConfig> {
        match self.kind {
            McpServerKind::Stdio => Ok(McpServerConfig::Stdio {
                command: non_empty(&self.endpoint, "command")?.to_string(),
                args: parse_args(&self.args)?,
                cwd: optional_string(self.cwd),
                env: parse_env_map(&self.env)?,
            }),
            McpServerKind::Http => Ok(McpServerConfig::Http {
                url: normalize_remote_url(&self.endpoint)?,
            }),
            McpServerKind::Sse => Ok(McpServerConfig::Sse {
                url: normalize_remote_url(&self.endpoint)?,
            }),
        }
    }

    pub fn summary_text(&self) -> String {
        let mut lines = vec![
            format!(
                "name: {}",
                if self.name.is_empty() {
                    "<empty>"
                } else {
                    self.name.as_str()
                }
            ),
            format!("transport: {}", self.kind.label()),
            format!(
                "endpoint: {}",
                if self.endpoint.is_empty() {
                    "<empty>"
                } else {
                    self.endpoint.as_str()
                }
            ),
        ];

        if self.kind.is_stdio() {
            let env_count = if self.env.is_empty() {
                "0".to_string()
            } else {
                self.env.lines().count().to_string()
            };

            lines.push(format!(
                "args: {}",
                if self.args.is_empty() {
                    "<none>"
                } else {
                    self.args.as_str()
                }
            ));
            lines.push(format!(
                "cwd: {}",
                if self.cwd.is_empty() {
                    "<workspace root>"
                } else {
                    self.cwd.as_str()
                }
            ));
            lines.push(format!("env lines: {env_count}"));
        }

        lines.join("\n")
    }
}

#[derive(Clone, Debug)]
pub(crate) struct McpServerEditorState {
    pub previous_query: String,
    pub original_name: Option<String>,
    pub step: McpEditorStep,
    pub draft: McpServerDraft,
}

impl McpServerEditorState {
    pub fn new_create(previous_query: String) -> Self {
        Self {
            previous_query,
            original_name: None,
            step: McpEditorStep::Name,
            draft: McpServerDraft::new(),
        }
    }

    pub fn new_edit(
        previous_query: String,
        original_name: String,
        config: &McpServerConfig,
    ) -> Self {
        Self {
            previous_query,
            original_name: Some(original_name.clone()),
            step: McpEditorStep::Name,
            draft: McpServerDraft::from_config(original_name, config),
        }
    }

    pub fn title(&self) -> String {
        match &self.original_name {
            Some(name) => format!("Edit MCP server '{name}'"),
            None => "Add MCP server".to_string(),
        }
    }

    pub fn current_input(&self) -> String {
        self.draft.current_value(self.step)
    }

    pub fn placeholder(&self) -> String {
        self.step.placeholder(self.draft.kind)
    }

    pub fn help(&self) -> String {
        self.step.help(self.draft.kind)
    }

    pub fn step_label(&self) -> &'static str {
        self.step.label()
    }

    pub fn apply_input(&mut self, input: &str) -> Result<Option<McpServerDraftResult>> {
        self.draft.apply_step(self.step, input)?;

        if let Some(next_step) = self.next_step() {
            self.step = next_step;
            return Ok(None);
        }

        let result = McpServerDraftResult {
            original_name: self.original_name.clone(),
            name: self.draft.name.clone(),
            config: self.draft.clone().into_config()?,
        };

        Ok(Some(result))
    }

    fn next_step(&self) -> Option<McpEditorStep> {
        match self.step {
            McpEditorStep::Name => Some(McpEditorStep::Kind),
            McpEditorStep::Kind => Some(McpEditorStep::Endpoint),
            McpEditorStep::Endpoint if self.draft.kind.is_stdio() => Some(McpEditorStep::Args),
            McpEditorStep::Endpoint => None,
            McpEditorStep::Args => Some(McpEditorStep::Cwd),
            McpEditorStep::Cwd => Some(McpEditorStep::Env),
            McpEditorStep::Env => None,
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct McpServerDraftResult {
    pub original_name: Option<String>,
    pub name: String,
    pub config: McpServerConfig,
}

fn non_empty<'a>(value: &'a str, label: &str) -> Result<&'a str> {
    if value.is_empty() {
        bail!("{label} cannot be empty");
    }

    Ok(value)
}

fn normalize_identifier(value: &str, label: &str) -> Result<String> {
    let normalized = value.trim().to_ascii_lowercase().replace([' ', '.'], "-");

    if normalized.is_empty() {
        bail!("{label} cannot be empty");
    }

    if normalized
        .chars()
        .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || matches!(ch, '-' | '_'))
    {
        Ok(normalized)
    } else {
        bail!("{label} may only contain lowercase letters, numbers, '-' or '_'");
    }
}

fn normalize_remote_url(value: &str) -> Result<String> {
    let value = value.trim();

    if value.is_empty() {
        bail!("URL cannot be empty");
    }

    if !(value.starts_with("http://") || value.starts_with("https://")) {
        bail!("URL must start with http:// or https://");
    }

    Ok(value.to_string())
}

fn parse_args(value: &str) -> Result<Vec<String>> {
    let value = value.trim();
    if value.is_empty() {
        return Ok(Vec::new());
    }

    shlex::split(value).with_context(|| "failed to parse arguments")
}

fn parse_env_map(value: &str) -> Result<BTreeMap<String, String>> {
    let mut env = BTreeMap::new();

    for (line_number, line) in value.lines().enumerate() {
        let line = line.trim();
        if line.is_empty() {
            continue;
        }

        let Some((key, raw_value)) = line.split_once('=') else {
            bail!("environment line {} must use KEY=VALUE", line_number + 1);
        };

        let key = key.trim();
        let raw_value = raw_value.trim();

        if key.is_empty() {
            bail!("environment line {} is missing a key", line_number + 1);
        }

        env.insert(key.to_string(), raw_value.to_string());
    }

    Ok(env)
}

fn optional_string(value: String) -> Option<String> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        None
    } else {
        Some(trimmed.to_string())
    }
}

fn format_env_lines(env: &BTreeMap<String, String>) -> String {
    env.iter()
        .map(|(key, value)| format!("{key}={value}"))
        .collect::<Vec<_>>()
        .join("\n")
}

fn first_selectable_index(items: &[McpPanelItem]) -> Option<usize> {
    items.iter().position(McpPanelItem::is_selectable)
}

fn mcp_panel_matches_query(query: &str, summary: &McpServerSummary) -> bool {
    if query.is_empty() {
        return true;
    }

    let name = summary.name.to_ascii_lowercase();
    let kind = summary.kind.to_ascii_lowercase();
    let status = summary.status_text().to_ascii_lowercase();
    let tool_count = summary.tool_count.to_string();

    name.contains(query)
        || kind.contains(query)
        || status.contains(query)
        || tool_count.contains(query)
}
