use std::path::PathBuf;

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "axocoatl")]
#[command(about = "Axocoatl — The agentic AI framework that doesn't waste tokens")]
#[command(version)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new Axocoatl project
    Init {
        /// Project name / directory
        name: Option<String>,
    },

    /// Validate a axocoatl.yaml configuration file
    Validate {
        /// Path to config file (default: axocoatl.yaml)
        #[arg(default_value = "axocoatl.yaml")]
        config: PathBuf,
    },

    /// Start in development mode (verbose logging, no daemonization)
    Dev {
        /// Path to config file
        #[arg(short, long, default_value = "axocoatl.yaml")]
        config: PathBuf,
    },

    /// Start the daemon + API server in production mode
    Serve {
        /// Path to config file
        #[arg(short, long, default_value = "axocoatl.yaml")]
        config: PathBuf,
    },

    /// Interactive chat with an agent
    Chat {
        /// Agent ID to chat with
        #[arg(short, long, default_value = "assistant")]
        agent: String,

        /// Config file
        #[arg(short, long, default_value = "axocoatl.yaml")]
        config: PathBuf,

        /// Resume a previous session
        #[arg(long)]
        session: Option<String>,
    },

    /// Session management
    Sessions {
        #[command(subcommand)]
        command: SessionCommands,
    },

    /// Agent management commands
    Agents {
        #[command(subcommand)]
        command: AgentCommands,
    },

    /// Skill management commands
    Skills {
        #[command(subcommand)]
        command: SkillCommands,
    },

    /// MCP server management
    Mcp {
        #[command(subcommand)]
        command: McpCommands,
    },

    /// Token usage reporting
    Tokens {
        #[command(subcommand)]
        command: TokenCommands,
    },

    /// Run benchmarks
    Benchmark {
        /// Benchmark to run (token, routing, isolation, actor, all)
        #[arg(default_value = "all")]
        name: String,
    },
}

#[derive(Subcommand)]
enum SessionCommands {
    /// List saved sessions (from checkpoints)
    List {
        /// Agent ID to list sessions for
        #[arg(short, long, default_value = "assistant")]
        agent: String,
    },
}

#[derive(Subcommand)]
enum AgentCommands {
    /// List all configured agents
    List {
        #[arg(short, long, default_value = "axocoatl.yaml")]
        config: PathBuf,
    },
    /// Show agent status (requires running daemon)
    Status,
    /// Restart an agent
    Restart {
        /// Agent ID
        agent_id: String,
    },
}

#[derive(Subcommand)]
enum SkillCommands {
    /// List available skills
    List,
    /// Run a skill
    Run {
        /// Skill name
        name: String,
        /// Parameters as key=value pairs
        #[arg(trailing_var_arg = true)]
        params: Vec<String>,
    },
}

#[derive(Subcommand)]
enum McpCommands {
    /// List connected MCP servers
    Servers,
    /// List available MCP tools
    Tools,
}

#[derive(Subcommand)]
enum TokenCommands {
    /// Show token usage report
    Report,
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();

    // Initialize tracing
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    match cli.command {
        Commands::Init { name } => cmd_init(name).await,
        Commands::Validate { config } => cmd_validate(&config).await,
        Commands::Dev { config } => cmd_dev(&config).await,
        Commands::Serve { config } => cmd_serve(&config).await,
        Commands::Chat {
            agent,
            config,
            session,
        } => cmd_chat(&config, &agent, session).await,
        Commands::Sessions { command } => match command {
            SessionCommands::List { agent } => cmd_sessions_list(&agent).await,
        },
        Commands::Agents { command } => match command {
            AgentCommands::List { config } => cmd_agents_list(&config).await,
            AgentCommands::Status => cmd_agents_status().await,
            AgentCommands::Restart { agent_id } => cmd_agents_restart(&agent_id).await,
        },
        Commands::Skills { command } => match command {
            SkillCommands::List => cmd_skills_list().await,
            SkillCommands::Run { name, params } => cmd_skills_run(&name, params).await,
        },
        Commands::Mcp { command } => match command {
            McpCommands::Servers => cmd_mcp_servers().await,
            McpCommands::Tools => cmd_mcp_tools().await,
        },
        Commands::Tokens { command } => match command {
            TokenCommands::Report => cmd_tokens_report().await,
        },
        Commands::Benchmark { name } => cmd_benchmark(&name).await,
    }
}

async fn cmd_init(name: Option<String>) {
    let project_name = name.unwrap_or_else(|| "my-axocoatl-project".to_string());
    let dir = PathBuf::from(&project_name);

    if dir.exists() {
        eprintln!("Error: Directory '{}' already exists", project_name);
        std::process::exit(1);
    }

    std::fs::create_dir_all(&dir).expect("Failed to create project directory");
    std::fs::create_dir_all(dir.join("data")).expect("Failed to create data directory");

    let config = r#"# Axocoatl Agent Configuration
# See: https://docs.axocoatl.dev for full reference

agents:
  - id: assistant
    name: "Assistant Agent"
    provider: openai
    model: gpt-4o
    system_prompt: "You are a helpful assistant."
    token_budget:
      per_execution: 20000
      per_call: 8192
      overflow_policy: summarize

providers:
  openai:
    api_key: "${OPENAI_API_KEY}"

server:
  port: 8080
  host: "0.0.0.0"
"#;

    std::fs::write(dir.join("axocoatl.yaml"), config).expect("Failed to write axocoatl.yaml");
    std::fs::write(
        dir.join(".env.example"),
        "OPENAI_API_KEY=sk-your-key-here\nANTHROPIC_API_KEY=sk-ant-your-key-here\n",
    )
    .expect("Failed to write .env.example");

    println!("Created new Axocoatl project: {project_name}/");
    println!("  axocoatl.yaml     — agent configuration");
    println!("  .env.example   — API key template");
    println!("  data/          — runtime data directory");
    println!();
    println!("Next steps:");
    println!("  cd {project_name}");
    println!("  cp .env.example .env  # add your API keys");
    println!("  axocoatl validate        # check config");
    println!("  axocoatl dev             # start development mode");
}

async fn cmd_validate(config_path: &std::path::Path) {
    match axocoatl_config::load_config(config_path).await {
        Ok(config) => {
            println!("Config is valid.");
            println!("  Agents: {}", config.agents.len());
            for agent in &config.agents {
                let budget = agent
                    .token_budget
                    .as_ref()
                    .map(|b| format!("{} tokens/exec", b.per_execution))
                    .unwrap_or_else(|| "unlimited".to_string());
                println!(
                    "    - {} ({}/{}) [{}]",
                    agent.id, agent.provider, agent.model, budget
                );
            }
            println!("  Workflows: {}", config.workflows.len());
            println!("  MCP servers: {}", config.mcp_servers.len());
        }
        Err(e) => {
            eprintln!("Configuration error:\n{e}");
            std::process::exit(1);
        }
    }
}

async fn cmd_dev(config_path: &std::path::Path) {
    let config = match axocoatl_config::load_config(config_path).await {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Configuration error:\n{e}");
            std::process::exit(1);
        }
    };

    let host = config.server.host.clone();
    let port = config.server.port;

    println!("Axocoatl dev mode");
    println!("  Config: {}", config_path.display());
    println!("  Agents: {}", config.agents.len());

    let daemon = match axocoatl_daemon::AxocoatlDaemon::bootstrap(config).await {
        Ok(d) => {
            println!("  Runtime: {} agents spawned", d.agent_count().await);
            d
        }
        Err(e) => {
            eprintln!("Failed to bootstrap daemon: {e}");
            std::process::exit(1);
        }
    };

    // Shared state for both IPC and HTTP
    let state: std::sync::Arc<tokio::sync::RwLock<axocoatl_daemon::AxocoatlDaemon>> =
        std::sync::Arc::new(tokio::sync::RwLock::new(daemon));

    // Start IPC server for CLI clients
    let socket_path = axocoatl_daemon::ipc::default_socket_path();
    match axocoatl_daemon::ipc::start_ipc_server(state.clone(), &socket_path).await {
        Ok(_handle) => {
            println!("  IPC:    {}", socket_path.display());
        }
        Err(e) => {
            eprintln!("  IPC:    failed to start ({e})");
        }
    }

    println!("  Server: http://{host}:{port}");
    println!("  Health: http://{host}:{port}/health");
    println!();
    println!("Axocoatl is running. Press Ctrl+C to stop.");

    // Start the HTTP server (blocks until shutdown) — shares state with IPC
    if let Err(e) = axocoatl_server::serve_shared(state, &host, port).await {
        eprintln!("Server error: {e}");
        std::process::exit(1);
    }
}

async fn cmd_serve(config_path: &std::path::Path) {
    let config = match axocoatl_config::load_config(config_path).await {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Configuration error:\n{e}");
            std::process::exit(1);
        }
    };

    let host = config.server.host.clone();
    let port = config.server.port;

    let daemon = match axocoatl_daemon::AxocoatlDaemon::bootstrap(config).await {
        Ok(d) => d,
        Err(e) => {
            eprintln!("Failed to bootstrap daemon: {e}");
            std::process::exit(1);
        }
    };

    println!("Axocoatl server starting on {host}:{port}");

    if let Err(e) = axocoatl_server::serve(daemon, &host, port).await {
        eprintln!("Server error: {e}");
        std::process::exit(1);
    }
}

async fn cmd_agents_list(config_path: &std::path::Path) {
    let config = match axocoatl_config::load_config(config_path).await {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Configuration error:\n{e}");
            std::process::exit(1);
        }
    };

    if config.agents.is_empty() {
        println!("No agents configured.");
        return;
    }

    println!(
        "{:<15} {:<20} {:<12} {:<15}",
        "ID", "NAME", "PROVIDER", "MODEL"
    );
    println!("{}", "-".repeat(62));
    for agent in &config.agents {
        println!(
            "{:<15} {:<20} {:<12} {:<15}",
            agent.id, agent.name, agent.provider, agent.model
        );
    }
}

async fn cmd_tokens_report() {
    println!("Token usage reporting requires a running daemon.");
    println!("Start with 'axocoatl dev' first, then query token usage.");
}

/// Display tool calls inline in the chat output.
fn display_tool_calls(tool_calls: &[axocoatl_core::ToolCallRecord]) {
    for tc in tool_calls {
        let args_summary = tc.arguments.to_string();
        let args_display = if args_summary.len() > 80 {
            format!("{}...", &args_summary[..77])
        } else {
            args_summary
        };
        if let Some(result) = &tc.result {
            let result_str = result.to_string();
            let result_display = if result_str.len() > 60 {
                format!("{}...", &result_str[..57])
            } else {
                result_str
            };
            println!(
                "  [tool: {}({})] -> {}",
                tc.tool_name, args_display, result_display
            );
        } else {
            println!("  [tool: {}({})]", tc.tool_name, args_display);
        }
    }
}

async fn cmd_chat(config_path: &std::path::Path, agent_id: &str, session_id: Option<String>) {
    use std::io::{self, BufRead, Write};

    let config = match axocoatl_config::load_config(config_path).await {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Configuration error:\n{e}");
            std::process::exit(1);
        }
    };

    // Find agent model for display
    let agent_model = config
        .agents
        .iter()
        .find(|a| a.id == agent_id)
        .map(|a| a.model.clone())
        .unwrap_or_else(|| "unknown".to_string());

    let session = session_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());

    // Try connecting to a running daemon via IPC first
    let socket_path = axocoatl_daemon::ipc::default_socket_path();
    let ipc_client = axocoatl_daemon::ipc::IpcClient::connect(&socket_path).await.ok();

    let using_ipc = ipc_client.is_some();

    // If no daemon running, bootstrap in-process
    let daemon = if ipc_client.is_none() {
        Some(match axocoatl_daemon::AxocoatlDaemon::bootstrap(config).await {
            Ok(d) => d,
            Err(e) => {
                eprintln!("Failed to bootstrap daemon: {e}");
                std::process::exit(1);
            }
        })
    } else {
        None
    };

    println!("Axocoatl Chat");
    println!("  Agent:   {agent_id} ({agent_model})");
    println!("  Session: {session}");
    if using_ipc {
        println!("  Mode:    connected to daemon (IPC)");
    } else {
        println!("  Mode:    in-process");
    }
    println!("  Type 'exit' or Ctrl+D to quit.\n");

    let stdin = io::stdin();
    let mut stdout = io::stdout();

    let mut total_input_tokens: usize = 0;
    let mut total_output_tokens: usize = 0;
    let mut turn_count: usize = 0;

    // Mutable IPC client (needs to be mutable for requests)
    let mut ipc = ipc_client;

    loop {
        print!("you> ");
        stdout.flush().unwrap();

        let mut line = String::new();
        if stdin.lock().read_line(&mut line).unwrap() == 0 {
            break; // EOF
        }

        let input = line.trim();
        if input.is_empty() {
            continue;
        }
        if input == "exit" || input == "quit" {
            break;
        }

        // Execute via IPC or in-process
        if let Some(ref mut client) = ipc {
            let req = axocoatl_daemon::ipc::IpcRequest::Execute {
                agent_id: agent_id.to_string(),
                input: input.to_string(),
                session_id: session.clone(),
            };
            match client.request(&req).await {
                Ok(axocoatl_daemon::ipc::IpcResponse::Response {
                    content,
                    tool_calls,
                    input_tokens,
                    output_tokens,
                }) => {
                    turn_count += 1;

                    // Convert IPC tool calls for display
                    let records: Vec<axocoatl_core::ToolCallRecord> = tool_calls
                        .into_iter()
                        .map(|tc| axocoatl_core::ToolCallRecord {
                            tool_name: tc.tool_name,
                            arguments: tc.arguments,
                            result: tc.result,
                        })
                        .collect();
                    display_tool_calls(&records);

                    println!("\nagent> {content}\n");

                    total_input_tokens += input_tokens;
                    total_output_tokens += output_tokens;
                    println!(
                        "  (tokens: {} in / {} out | session total: {} in / {} out)",
                        input_tokens, output_tokens, total_input_tokens, total_output_tokens,
                    );
                    println!();
                }
                Ok(axocoatl_daemon::ipc::IpcResponse::Error { message }) => {
                    eprintln!("\nerror> {message}\n");
                }
                Ok(_) => {
                    eprintln!("\nerror> unexpected response from daemon\n");
                }
                Err(e) => {
                    eprintln!("\nerror> IPC error: {e}\n");
                }
            }
        } else if let Some(ref daemon) = daemon {
            match daemon.execute_agent(agent_id, input).await {
                Ok(output) => {
                    turn_count += 1;
                    display_tool_calls(&output.tool_calls);

                    println!("\nagent> {}\n", output.content);

                    total_input_tokens += output.token_usage.input_tokens;
                    total_output_tokens += output.token_usage.output_tokens;
                    println!(
                        "  (tokens: {} in / {} out | session total: {} in / {} out)",
                        output.token_usage.input_tokens,
                        output.token_usage.output_tokens,
                        total_input_tokens,
                        total_output_tokens,
                    );
                    println!();
                }
                Err(e) => {
                    eprintln!("\nerror> {e}\n");
                }
            }
        }
    }

    println!();
    println!("Session summary:");
    println!("  Turns:  {turn_count}");
    println!(
        "  Tokens: {total_input_tokens} in / {total_output_tokens} out ({} total)",
        total_input_tokens + total_output_tokens
    );
    println!("  Session ID: {session} (use --session {session} to resume)");
    println!();
    println!("Goodbye!");
    if let Some(daemon) = daemon {
        daemon.shutdown().await;
    }
}

async fn cmd_sessions_list(agent_id: &str) {
    let data_dir = std::env::var("AXOCOATL_DATA_DIR").unwrap_or_else(|_| "./data".to_string());
    let checkpoint_dir = std::path::PathBuf::from(&data_dir).join("checkpoints");

    if !checkpoint_dir.exists() {
        println!("No sessions found (checkpoint directory does not exist).");
        println!("Start a chat with 'axocoatl chat' to create a session.");
        return;
    }

    // Checkpoints are stored as files in the checkpoint dir with the agent ID prefix.
    // List matching files and show their timestamps.
    let pattern = format!("{}-", agent_id);
    let mut found = false;

    if let Ok(entries) = std::fs::read_dir(&checkpoint_dir) {
        let mut sessions: Vec<(String, std::time::SystemTime)> = Vec::new();
        for entry in entries.flatten() {
            let name = entry.file_name().to_string_lossy().to_string();
            if name.starts_with(&pattern) || name.starts_with(agent_id) {
                if let Ok(meta) = entry.metadata() {
                    if let Ok(modified) = meta.modified() {
                        sessions.push((name, modified));
                    }
                }
            }
        }
        sessions.sort_by(|a, b| b.1.cmp(&a.1));

        if !sessions.is_empty() {
            println!("{:<40} {:<20}", "SESSION", "LAST MODIFIED");
            println!("{}", "-".repeat(60));
            for (name, modified) in &sessions {
                let elapsed = modified.elapsed().unwrap_or_default();
                let ago = if elapsed.as_secs() < 60 {
                    format!("{}s ago", elapsed.as_secs())
                } else if elapsed.as_secs() < 3600 {
                    format!("{}m ago", elapsed.as_secs() / 60)
                } else if elapsed.as_secs() < 86400 {
                    format!("{}h ago", elapsed.as_secs() / 3600)
                } else {
                    format!("{}d ago", elapsed.as_secs() / 86400)
                };
                println!("{:<40} {:<20}", name, ago);
            }
            found = true;
        }
    }

    if !found {
        println!("No sessions found for agent '{agent_id}'.");
        println!("Start a chat with 'axocoatl chat -a {agent_id}' to create a session.");
    }
}

async fn cmd_agents_status() {
    println!("Agent status requires a running daemon.");
    println!("Connect to a running daemon via 'axocoatl dev' and use the HTTP API.");
}

async fn cmd_agents_restart(_agent_id: &str) {
    println!("Agent restart requires a running daemon.");
}

async fn cmd_skills_list() {
    let mut registry = axocoatl_core::SkillRegistry::new();
    registry.register_builtins();

    println!("{:<15} {}", "NAME", "DESCRIPTION");
    println!("{}", "-".repeat(60));
    for name in registry.names() {
        if let Some(skill) = registry.get(&name) {
            println!("{:<15} {}", skill.name, skill.description);
        }
    }
}

async fn cmd_skills_run(name: &str, params: Vec<String>) {
    let mut registry = axocoatl_core::SkillRegistry::new();
    registry.register_builtins();

    let skill = match registry.get(name) {
        Some(s) => s.clone(),
        None => {
            eprintln!("Skill not found: {name}");
            eprintln!("Available skills: {:?}", registry.names());
            std::process::exit(1);
        }
    };

    let mut param_map = std::collections::HashMap::new();
    for p in params {
        if let Some((k, v)) = p.split_once('=') {
            param_map.insert(k.to_string(), v.to_string());
        }
    }

    match skill.render(&param_map) {
        Ok(prompt) => {
            println!("Rendered skill prompt:\n");
            println!("{prompt}");
            println!("\n(To execute, pipe this to an agent via 'axocoatl chat')");
        }
        Err(e) => {
            eprintln!("Skill error: {e}");
            std::process::exit(1);
        }
    }
}

async fn cmd_mcp_servers() {
    println!("MCP server listing requires a running daemon.");
}

async fn cmd_mcp_tools() {
    println!("MCP tool listing requires a running daemon.");
}

async fn cmd_benchmark(name: &str) {
    println!("Running benchmark: {name}");
    println!("Use 'cargo bench' for detailed benchmarks.");
    println!("Available: token, routing, isolation, actor, all");
}
