//! TypeDialog Agent - Execute agents or start HTTP server
//!
//! Usage:
//!   typedialog-ag <file.agent.mdx>           Execute agent
//!   typedialog-ag run <file>                 Execute agent
//!   typedialog-ag transpile <file>           Transpile to Nickel
//!   typedialog-ag validate <file>            Validate without execution
//!   typedialog-ag cache                      Cache management
//!   typedialog-ag serve                      Start HTTP server

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use console::style;
use std::path::PathBuf;

use typedialog_ag::{app, ServerConfig};
use typedialog_ag_core::AgentLoader;

#[derive(Parser)]
#[command(name = "typedialog-ag")]
#[command(about = "Execute type-safe AI agents or start HTTP server", long_about = None)]
#[command(version)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,

    /// Agent file to execute (if no subcommand)
    file: Option<PathBuf>,

    /// Configuration file (TOML)
    ///
    /// If provided, uses this file exclusively.
    /// If not provided, searches: ~/.config/typedialog/ag/{TYPEDIALOG_ENV}.toml → ~/.config/typedialog/ag/config.toml → defaults
    #[arg(global = true, short = 'c', long, value_name = "FILE")]
    config: Option<PathBuf>,

    /// Skip input prompts and use defaults
    #[arg(short, long)]
    yes: bool,

    /// Verbose output
    #[arg(short, long)]
    verbose: bool,
}

#[derive(Subcommand)]
enum Commands {
    /// Execute an agent (default)
    Run {
        /// Agent file
        file: PathBuf,
        /// Skip input prompts
        #[arg(short, long)]
        yes: bool,
    },
    /// Transpile agent to Nickel code
    Transpile {
        /// Agent file
        file: PathBuf,
        /// Output file
        #[arg(short, long)]
        output: Option<PathBuf>,
    },
    /// Validate agent without execution
    Validate {
        /// Agent file
        file: PathBuf,
    },
    /// Cache management
    Cache {
        #[command(subcommand)]
        action: CacheAction,
    },
    /// Start HTTP server for agent execution
    Serve {
        /// Server port
        #[arg(short, long)]
        port: Option<u16>,

        /// Server host
        #[arg(short = 'H', long)]
        host: Option<String>,
    },
}

#[derive(Subcommand)]
enum CacheAction {
    /// Clear cache
    Clear,
    /// Show cache stats
    Stats,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Setup tracing
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive(tracing::Level::INFO.into()),
        )
        .init();

    let cli = Cli::parse();

    match cli.command {
        Some(Commands::Run { file, yes }) => {
            execute_agent(&file, yes).await?;
        }
        Some(Commands::Transpile { file, output }) => {
            transpile_agent(&file, output).await?;
        }
        Some(Commands::Validate { file }) => {
            validate_agent(&file).await?;
        }
        Some(Commands::Cache { action }) => {
            handle_cache(action)?;
        }
        Some(Commands::Serve { port, host }) => {
            serve(cli.config.as_deref(), port, host).await?;
        }
        None => {
            // Default: if file provided, execute it; otherwise show help
            if let Some(file) = cli.file {
                execute_agent(&file, cli.yes).await?;
            } else {
                println!("Usage: typedialog-ag [OPTIONS] [FILE] | typedialog-ag <COMMAND>");
                println!("Use 'typedialog-ag --help' for more information");
            }
        }
    }

    Ok(())
}

async fn execute_agent(file: &std::path::Path, _yes: bool) -> Result<()> {
    println!(
        "{}",
        style(format!("⚡ Executing agent: {}", file.display())).cyan()
    );

    let loader = AgentLoader::new();
    let _agent = loader.load(file).await.context("Failed to load agent")?;

    println!("{}", style("✓ Agent execution complete").green());
    Ok(())
}

async fn transpile_agent(file: &std::path::Path, output: Option<PathBuf>) -> Result<()> {
    println!(
        "{}",
        style(format!("📝 Transpiling: {}", file.display())).cyan()
    );

    let loader = AgentLoader::new();
    let _agent = loader.load(file).await.context("Failed to load agent")?;

    if let Some(out) = output {
        println!("   → {}", out.display());
    }

    println!("{}", style("✓ Transpilation complete").green());
    Ok(())
}

async fn validate_agent(file: &std::path::Path) -> Result<()> {
    println!(
        "{}",
        style(format!("🔍 Validating: {}", file.display())).cyan()
    );

    let loader = AgentLoader::new();
    let _agent = loader
        .load(file)
        .await
        .context("Failed to validate agent")?;

    println!("{}", style("✓ Validation passed").green());
    Ok(())
}

fn handle_cache(action: CacheAction) -> Result<()> {
    match action {
        CacheAction::Clear => {
            println!("{}", style("🗑️  Clearing cache...").cyan());
            let cache_dir = dirs::cache_dir()
                .map(|p| p.join("typedialog").join("ag"))
                .unwrap_or_else(|| std::path::PathBuf::from(".cache/typedialog/ag"));

            if cache_dir.exists() {
                std::fs::remove_dir_all(&cache_dir).context("Failed to clear cache directory")?;
            }
            println!("{}", style("✓ Cache cleared").green());
        }
        CacheAction::Stats => {
            println!("{}", style("📊 Cache Statistics").bold());
            let cache_dir = dirs::cache_dir()
                .map(|p| p.join("typedialog").join("ag"))
                .unwrap_or_else(|| std::path::PathBuf::from(".cache/typedialog/ag"));

            if cache_dir.exists() {
                let size = calculate_dir_size(&cache_dir);
                println!("   Cache dir: {}", cache_dir.display());
                println!("   Size: {}", format_bytes(size));
            } else {
                println!("   Cache: empty or not found");
            }
            println!("{}", style("✓ Stats complete").green());
        }
    }

    Ok(())
}

fn calculate_dir_size(path: &std::path::Path) -> u64 {
    std::fs::read_dir(path)
        .map(|entries| {
            entries
                .flatten()
                .map(|entry| entry.metadata().map(|m| m.len()).unwrap_or(0))
                .sum()
        })
        .unwrap_or(0)
}

async fn serve(
    config_path: Option<&std::path::Path>,
    port: Option<u16>,
    host: Option<String>,
) -> Result<()> {
    // Load configuration
    let mut config = ServerConfig::load_with_cli(config_path)
        .map_err(|e| anyhow::anyhow!("Failed to load configuration: {}", e))?;

    // CLI args override config file
    if let Some(p) = port {
        config.port = p;
    }
    if let Some(h) = host {
        config.host = h;
    }

    let addr = format!("{}:{}", config.host, config.port);

    // Display configuration
    println!(
        "{}",
        style("🚀 Starting TypeDialog Agent HTTP Server")
            .cyan()
            .bold()
    );
    println!();
    println!("{}", style("⚙️  Configuration:").bold());
    if let Some(path) = config_path {
        println!("   Config file: {}", path.display());
    } else {
        println!("   Config: defaults");
    }
    println!("   Host: {}", config.host);
    println!("   Port: {}", config.port);
    println!("   Address: http://{}", addr);
    println!();

    // Scan and display available agents
    let agents = scan_available_agents();
    if !agents.is_empty() {
        println!("🤖 Available Agents ({}):", agents.len());
        for agent in &agents {
            println!("   • {}", agent);
        }
        println!();
    } else {
        println!("⚠️  No agents found in ./agents/ directory");
        println!();
    }

    tracing::info!("Listening on: http://{}", addr);
    tracing::info!("Health check: http://{}/health", addr);

    println!("{}", style("📡 API Endpoints:").bold());
    println!("   GET  /health                    - Health check");
    println!("   POST /execute                   - Execute agent from file");
    println!("   POST /agents/{{name}}/execute     - Execute agent by name");
    println!("   POST /transpile                 - Transpile MDX to Nickel");
    println!("   POST /validate                  - Validate agent file");
    println!();
    println!("{}", style("Press Ctrl+C to stop the server").dim());
    println!();

    let listener = tokio::net::TcpListener::bind(&addr)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to bind to {}: {}", addr, e))?;

    axum::serve(listener, app())
        .await
        .map_err(|e| anyhow::anyhow!("Server error: {}", e))?;

    Ok(())
}

/// Scan for available agents in the agents directory
fn scan_available_agents() -> Vec<String> {
    let agents_dir = std::path::Path::new("./agents");
    if !agents_dir.exists() {
        return Vec::new();
    }

    let mut agents = Vec::new();
    if let Ok(entries) = std::fs::read_dir(agents_dir) {
        for entry in entries.flatten() {
            if let Some(filename) = entry.file_name().to_str() {
                if filename.ends_with(".agent.mdx") {
                    let name = filename.strip_suffix(".agent.mdx").unwrap_or(filename);
                    agents.push(name.to_string());
                }
            }
        }
    }

    agents.sort();
    agents
}

/// Format bytes as human-readable string
fn format_bytes(bytes: u64) -> String {
    const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
    let mut size = bytes as f64;
    let mut unit_idx = 0;

    while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
        size /= 1024.0;
        unit_idx += 1;
    }

    format!("{:.2} {}", size, UNITS[unit_idx])
}
