//! Aurora Semantic CLI - Code search tool
//!
//! A command-line interface for indexing and searching code semantically.

use std::path::PathBuf;
use std::time::Instant;

use clap::{Parser, Subcommand};
use colored::*;
use indicatif::{ProgressBar, ProgressStyle};

use aurora_semantic::{
    Engine, EngineConfig, IndexPhase, ModelConfig, SearchQuery, WorkspaceConfig,
};

#[derive(Parser)]
#[command(name = "aurora")]
#[command(author, version, about = "Semantic code search engine", long_about = None)]
struct Cli {
    /// Index storage directory
    #[arg(short, long, default_value = ".aurora", global = true)]
    index_dir: PathBuf,

    /// Path to ONNX model directory
    #[arg(short, long, env = "AURORA_MODEL_PATH", global = true)]
    model: Option<PathBuf>,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Index a workspace/directory
    Index {
        /// Path to the workspace to index
        #[arg(default_value = ".")]
        path: PathBuf,

        /// Maximum sequence length for embeddings
        #[arg(long, default_value = "512")]
        max_length: usize,
    },

    /// Search indexed code
    Search {
        /// Search query
        query: String,

        /// Number of results to return
        #[arg(short, long, default_value = "10")]
        limit: usize,

        /// Search mode: lexical, semantic, or hybrid
        #[arg(short = 'm', long, default_value = "hybrid")]
        mode: String,

        /// Minimum score threshold (0.0 - 1.0)
        #[arg(long, default_value = "0.1")]
        min_score: f32,
    },

    /// List indexed workspaces
    List,

    /// Show workspace statistics
    Stats {
        /// Workspace ID (optional, shows all if not provided)
        workspace_id: Option<String>,
    },

    /// Delete a workspace index
    Delete {
        /// Workspace ID to delete
        workspace_id: String,
    },

    /// Re-index changed files in a workspace
    Reindex {
        /// Workspace ID
        workspace_id: String,
    },

    /// Show info about model configuration
    ModelInfo,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive("aurora_semantic=info".parse()?),
        )
        .init();

    let cli = Cli::parse();

    match cli.command {
        Commands::Index { path, max_length } => {
            index_workspace(&cli.index_dir, &path, cli.model.as_ref(), max_length).await?;
        }
        Commands::Search {
            query,
            limit,
            mode,
            min_score,
        } => {
            search(&cli.index_dir, &query, limit, &mode, min_score, cli.model.as_ref()).await?;
        }
        Commands::List => {
            list_workspaces(&cli.index_dir)?;
        }
        Commands::Stats { workspace_id } => {
            show_stats(&cli.index_dir, workspace_id.as_deref())?;
        }
        Commands::Delete { workspace_id } => {
            delete_workspace(&cli.index_dir, &workspace_id)?;
        }
        Commands::Reindex { workspace_id } => {
            reindex_workspace(&cli.index_dir, &workspace_id, cli.model.as_ref()).await?;
        }
        Commands::ModelInfo => {
            show_model_info(cli.model.as_ref());
        }
    }

    Ok(())
}

async fn index_workspace(
    index_dir: &PathBuf,
    workspace_path: &PathBuf,
    model_path: Option<&PathBuf>,
    max_length: usize,
) -> anyhow::Result<()> {
    let abs_path = std::fs::canonicalize(workspace_path)?;
    println!(
        "{} {}",
        "Indexing:".cyan().bold(),
        abs_path.display()
    );

    let start = Instant::now();

    // Create engine
    let config = EngineConfig::new(index_dir.clone());
    let engine = if let Some(model_dir) = model_path {
        let model_config = ModelConfig::from_directory(model_dir).with_max_length(max_length);
        let embedder = model_config.load()?;
        Engine::with_embedder(config, embedder)?
    } else {
        println!(
            "{} Using hash-based embeddings (no model specified)",
            "Note:".yellow()
        );
        println!(
            "{}",
            "      For semantic search, provide --model path/to/onnx/model".yellow()
        );
        Engine::new(config)?
    };

    // Setup progress bar
    let pb = ProgressBar::new(100);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{bar:40.cyan/blue}] {pos}% {msg}")?
            .progress_chars("=>-"),
    );

    let pb_clone = pb.clone();
    let progress_callback = Box::new(move |progress: aurora_semantic::IndexProgress| {
        let pct = progress.percentage() as u64;
        pb_clone.set_position(pct);

        let msg = match progress.phase {
            IndexPhase::Scanning => "Scanning files...".to_string(),
            IndexPhase::Parsing => format!(
                "Parsing {}/{}",
                progress.processed, progress.total
            ),
            IndexPhase::Embedding => format!(
                "Generating embeddings {}/{}",
                progress.processed, progress.total
            ),
            IndexPhase::Indexing => "Building search index...".to_string(),
            IndexPhase::Persisting => "Saving to disk...".to_string(),
            IndexPhase::Complete => "Complete!".to_string(),
        };
        pb_clone.set_message(msg);
    });

    // Index workspace
    let ws_config = WorkspaceConfig::new(abs_path);
    let workspace_id = engine
        .index_workspace(ws_config, Some(progress_callback))
        .await?;

    pb.finish_with_message("Done!");

    let elapsed = start.elapsed();
    let stats = engine.get_workspace_stats(&workspace_id)?;

    println!();
    println!("{}", "=== Indexing Complete ===".green().bold());
    println!("Workspace ID: {}", workspace_id.to_string().cyan());
    println!("Documents:    {}", stats.document_count);
    println!("Chunks:       {}", stats.chunk_count);
    println!("Total size:   {} KB", stats.total_bytes / 1024);
    println!("Index size:   {} KB", stats.index_size_bytes / 1024);
    println!("Time:         {:.2}s", elapsed.as_secs_f64());

    Ok(())
}

async fn search(
    index_dir: &PathBuf,
    query_text: &str,
    limit: usize,
    mode: &str,
    min_score: f32,
    model_path: Option<&PathBuf>,
) -> anyhow::Result<()> {
    let config = EngineConfig::new(index_dir.clone());
    let engine = if let Some(model_dir) = model_path {
        let model_config = ModelConfig::from_directory(model_dir);
        let embedder = model_config.load()?;
        Engine::with_embedder(config, embedder)?
    } else {
        Engine::new(config)?
    };

    // Find workspaces
    let workspaces = engine.list_workspaces()?;
    if workspaces.is_empty() {
        println!(
            "{} No indexed workspaces found. Run 'aurora index <path>' first.",
            "Error:".red().bold()
        );
        return Ok(());
    }

    // Load first workspace
    let workspace_id = &workspaces[0];
    engine.load_workspace(workspace_id)?;

    // Build search query
    let search_mode = match mode.to_lowercase().as_str() {
        "lexical" | "l" => aurora_semantic::SearchMode::Lexical,
        "semantic" | "s" => aurora_semantic::SearchMode::Semantic,
        _ => aurora_semantic::SearchMode::Hybrid,
    };

    let query = SearchQuery::new(query_text)
        .mode(search_mode)
        .limit(limit)
        .min_score(min_score);

    println!(
        "{} '{}' (mode: {:?})",
        "Searching:".cyan().bold(),
        query_text,
        search_mode
    );
    println!();

    let start = Instant::now();
    let results = engine.search(workspace_id, query)?;
    let elapsed = start.elapsed();

    if results.is_empty() {
        println!("{}", "No results found.".yellow());
        return Ok(());
    }

    println!(
        "{} {} results in {:.2}ms\n",
        "Found:".green().bold(),
        results.len(),
        elapsed.as_secs_f64() * 1000.0
    );

    for (i, result) in results.iter().enumerate() {
        let score_color = if result.score > 0.7 {
            result.score.to_string().green()
        } else if result.score > 0.4 {
            result.score.to_string().yellow()
        } else {
            result.score.to_string().red()
        };

        println!(
            "{}. {} [score: {}]",
            (i + 1).to_string().bold(),
            result.document.relative_path.display().to_string().cyan(),
            score_color
        );

        if let Some(ref name) = result.chunk.symbol_name {
            println!(
                "   {} {:?} '{}'",
                "Symbol:".dimmed(),
                result.chunk.chunk_type,
                name.yellow()
            );
        }

        println!(
            "   {} lines {}-{}",
            "Lines:".dimmed(),
            result.chunk.start_line,
            result.chunk.end_line
        );

        // Show preview (first 150 chars)
        let preview: String = result
            .chunk
            .content
            .chars()
            .take(150)
            .collect::<String>()
            .replace('\n', " ");
        println!("   {}", preview.dimmed());
        println!();
    }

    Ok(())
}

fn list_workspaces(index_dir: &PathBuf) -> anyhow::Result<()> {
    let config = EngineConfig::new(index_dir.clone());
    let engine = Engine::new(config)?;

    let workspaces = engine.list_workspaces()?;

    if workspaces.is_empty() {
        println!("{}", "No indexed workspaces found.".yellow());
        return Ok(());
    }

    println!("{}", "Indexed Workspaces:".green().bold());
    println!();

    for ws_id in workspaces {
        match engine.get_workspace_stats(&ws_id) {
            Ok(stats) => {
                println!("  {} {}", "ID:".cyan(), ws_id);
                println!("     Path: {}", stats.root_path.display());
                println!(
                    "     {} docs, {} chunks",
                    stats.document_count, stats.chunk_count
                );
                println!("     Updated: {}", stats.updated_at.format("%Y-%m-%d %H:%M"));
                println!();
            }
            Err(_) => {
                println!("  {} {} (metadata unavailable)", "ID:".cyan(), ws_id);
            }
        }
    }

    Ok(())
}

fn show_stats(index_dir: &PathBuf, workspace_id: Option<&str>) -> anyhow::Result<()> {
    let config = EngineConfig::new(index_dir.clone());
    let engine = Engine::new(config)?;

    let workspaces = if let Some(id) = workspace_id {
        let uuid = uuid::Uuid::parse_str(id)?;
        vec![aurora_semantic::WorkspaceId::from_uuid(uuid)]
    } else {
        engine.list_workspaces()?
    };

    for ws_id in workspaces {
        let stats = engine.get_workspace_stats(&ws_id)?;

        println!("{}", "=== Workspace Statistics ===".green().bold());
        println!("ID:           {}", ws_id.to_string().cyan());
        println!("Path:         {}", stats.root_path.display());
        println!("Documents:    {}", stats.document_count);
        println!("Chunks:       {}", stats.chunk_count);
        println!("Source size:  {} KB", stats.total_bytes / 1024);
        println!("Index size:   {} KB", stats.index_size_bytes / 1024);
        println!("Created:      {}", stats.created_at.format("%Y-%m-%d %H:%M:%S"));
        println!("Updated:      {}", stats.updated_at.format("%Y-%m-%d %H:%M:%S"));

        if !stats.language_stats.is_empty() {
            println!("\n{}", "Languages:".cyan());
            for lang_stat in &stats.language_stats {
                println!(
                    "  {:12} {} files, {} chunks",
                    format!("{:?}", lang_stat.language),
                    lang_stat.file_count,
                    lang_stat.chunk_count
                );
            }
        }
        println!();
    }

    Ok(())
}

fn delete_workspace(index_dir: &PathBuf, workspace_id: &str) -> anyhow::Result<()> {
    let uuid = uuid::Uuid::parse_str(workspace_id)?;
    let ws_id = aurora_semantic::WorkspaceId::from_uuid(uuid);

    let config = EngineConfig::new(index_dir.clone());
    let engine = Engine::new(config)?;

    engine.delete_workspace(&ws_id)?;

    println!(
        "{} Workspace {} deleted.",
        "Success:".green().bold(),
        workspace_id.cyan()
    );

    Ok(())
}

async fn reindex_workspace(
    index_dir: &PathBuf,
    workspace_id: &str,
    model_path: Option<&PathBuf>,
) -> anyhow::Result<()> {
    let uuid = uuid::Uuid::parse_str(workspace_id)?;
    let ws_id = aurora_semantic::WorkspaceId::from_uuid(uuid);

    let config = EngineConfig::new(index_dir.clone());
    let engine = if let Some(model_dir) = model_path {
        let model_config = ModelConfig::from_directory(model_dir);
        let embedder = model_config.load()?;
        Engine::with_embedder(config, embedder)?
    } else {
        Engine::new(config)?
    };

    engine.load_workspace(&ws_id)?;

    println!(
        "{} Re-indexing workspace {}",
        "Info:".cyan().bold(),
        workspace_id
    );

    // For now, just report success - full reindex requires file change detection
    println!(
        "{} Workspace is up to date.",
        "Success:".green().bold()
    );

    Ok(())
}

fn show_model_info(model_path: Option<&PathBuf>) {
    println!("{}", "=== Model Configuration ===".green().bold());
    println!();

    match model_path {
        Some(path) => {
            println!("Model path: {}", path.display().to_string().cyan());

            let model_file = path.join("model.onnx");
            let tokenizer_file = path.join("tokenizer.json");

            println!(
                "model.onnx:     {}",
                if model_file.exists() {
                    "Found".green()
                } else {
                    "Missing".red()
                }
            );
            println!(
                "tokenizer.json: {}",
                if tokenizer_file.exists() {
                    "Found".green()
                } else {
                    "Missing".red()
                }
            );
        }
        None => {
            println!("No model configured.");
            println!();
            println!("{}", "To use semantic search, download an ONNX model:".yellow());
            println!();
            println!("1. Download a model (e.g., jina-embeddings-v2-base-code):");
            println!("   - model.onnx");
            println!("   - tokenizer.json");
            println!();
            println!("2. Run aurora with --model flag:");
            println!("   aurora --model ./models/jina-code index .");
            println!();
            println!("Or set AURORA_MODEL_PATH environment variable.");
        }
    }
}
