use anyhow::{anyhow, Result};
use colored::*;

use std::path::PathBuf;

use crate::cli::client::get_client;
use crate::cli::display::display_search_result;
use crate::cli::server_manager::ensure_server_running;
use crate::server::services::search::SearchCommandOptions;
use crate::server::types::SearchRequest;
// CLI is now a pure thin client - no business logic imports needed

/// Add a new insight to the knowledge base (production version)
pub async fn add_insight(topic: &str, name: &str, overview: &str, details: &str) -> Result<()> {
    ensure_server_running().await?;
    let client = get_client();
    client.add_insight(topic, name, overview, details).await?;

    println!(
        "{} Added insight {}/{}",
        "✓".green(),
        topic.cyan(),
        name.yellow()
    );
    Ok(())
}

/// Get content of a specific insight
pub async fn get_insight(topic: &str, name: &str, overview_only: bool) -> Result<()> {
    ensure_server_running().await?;

    let client = get_client();
    let response = client.get_insight(topic, name, overview_only).await?;

    if overview_only {
        println!("{}", response.insight.overview);
    } else {
        println!(
            "---\n{}\n---\n\n{}",
            response.insight.overview, response.insight.details
        );
    }

    Ok(())
}

pub async fn list_insights(
    filter: Option<&str>,
    verbose: bool,
    pinned_only: bool,
    sort: crate::server::types::SearchSort,
) -> Result<()> {
    ensure_server_running().await?;

    let client = get_client();
    let response = client.list_insights(Vec::new()).await?;

    let mut insights: Vec<_> = response
        .insights
        .into_iter()
        .filter(|insight| {
            let topic_match = filter.is_none_or(|t| insight.topic == t);
            let pin_match = !pinned_only || insight.pinned;
            topic_match && pin_match
        })
        .collect();

    sort_insight_summaries(&mut insights, sort);

    if insights.is_empty() {
        if pinned_only {
            println!("No pinned insights found.");
        } else if let Some(topic) = filter {
            println!("No insights found for topic: {}", topic.yellow());
        } else {
            println!("No insights found.");
        }
        return Ok(());
    }

    use std::collections::BTreeMap;
    let mut by_topic: BTreeMap<String, Vec<_>> = BTreeMap::new();
    for insight in insights {
        by_topic
            .entry(insight.topic.clone())
            .or_insert_with(Vec::new)
            .push(insight);
    }

    for (topic, insights) in by_topic {
        println!("{} {}", "📂".cyan(), topic.blue().bold());

        for insight in insights {
            let pin_indicator = if insight.pinned { " 📌" } else { "" };

            if verbose {
                let total_access = insight.retrieval_count + insight.search_hit_count;
                let access_info = format!(
                    "gets:{} hits:{} total:{}",
                    insight.retrieval_count, insight.search_hit_count, total_access
                );
                let last_accessed_info = insight
                    .last_accessed
                    .map(|ts| format!(" last:{}", ts.format("%Y-%m-%d")))
                    .unwrap_or_default();

                println!(
                    "  {} {}{} - {}",
                    "📄".yellow(),
                    insight.name.bold(),
                    pin_indicator,
                    insight.overview.dimmed()
                );
                println!(
                    "      {}{}",
                    access_info.dimmed(),
                    last_accessed_info.dimmed()
                );
            } else {
                println!(
                    "  {} {}{}",
                    "📄".yellow(),
                    insight.name.bold(),
                    pin_indicator
                );
            }
        }
        println!();
    }

    Ok(())
}

fn sort_insight_summaries(
    insights: &mut [crate::server::types::InsightSummary],
    sort: crate::server::types::SearchSort,
) {
    match sort {
        crate::server::types::SearchSort::Relevance => {}
        crate::server::types::SearchSort::Updated => {
            insights.sort_by_key(|b| std::cmp::Reverse(b.updated_at));
        }
        crate::server::types::SearchSort::Created => {
            insights.sort_by_key(|b| std::cmp::Reverse(b.created_at));
        }
        crate::server::types::SearchSort::LeastAccessed => {
            insights.sort_by(|a, b| {
                let a_total = a.retrieval_count + a.search_hit_count;
                let b_total = b.retrieval_count + b.search_hit_count;
                a_total
                    .cmp(&b_total)
                    .then_with(|| a.last_accessed.cmp(&b.last_accessed))
            });
        }
    }
}

pub async fn pin_insight(topic: &str, name: &str) -> Result<()> {
    ensure_server_running().await?;
    let client = get_client();
    client.pin_insight(topic, name).await?;

    println!(
        "{} Pinned insight {}/{}",
        "📌".cyan(),
        topic.cyan(),
        name.yellow()
    );
    Ok(())
}

pub async fn unpin_insight(topic: &str, name: &str) -> Result<()> {
    ensure_server_running().await?;
    let client = get_client();
    client.unpin_insight(topic, name).await?;

    println!(
        "{} Unpinned insight {}/{}",
        "✓".green(),
        topic.cyan(),
        name.yellow()
    );
    Ok(())
}

pub async fn list_topics() -> Result<()> {
    ensure_server_running().await?;

    let client = get_client();
    let response = client.list_topics().await?;

    if response.is_empty() {
        println!("No topics found.");
        return Ok(());
    }

    println!("{} Available topics:", "📂".cyan());
    for topic in response {
        println!("  {}", topic.blue());
    }

    Ok(())
}

pub async fn update_insight(
    topic: &str,
    name: &str,
    overview: Option<&str>,
    details: Option<&str>,
) -> Result<()> {
    if overview.is_none() && details.is_none() {
        return Err(anyhow!(
            "At least one of --overview or --details must be specified"
        ));
    }

    ensure_server_running().await?;

    let client = get_client();
    client
        .update_insight(topic, name, overview, details)
        .await?;

    println!(
        "{} Updated insight {}/{}",
        "✓".green(),
        topic.cyan(),
        name.yellow()
    );
    Ok(())
}

pub async fn delete_insight(topic: &str, name: &str, force: bool) -> Result<()> {
    ensure_server_running().await?;
    let client = get_client();

    // First check if the insight exists
    match client.get_insight(topic, name, true).await {
        Ok(_) => {
            // Insight exists, proceed with deletion
            if !force {
                // Ask for confirmation
                print!(
                    "Are you sure you want to delete insight {}/{}? (y/N): ",
                    topic.cyan(),
                    name.yellow()
                );
                std::io::Write::flush(&mut std::io::stdout())?;

                let mut input = String::new();
                std::io::stdin().read_line(&mut input)?;

                let response = input.trim().to_lowercase();
                if response != "y" && response != "yes" {
                    println!("Delete operation cancelled.");
                    return Ok(());
                }
            }

            // Proceed with deletion
            client.remove_insight(topic, name).await?;
            println!(
                "{} Deleted insight {}/{}",
                "✓".green(),
                topic.cyan(),
                name.yellow()
            );
            Ok(())
        }
        Err(_) => {
            // Insight doesn't exist
            Err(anyhow!("Insight {topic}/{name} not found"))
        }
    }
}

pub async fn index_insights(_force: bool) -> Result<()> {
    ensure_server_running().await?;
    let client = get_client();

    match client.reindex_insights().await {
        Ok(()) => {
            println!(
                "Re-indexing started. This will run in the background and may take some time."
            );
            println!("Check server logs for progress updates.");
            Ok(())
        }
        Err(e) => {
            println!("{} Failed to start re-indexing: {}", "✗".red(), e);
            Err(e)
        }
    }
}

/// Query daemon logs for debugging and monitoring
pub async fn logs(_limit: usize, _level: &str) -> Result<()> {
    ensure_server_running().await?;

    let client = get_client();

    // TODO: Add support for limit and level parameters to the REST API
    let logs_response = client.get_logs().await?;

    if logs_response.data.logs.is_empty() {
        println!("No logs found.");
        return Ok(());
    }

    for log in logs_response.data.logs {
        print_log_entry(&log);
    }

    Ok(())
}

/// Print a single log entry with formatting
fn print_log_entry(log: &crate::server::types::LogEntry) {
    let level_colored = format_log_level(&log.level);

    // Main log line with timestamp, level, and message
    println!(
        "{} [{}] {}",
        log.timestamp.to_string().cyan(),
        level_colored,
        log.message
    );

    if let Some(context) = &log.context {
        print_log_context(context);
    }
}

/// Format log level with appropriate colors
fn format_log_level(level: &str) -> colored::ColoredString {
    match level {
        "error" => level.red().bold(),
        "warn" => level.yellow().bold(),
        "info" => level.blue().bold(),
        "debug" => level.green(),
        "success" => level.bright_green().bold(),
        _ => level.normal(),
    }
}

/// Print log context information
fn print_log_context(context: &crate::server::types::LogContext) {
    let context_parts = build_context_parts(context);

    if !context_parts.is_empty() {
        for part in context_parts {
            println!("  {} {}", "└─".white().dimmed(), part);
        }
        println!();
    }
}

/// Build context parts from log context
fn build_context_parts(context: &crate::server::types::LogContext) -> Vec<String> {
    let mut context_parts = Vec::new();

    if let Some(request_id) = &context.request_id {
        context_parts.push(format!("request_id: {}", request_id.bright_blue()));
    }

    if let Some(method) = &context.method {
        context_parts.push(format!("method: {}", method.magenta().bold()));
    }

    if let Some(path) = &context.path {
        context_parts.push(format!("path: {}", path.cyan()));
    }

    if let Some(user_agent) = &context.user_agent {
        context_parts.push(format!("user_agent: {}", user_agent.white().dimmed()));
    }

    if let Some(status_code) = context.status_code {
        let status_color = format_status_code(status_code);
        context_parts.push(format!("status: {status_color}"));
    }

    if let Some(duration) = context.duration_ms {
        let duration_color = format_duration(duration);
        context_parts.push(format!("duration: {duration_color}"));
    }

    context_parts
}

/// Format status code with appropriate colors
fn format_status_code(status_code: u16) -> colored::ColoredString {
    match status_code {
        200..=299 => status_code.to_string().green(),
        300..=399 => status_code.to_string().yellow(),
        400..=499 => status_code.to_string().red(),
        500..=599 => status_code.to_string().bright_red().bold(),
        _ => status_code.to_string().white(),
    }
}

/// Format duration with appropriate colors
fn format_duration(duration: f64) -> colored::ColoredString {
    let formatted = format!("{duration:.2}ms");
    if duration < 1.0 {
        formatted.bright_green()
    } else if duration < 10.0 {
        formatted.green()
    } else if duration < 100.0 {
        formatted.yellow()
    } else {
        formatted.red()
    }
}

/// Search through all insights for matching content
pub async fn search_insights(terms: &[String], options: SearchCommandOptions) -> Result<()> {
    ensure_server_running().await?;

    let client = get_client();
    let overview_only = options.overview_only;
    let request = SearchRequest {
        terms: terms.to_vec(),
        topic: options.topic,
        case_sensitive: options.case_sensitive,
        overview_only,
        exact: options.exact,
        semantic: options.semantic,
        sort: options.sort,
        since: options.since,
        until: options.until,
        max_results: Some(options.max_results),
    };
    let response = client.search_insights(request).await?;

    display_search_results(&response.results, terms, overview_only);

    Ok(())
}

/// Display search results (moved from search.rs)
fn display_search_results(
    results: &[crate::server::types::SearchResultData],
    terms: &[String],
    overview_only: bool,
) {
    if results.is_empty() {
        println!("No matches found for: {}", terms.join(" ").yellow());
    } else {
        for result in results {
            display_search_result(
                &result.topic,
                &result.name,
                &result.overview,
                &result.details,
                terms,
                overview_only,
            );
        }
    }
}

const CURSOR_RULE: &str = include_str!("../../.cursor/rules/use-insights.mdc");
const RULE_FILENAME: &str = "use-insights.mdc";

fn find_git_root() -> Option<PathBuf> {
    let mut dir = std::env::current_dir().ok()?;
    loop {
        if dir.join(".git").exists() {
            return Some(dir);
        }
        if !dir.pop() {
            return None;
        }
    }
}

pub fn setup_rules(global: bool) -> Result<()> {
    let rules_dir = if global {
        dirs::home_dir()
            .ok_or_else(|| anyhow::anyhow!("could not determine home directory"))?
            .join(".cursor")
            .join("rules")
    } else {
        match find_git_root() {
            Some(root) => root.join(".cursor").join("rules"),
            None => {
                let home = dirs::home_dir()
                    .ok_or_else(|| anyhow::anyhow!("could not determine home directory"))?;
                println!(
                    "{}",
                    "Not inside a git repository — installing globally.".yellow()
                );
                home.join(".cursor").join("rules")
            }
        }
    };

    std::fs::create_dir_all(&rules_dir)?;

    let dest = rules_dir.join(RULE_FILENAME);
    if dest.exists() {
        println!(
            "{} already exists at {}",
            RULE_FILENAME.cyan(),
            dest.display()
        );
        println!("{}", "Overwriting with latest version.".yellow());
    }

    std::fs::write(&dest, CURSOR_RULE)?;
    println!(
        "{} Installed {} to {}",
        "✓".green(),
        RULE_FILENAME.cyan(),
        dest.display()
    );
    Ok(())
}
