//! Terminal formatting utilities using termimad for rich markdown rendering

use crate::spec_ai_core::agent::core::{AgentOutput, MemoryRecallStrategy};
use serde_json::to_string;
use std::cell::Cell;
use termimad::*;

thread_local! {
    /// Override for terminal detection in tests
    static FORCE_PLAIN_TEXT: Cell<bool> = const { Cell::new(false) };
}

/// Force plain text output (for testing)
/// Available for both unit and integration tests
pub fn set_plain_text_mode(enabled: bool) {
    FORCE_PLAIN_TEXT.with(|f| f.set(enabled));
}

/// Initialize a custom MadSkin with spec-ai color scheme
pub fn create_skin() -> MadSkin {
    let mut skin = MadSkin::default();

    // Headers - cyan with bold
    let mut header_style = CompoundStyle::with_fg(termimad::crossterm::style::Color::Cyan);
    header_style.add_attr(termimad::crossterm::style::Attribute::Bold);
    skin.headers[0].compound_style = header_style;
    skin.headers[1].compound_style =
        CompoundStyle::with_fg(termimad::crossterm::style::Color::Cyan);

    // Bold text - bright white
    skin.bold.set_fg(termimad::crossterm::style::Color::White);

    // Italic - dim white
    skin.italic.set_fg(termimad::crossterm::style::Color::Grey);

    // Inline code - yellow background
    skin.inline_code
        .set_fg(termimad::crossterm::style::Color::Yellow);

    // Code blocks - with border
    skin.code_block
        .set_fg(termimad::crossterm::style::Color::White);

    // Links - blue
    skin.paragraph.compound_style = CompoundStyle::default();

    // Lists - improved bullet points with better colors and symbol
    skin.bullet = StyledChar::from_fg_char(termimad::crossterm::style::Color::Green, '▸');

    // List item styling - make list items stand out
    skin.paragraph.compound_style =
        CompoundStyle::with_fg(termimad::crossterm::style::Color::White);

    // Quote styling for better visual hierarchy
    skin.quote_mark
        .set_fg(termimad::crossterm::style::Color::DarkCyan);
    skin.quote_mark.set_char('┃');

    skin
}

/// Check if we're in a TTY (terminal) or if output is piped/redirected
pub fn is_terminal() -> bool {
    // Check for test override first
    if FORCE_PLAIN_TEXT.with(|f| f.get()) {
        return false;
    }

    // Use terminal_size as a proxy for TTY detection
    terminal_size::terminal_size().is_some()
}

/// Render markdown text with the spec-ai skin
/// Falls back to plain text if not in a terminal
pub fn render_markdown(text: &str) -> String {
    if !is_terminal() {
        return text.to_string();
    }

    let skin = create_skin();
    let terminal_width = terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80);

    skin.text(text, Some(terminal_width)).to_string()
}

/// Render agent response with markdown formatting
pub fn render_agent_response(role: &str, content: &str) -> String {
    if !is_terminal() {
        return format!("{}: {}", role, content);
    }

    let skin = create_skin();
    let terminal_width = terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80);

    // Format with role header
    let formatted = format!("**{}:**\n\n{}", role, content);
    skin.text(&formatted, Some(terminal_width)).to_string()
}

/// Render run metadata (memory recall, tools, token usage)
pub fn render_run_stats(output: &AgentOutput, show_reasoning: bool) -> Option<String> {
    let mut sections = Vec::new();

    if let Some(stats) = &output.recall_stats {
        let mut section = String::from("## Memory Recall\n");
        match stats.strategy {
            MemoryRecallStrategy::Semantic {
                requested,
                returned,
            } => {
                section.push_str(&format!(
                    "- Strategy: semantic (requested top {}, returned {})\n",
                    requested, returned
                ));
            }
            MemoryRecallStrategy::RecentContext { limit } => {
                section.push_str(&format!(
                    "- Strategy: recent context window (last {} messages)\n",
                    limit
                ));
            }
        }

        if stats.matches.is_empty() {
            section.push_str("- No recalled vector matches this turn.\n");
        } else {
            section.push_str("- Matches:\n");
            for (idx, m) in stats.matches.iter().take(3).enumerate() {
                section.push_str(&format!(
                    "  {}. [{} | score {:.2}] {}\n",
                    idx + 1,
                    m.role.as_str(),
                    m.score,
                    m.preview
                ));
            }
            if stats.matches.len() > 3 {
                section.push_str(&format!(
                    "  ... {} additional matches omitted\n",
                    stats.matches.len() - 3
                ));
            }
        }
        sections.push(section);
    }

    if !output.tool_invocations.is_empty() {
        let mut section = String::from("## Tool Calls\n\n");
        for (idx, inv) in output.tool_invocations.iter().enumerate() {
            // Status symbol
            let status_symbol = if inv.success { "✓" } else { "✗" };

            // Tool header
            section.push_str(&format!(
                "**{}. {} [{}]**\n\n",
                idx + 1,
                inv.name,
                status_symbol
            ));

            // Parse and format arguments nicely
            if let Ok(args_map) = serde_json::from_value::<serde_json::Map<String, serde_json::Value>>(
                inv.arguments.clone(),
            ) {
                for (key, value) in args_map.iter() {
                    let formatted_value = match value {
                        serde_json::Value::String(s) => {
                            if s.len() > 80 {
                                format!("{}...", &s[..77])
                            } else {
                                s.clone()
                            }
                        }
                        serde_json::Value::Number(n) => n.to_string(),
                        serde_json::Value::Bool(b) => b.to_string(),
                        _ => to_string(value).unwrap_or_else(|_| "...".to_string()),
                    };
                    section.push_str(&format!("  - **{}**: `{}`\n", key, formatted_value));
                }
            }

            // Output section
            if let Some(out) = &inv.output {
                if !out.is_empty() {
                    section.push_str("\n  **Result:**\n");

                    // Try to parse as JSON for better formatting
                    if let Ok(json_out) = serde_json::from_str::<serde_json::Value>(out) {
                        // Extract key fields for common tool responses
                        if let Some(obj) = json_out.as_object() {
                            if let Some(stdout) = obj.get("stdout").and_then(|v| v.as_str()) {
                                let lines: Vec<&str> = stdout.lines().collect();
                                if !lines.is_empty() {
                                    section
                                        .push_str(&format!("  - stdout: {} lines\n", lines.len()));
                                    // Show first few lines if not too many
                                    if lines.len() <= 5 {
                                        for line in lines.iter().take(5) {
                                            let trimmed =
                                                if line.len() > 60 { &line[..60] } else { line };
                                            section.push_str(&format!("    `{}`\n", trimmed));
                                        }
                                    }
                                }
                            }
                            if let Some(stderr) = obj.get("stderr").and_then(|v| v.as_str()) {
                                if !stderr.is_empty() {
                                    section.push_str(&format!("  - stderr: {}\n", stderr));
                                }
                            }
                            if let Some(exit_code) = obj.get("exit_code") {
                                section.push_str(&format!("  - exit_code: {}\n", exit_code));
                            }
                            if let Some(duration_ms) = obj.get("duration_ms") {
                                section.push_str(&format!("  - duration: {}ms\n", duration_ms));
                            }
                        }
                    } else {
                        // Plain text output
                        let trimmed = if out.len() > 200 {
                            format!("{}... ({} chars)", &out[..197], out.len())
                        } else {
                            out.clone()
                        };
                        section.push_str(&format!("  ```\n  {}\n  ```\n", trimmed));
                    }
                }
            }

            // Error section
            if let Some(err) = &inv.error {
                section.push_str(&format!("\n  **Error:** {}\n", err));
            }

            section.push('\n');
        }
        sections.push(section);
    }

    if let Some(graph_debug) = &output.graph_debug {
        let mut section = String::from("## Graph Debug\n");
        section.push_str(&format!(
            "- Enabled: {}\n- Memory: {}\n- Auto Build: {}\n- Steering: {}\n",
            if graph_debug.enabled { "yes" } else { "no" },
            if graph_debug.graph_memory_enabled {
                "enabled"
            } else {
                "disabled"
            },
            if graph_debug.auto_graph_enabled {
                "enabled"
            } else {
                "disabled"
            },
            if graph_debug.graph_steering_enabled {
                "enabled"
            } else {
                "disabled"
            }
        ));

        if graph_debug.enabled {
            section.push_str(&format!(
                "- Node Count: {}\n- Edge Count: {}\n",
                graph_debug.node_count, graph_debug.edge_count
            ));

            if graph_debug.recent_nodes.is_empty() {
                section.push_str("- Recent Nodes: none recorded yet\n");
            } else {
                section.push_str("- Recent Nodes:\n");
                for node in &graph_debug.recent_nodes {
                    section.push_str(&format!(
                        "  - #{} [{}] {}\n",
                        node.id, node.node_type, node.label
                    ));
                }
            }
        } else {
            section.push_str("- Graph disabled; skipping node snapshot\n");
        }

        sections.push(section);
    }

    // Display reasoning summary if enabled and available
    if show_reasoning {
        // Display reasoning summary if available (more user-friendly)
        // Fall back to full reasoning if no summary was generated
        if let Some(summary) = &output.reasoning_summary {
            if !summary.is_empty() {
                let mut section = String::from("## Reasoning\n\n");
                section.push_str(&format!("💭 {}\n", summary));
                sections.push(section);
            }
        } else if let Some(reasoning) = &output.reasoning {
            if !reasoning.is_empty() {
                let mut section = String::from("## Reasoning\n\n");
                // Truncate long reasoning for display
                let preview = if reasoning.len() > 200 {
                    format!(
                        "💭 {}... ({} chars total)",
                        &reasoning[..197],
                        reasoning.len()
                    )
                } else {
                    format!("💭 {}", reasoning)
                };
                section.push_str(&format!("{}\n", preview));
                sections.push(section);
            }
        }
    }

    if let Some(next_action) = &output.next_action {
        let mut section = String::from("## Graph Steering\n");
        section.push_str(&format!("- Recommendation: {}\n", next_action));
        sections.push(section);
    }

    if let Some(usage) = &output.token_usage {
        sections.push(format!(
            "## Tokens\n- Prompt: {}\n- Completion: {}\n- Total: {}\n",
            usage.prompt_tokens, usage.completion_tokens, usage.total_tokens
        ));
    }

    if sections.is_empty() {
        return None;
    }

    let markdown = format!("---\n\n# Run Stats\n\n{}", sections.join("\n"));
    Some(render_markdown(&markdown))
}

/// Render help text with rich markdown formatting
pub fn render_help() -> String {
    let help_text = r#"
# SpecAI Commands

## Agent Management
Manage your AI agent profiles and sessions:

- **`/agents`** or **`/list`** — List all available agent profiles
- **`/switch <name>`** — Switch to a different agent profile
- **`/new <name>`** — Create new conversation session

## Configuration
Control your SpecAI configuration:

- **`/config show`** — Display current configuration
  - Shows model provider, temperature, and other settings
- **`/config reload`** — Reload configuration from file
  - Useful after editing spec-ai.config.toml

## Memory & History
Access conversation memory:

- **`/memory show [N]`** — Show last N messages (default: 10)
  - Displays color-coded conversation history
- **`/memory clear`** — Clear conversation history

## Session Management
Manage multiple conversation sessions:

- **`/session list`** — List all conversation sessions
- **`/session load <id>`** — Load a specific session
- **`/session delete <id>`** — Delete a session

## Knowledge Graph
AI reasoning with graph-based memory:

- **`/graph enable`** — Enable knowledge graph features
  - Activates graph memory and automatic entity extraction
- **`/graph disable`** — Disable knowledge graph features
- **`/graph status`** — Show current graph configuration
- **`/graph show [N]`** — Display last N graph nodes (default: 10)
- **`/graph clear`** — Clear graph for current session

## Graph Synchronization
Distributed graph sync across instances:

- **`/sync`** or **`/sync list`** — List all graphs with sync enabled

Configure sync in `spec-ai.config.toml`:
```toml
[sync]
enabled = true
namespaces = [
  { session_id = "shared", graph_name = "knowledge" }
]
```

## Repository Bootstrap
Prime the knowledge graph with source facts before the first prompt:

- **`/init`** — Run the bootstrap-self pipeline against the repo (only valid as the first message)
- **`/refresh`** — Re-run the bootstrap-self pipeline with caching enabled (safe after `/init`)

## Audio Transcription
Mock audio input transcription for testing:

- **`/listen [scenario] [duration]`** — Start audio transcription simulation
  - **Scenarios:** `simple_conversation`, `command_sequence`, `noisy_environment`, `emotional_context`, `multi_speaker`
  - **Duration:** Time in seconds (default: 30)
  - Example: `/listen simple_conversation 60`
- **`/speak [on|off|toggle]`** — Enable or disable macOS speech playback (`Ctrl+S` while a response is streaming also toggles)

## Spec Runs
Execute structured `.spec` files with clear goals:

- **`/spec run <file>`** — Load and execute a TOML spec (extension must be `.spec`)
- **`/spec <file>`** — Shorthand for `/spec run <file>`
  - Specs must define a `goal` and at least one `tasks` or `deliverables` entry

## General Commands
- **`/help`** — Show this help message
- **`/quit`** or **`/exit`** — Exit the REPL

---

**Usage:** Type your message to chat with the current agent. Use `/` prefix for commands.
"#;

    render_markdown(help_text)
}

/// Create a formatted table for agent list
pub fn render_agent_table(agents: Vec<(String, bool, Option<String>)>) -> String {
    if !is_terminal() {
        // Plain text fallback
        let mut output = String::from("Available agents:\n");
        for (name, is_active, description) in agents {
            let active_marker = if is_active { " (active)" } else { "" };
            let desc = description.unwrap_or_default();
            output.push_str(&format!("  - {}{}", name, active_marker));
            if !desc.is_empty() {
                output.push_str(&format!(" - {}", desc));
            }
            output.push('\n');
        }
        return output;
    }

    let skin = create_skin();
    let terminal_width = terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80);

    // Build markdown table
    let mut table = String::from("# Available Agents\n\n");
    table.push_str("| Agent | Status | Description |\n");
    table.push_str("|-------|--------|-------------|\n");

    for (name, is_active, description) in agents {
        let status = if is_active { "**active**" } else { "" };
        let desc = description.unwrap_or_default();
        table.push_str(&format!("| {} | {} | {} |\n", name, status, desc));
    }

    skin.text(&table, Some(terminal_width)).to_string()
}

/// Format memory/history display with role-based color coding
pub fn render_memory(messages: Vec<(String, String)>) -> String {
    if !is_terminal() {
        // Plain text fallback
        let mut output = String::new();
        for (role, content) in messages {
            output.push_str(&format!("{}: {}\n", role, content));
        }
        return output;
    }

    let skin = create_skin();
    let terminal_width = terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80);

    let mut formatted = String::from("# Conversation History\n\n");

    for (role, content) in messages {
        let role_formatted = match role.as_str() {
            "user" => "**👤 User:**",
            "assistant" => "**🤖 Assistant:**",
            "system" => "**⚙️  System:**",
            _ => &format!("**{}:**", role),
        };

        formatted.push_str(&format!("{}\n{}\n\n---\n\n", role_formatted, content));
    }

    skin.text(&formatted, Some(terminal_width)).to_string()
}

/// Format configuration display with sections
pub fn render_config(config_text: &str) -> String {
    if !is_terminal() {
        return config_text.to_string();
    }

    let skin = create_skin();
    let terminal_width = terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80);

    let formatted = format!("# Current Configuration\n\n```toml\n{}\n```", config_text);
    skin.text(&formatted, Some(terminal_width)).to_string()
}

/// Render a formatted list with custom bullet styling
pub fn render_list(title: &str, items: Vec<String>) -> String {
    if !is_terminal() {
        let mut output = format!("{}:\n", title);
        for item in items {
            output.push_str(&format!("  - {}\n", item));
        }
        return output;
    }

    let skin = create_skin();
    let terminal_width = terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80);

    let mut formatted = format!("## {}\n\n", title);
    for item in items {
        formatted.push_str(&format!("- {}\n", item));
    }

    skin.text(&formatted, Some(terminal_width)).to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_render_markdown_basic() {
        let text = "**bold** and *italic*";
        let result = render_markdown(text);
        // Just ensure it doesn't panic
        assert!(!result.is_empty());
    }

    #[test]
    fn test_render_agent_table() {
        let agents = vec![
            (
                "default".to_string(),
                true,
                Some("Default agent".to_string()),
            ),
            ("researcher".to_string(), false, None),
        ];
        let result = render_agent_table(agents);
        assert!(result.contains("default"));
        assert!(result.contains("researcher"));
    }
}
