use crate::cli::render::{Icons, Theme};
use crate::core::error::{LoreError, Result};
use crate::core::trace::{TraceChain, TraceResult};

/// Parse a path with optional `:line` suffix.
///
/// Handles Windows drive letters (e.g. `C:/foo.rs`) by checking that the
/// prefix before the colon is not a single ASCII letter.
pub fn parse_trace_path(input: &str) -> (String, Option<u32>) {
    if let Some((path, suffix)) = input.rsplit_once(':')
        && !path.is_empty()
        && let Ok(line) = suffix.parse::<u32>()
        // Reject Windows drive letters: single ASCII letter before colon
        && (path.len() > 1 || !path.chars().next().unwrap_or(' ').is_ascii_alphabetic())
    {
        return (path.to_string(), Some(line));
    }
    (input.to_string(), None)
}

// ── Human output ────────────────────────────────────────────────────────────

pub fn print_trace(result: &TraceResult) {
    let chain_info = if result.total_chains == 1 {
        "1 chain".to_string()
    } else {
        format!("{} chains", result.total_chains)
    };

    let paths_info = if result.resolved_paths.len() > 1 {
        format!(", {} paths", result.resolved_paths.len())
    } else {
        String::new()
    };

    println!();
    println!(
        "{}",
        Theme::bold().render(&format!(
            "Trace: {} ({}{})",
            result.path, chain_info, paths_info
        ))
    );

    // Rename chain
    if result.renames_followed && result.resolved_paths.len() > 1 {
        let chain_str: Vec<&str> = result.resolved_paths.iter().map(String::as_str).collect();
        println!(
            "  Rename chain: {}",
            Theme::dim().render(&chain_str.join(" -> "))
        );
    }

    // Show searched paths when there are renames but no chains
    if result.trace_chains.is_empty() {
        println!(
            "\n  {} {}",
            Icons::info(),
            Theme::dim().render("No trace chains found for this file.")
        );
        if !result.renames_followed && result.resolved_paths.len() == 1 {
            println!(
                "  {} Searched: {}",
                Icons::info(),
                Theme::dim().render(&result.resolved_paths[0])
            );
        }
        for hint in &result.hints {
            println!("  {} {}", Icons::info(), Theme::dim().render(hint));
        }
        println!();
        return;
    }

    println!();

    for chain in &result.trace_chains {
        print_chain(chain);
    }

    println!();
}

fn print_chain(chain: &TraceChain) {
    let (icon, state_style) = match chain.mr_state.as_str() {
        "merged" => (Icons::mr_merged(), Theme::accent()),
        "opened" => (Icons::mr_opened(), Theme::success()),
        "closed" => (Icons::mr_closed(), Theme::warning()),
        _ => (Icons::mr_opened(), Theme::dim()),
    };

    let date = chain
        .merged_at_iso
        .as_deref()
        .or(Some(chain.updated_at_iso.as_str()))
        .unwrap_or("")
        .split('T')
        .next()
        .unwrap_or("");

    println!(
        "  {} {}  {}  {}  @{}  {}  {}",
        icon,
        Theme::accent().render(&format!("!{}", chain.mr_iid)),
        chain.mr_title,
        state_style.render(&chain.mr_state),
        chain.mr_author,
        date,
        Theme::dim().render(&chain.change_type),
    );

    // Linked issues
    for issue in &chain.issues {
        let ref_icon = match issue.reference_type.as_str() {
            "closes" => Icons::issue_closed(),
            _ => Icons::issue_opened(),
        };

        println!(
            "    {} #{} {} {} [{}]",
            ref_icon,
            issue.iid,
            issue.title,
            Theme::dim().render(&issue.state),
            Theme::dim().render(&issue.reference_type),
        );
    }

    // Discussions
    for disc in &chain.discussions {
        let date = disc.created_at_iso.split('T').next().unwrap_or("");
        println!(
            "    {} @{} ({}) [{}]: {}",
            Icons::note(),
            disc.author_username,
            date,
            Theme::dim().render(&disc.path),
            disc.body
        );
    }
}

// ── Robot (JSON) output ─────────────────────────────────────────────────────

/// Maximum body length in robot JSON output (token efficiency).
const ROBOT_BODY_SNIPPET_LEN: usize = 500;

fn truncate_body(body: &str, max: usize) -> String {
    if body.len() <= max {
        return body.to_string();
    }
    let boundary = body.floor_char_boundary(max);
    format!("{}...", &body[..boundary])
}

pub fn print_trace_json(
    result: &TraceResult,
    elapsed_ms: u64,
    line_requested: Option<u32>,
) -> Result<()> {
    // Truncate discussion bodies for token efficiency in robot mode
    let chains: Vec<serde_json::Value> = result
        .trace_chains
        .iter()
        .map(|chain| {
            let discussions: Vec<serde_json::Value> = chain
                .discussions
                .iter()
                .map(|d| {
                    serde_json::json!({
                        "discussion_id": d.discussion_id,
                        "mr_iid": d.mr_iid,
                        "author_username": d.author_username,
                        "body_snippet": truncate_body(&d.body, ROBOT_BODY_SNIPPET_LEN),
                        "path": d.path,
                        "created_at_iso": d.created_at_iso,
                    })
                })
                .collect();

            serde_json::json!({
                "mr_iid": chain.mr_iid,
                "mr_title": chain.mr_title,
                "mr_state": chain.mr_state,
                "mr_author": chain.mr_author,
                "change_type": chain.change_type,
                "merged_at_iso": chain.merged_at_iso,
                "updated_at_iso": chain.updated_at_iso,
                "web_url": chain.web_url,
                "issues": chain.issues,
                "discussions": discussions,
            })
        })
        .collect();

    let output = serde_json::json!({
        "ok": true,
        "data": {
            "path": result.path,
            "resolved_paths": result.resolved_paths,
            "trace_chains": chains,
        },
        "meta": {
            "tier": "api_only",
            "line_requested": line_requested,
            "elapsed_ms": elapsed_ms,
            "total_chains": result.total_chains,
            "renames_followed": result.renames_followed,
            "hints": if result.hints.is_empty() { None } else { Some(&result.hints) },
        }
    });

    println!(
        "{}",
        serde_json::to_string(&output)
            .map_err(|e| LoreError::Other(format!("JSON serialization failed: {e}")))?
    );
    Ok(())
}

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

    #[test]
    fn test_parse_trace_path_simple() {
        let (path, line) = parse_trace_path("src/foo.rs");
        assert_eq!(path, "src/foo.rs");
        assert_eq!(line, None);
    }

    #[test]
    fn test_parse_trace_path_with_line() {
        let (path, line) = parse_trace_path("src/foo.rs:42");
        assert_eq!(path, "src/foo.rs");
        assert_eq!(line, Some(42));
    }

    #[test]
    fn test_parse_trace_path_windows() {
        let (path, line) = parse_trace_path("C:/foo.rs");
        assert_eq!(path, "C:/foo.rs");
        assert_eq!(line, None);
    }

    #[test]
    fn test_parse_trace_path_directory() {
        let (path, line) = parse_trace_path("src/auth/");
        assert_eq!(path, "src/auth/");
        assert_eq!(line, None);
    }

    #[test]
    fn test_parse_trace_path_with_line_zero() {
        let (path, line) = parse_trace_path("file.rs:0");
        assert_eq!(path, "file.rs");
        assert_eq!(line, Some(0));
    }
}
