//! Benchmarks for git context operations.
//!
//! Run with: cargo bench --bench git_context

use std::fs;
use std::path::Path;
use std::time::Instant;

use git2::Repository;

// Import the actual implementations to test them
use mi6_core::{get_current_branch, get_github_repo, get_local_git_dir};

/// Direct file-based approach to get GitHub repo from config
fn get_github_repo_direct(cwd: &Path) -> Option<String> {
    // Walk up the directory tree to find .git
    let mut current = cwd;
    let git_dir = loop {
        let git_path = current.join(".git");
        if git_path.exists() {
            // Check if it's a file (worktree) or directory (normal repo)
            if git_path.is_file() {
                // Worktree: .git file contains "gitdir: /path/to/.git/worktrees/name"
                let content = fs::read_to_string(&git_path).ok()?;
                let gitdir = content.strip_prefix("gitdir: ")?.trim();
                // For worktrees, config is in the main .git directory
                // The worktree .git points to .git/worktrees/name, we need .git/config
                let worktree_git = Path::new(gitdir);
                // Go up two levels: .git/worktrees/name -> .git
                let main_git = worktree_git.parent()?.parent()?;
                break main_git.to_path_buf();
            } else {
                break git_path;
            }
        }
        current = current.parent()?;
    };

    // Read config file
    let config_path = git_dir.join("config");
    let config = fs::read_to_string(config_path).ok()?;

    // Parse for origin URL - simple approach
    let mut in_origin = false;
    for line in config.lines() {
        let trimmed = line.trim();
        if trimmed == "[remote \"origin\"]" {
            in_origin = true;
        } else if trimmed.starts_with('[') {
            in_origin = false;
        } else if in_origin && trimmed.starts_with("url = ") {
            let url = trimmed.strip_prefix("url = ")?;
            return parse_github_repo_from_remote_url(url);
        }
    }

    None
}

/// Get just the git directory path - direct approach
fn get_local_git_dir_direct(cwd: &Path) -> Option<String> {
    // Walk up the directory tree to find .git
    let mut current = cwd;
    loop {
        let git_path = current.join(".git");
        if git_path.exists() {
            if git_path.is_file() {
                // Worktree: .git file contains "gitdir: /path/to/.git/worktrees/name"
                let content = fs::read_to_string(&git_path).ok()?;
                let gitdir = content.strip_prefix("gitdir: ")?.trim();
                return Some(gitdir.to_string());
            } else {
                return git_path
                    .canonicalize()
                    .ok()
                    .map(|p| p.to_string_lossy().to_string());
            }
        }
        current = current.parent()?;
    }
}

/// Combined approach - get both values with one Repository::discover call
fn get_git_context_combined(cwd: &Path) -> (Option<String>, Option<String>) {
    let repo = match Repository::discover(cwd) {
        Ok(r) => r,
        Err(_) => return (None, None),
    };

    // Get git dir
    let git_dir = repo
        .path()
        .canonicalize()
        .ok()
        .map(|p| p.to_string_lossy().to_string());

    // Get GitHub repo
    let github_repo = repo
        .find_remote("origin")
        .ok()
        .and_then(|r| r.url().map(String::from))
        .and_then(|url| parse_github_repo_from_remote_url(&url));

    (git_dir, github_repo)
}

/// Original git2 approach - separate calls
fn get_local_git_dir_git2(cwd: &Path) -> Option<String> {
    let repo = Repository::discover(cwd).ok()?;
    let path = repo.path();
    path.canonicalize()
        .ok()
        .map(|p| p.to_string_lossy().to_string())
}

fn get_github_repo_git2(cwd: &Path) -> Option<String> {
    let repo = Repository::discover(cwd).ok()?;
    let remote = repo.find_remote("origin").ok()?;
    let url = remote.url()?;
    parse_github_repo_from_remote_url(url)
}

/// Get current branch using git2
fn get_current_branch_git2(cwd: &Path) -> Option<String> {
    let repo = Repository::discover(cwd).ok()?;
    let head = repo.head().ok()?;
    if !head.is_branch() {
        return None;
    }
    head.shorthand().map(|s| s.to_string())
}

/// Get current branch by reading .git/HEAD directly
fn get_current_branch_direct(cwd: &Path) -> Option<String> {
    // Find the .git directory
    let git_dir = find_git_dir_direct(cwd)?;

    // Read HEAD file
    let head_path = git_dir.join("HEAD");
    let head_content = fs::read_to_string(head_path).ok()?;
    let head_content = head_content.trim();

    // Parse HEAD - it's either "ref: refs/heads/branch" or a raw SHA (detached)
    if let Some(ref_path) = head_content.strip_prefix("ref: ") {
        // Extract branch name from refs/heads/branch
        ref_path.strip_prefix("refs/heads/").map(|s| s.to_string())
    } else {
        // Detached HEAD - no branch
        None
    }
}

/// Helper to find git dir for direct benchmarks
fn find_git_dir_direct(cwd: &Path) -> Option<std::path::PathBuf> {
    let mut current = cwd;
    loop {
        let git_path = current.join(".git");
        if git_path.exists() {
            if git_path.is_file() {
                // Worktree: .git file contains "gitdir: /path/to/.git/worktrees/name"
                let content = fs::read_to_string(&git_path).ok()?;
                let gitdir = content.strip_prefix("gitdir: ")?.trim();
                return Some(std::path::PathBuf::from(gitdir));
            } else {
                return Some(git_path);
            }
        }
        current = current.parent()?;
    }
}

fn parse_github_repo_from_remote_url(url: &str) -> Option<String> {
    if let Some(rest) = url.strip_prefix("git@github.com:") {
        return Some(rest.trim_end_matches(".git").to_string());
    }
    if let Some(rest) = url
        .strip_prefix("https://github.com/")
        .or_else(|| url.strip_prefix("http://github.com/"))
    {
        return Some(rest.trim_end_matches(".git").to_string());
    }
    if let Some(rest) = url.strip_prefix("ssh://git@github.com/") {
        return Some(rest.trim_end_matches(".git").to_string());
    }
    None
}

fn benchmark<F, R>(name: &str, iterations: u32, f: F) -> f64
where
    F: Fn() -> R,
{
    // Warmup
    for _ in 0..10 {
        let _ = f();
    }

    // Actual benchmark
    let start = Instant::now();
    for _ in 0..iterations {
        let _ = f();
    }
    let elapsed = start.elapsed();
    let avg_us = elapsed.as_micros() as f64 / iterations as f64;
    println!(
        "{}: {:.1} µs average ({} iterations)",
        name, avg_us, iterations
    );
    avg_us
}

fn main() {
    let cwd = std::env::current_dir().expect("need cwd");
    println!("Benchmarking git context operations in: {:?}\n", cwd);

    let iterations = 1000;

    println!("=== Actual Implementation (mi6_core exports) ===");
    benchmark("mi6: get_local_git_dir", iterations, || {
        get_local_git_dir(&cwd)
    });
    benchmark("mi6: get_github_repo", iterations, || get_github_repo(&cwd));
    benchmark("mi6: get_current_branch", iterations, || {
        get_current_branch(&cwd)
    });

    println!("\n=== git2 vs direct comparison ===");
    benchmark("git2: get_local_git_dir", iterations, || {
        get_local_git_dir_git2(&cwd)
    });
    benchmark("direct: get_local_git_dir", iterations, || {
        get_local_git_dir_direct(&cwd)
    });

    benchmark("git2: get_github_repo", iterations, || {
        get_github_repo_git2(&cwd)
    });
    benchmark("direct: get_github_repo", iterations, || {
        get_github_repo_direct(&cwd)
    });

    benchmark("git2: get_current_branch", iterations, || {
        get_current_branch_git2(&cwd)
    });
    benchmark("direct: get_current_branch", iterations, || {
        get_current_branch_direct(&cwd)
    });

    println!("\n=== Combined context (hook call overhead) ===");
    benchmark("git2: all three separate", iterations, || {
        (
            get_local_git_dir_git2(&cwd),
            get_github_repo_git2(&cwd),
            get_current_branch_git2(&cwd),
        )
    });
    benchmark("direct: all three separate", iterations, || {
        (
            get_local_git_dir_direct(&cwd),
            get_github_repo_direct(&cwd),
            get_current_branch_direct(&cwd),
        )
    });

    println!("\n=== Breakdown: Repository::discover only ===");
    benchmark("git2: Repository::discover only", iterations, || {
        Repository::discover(&cwd)
    });

    println!("\n=== Verification ===");
    println!("git_dir:");
    println!("  git2:   {:?}", get_local_git_dir_git2(&cwd));
    println!("  direct: {:?}", get_local_git_dir_direct(&cwd));
    println!("  mi6:    {:?}", get_local_git_dir(&cwd));

    println!("github_repo:");
    println!("  git2:   {:?}", get_github_repo_git2(&cwd));
    println!("  direct: {:?}", get_github_repo_direct(&cwd));
    println!("  mi6:    {:?}", get_github_repo(&cwd));

    println!("current_branch:");
    println!("  git2:   {:?}", get_current_branch_git2(&cwd));
    println!("  direct: {:?}", get_current_branch_direct(&cwd));
    println!("  mi6:    {:?}", get_current_branch(&cwd));
}
