use crate::benchmark::mcp_session::McpSession;
use crate::benchmark::repo_cache::prepare_repo;
use crate::benchmark::report::{
    BenchmarkRepoReport, BenchmarkRunReport, ScenarioReport, ScenarioTransport,
};
use crate::benchmark::subset_workspace::prepare_subset_workspace;
use crate::benchmark::{
    BenchmarkLocationSelector, BenchmarkManifest, BenchmarkRepoTarget, BenchmarkScenario,
    HierarchyQueryTarget,
};
use crate::lsp::conversion::path_to_uri_string;
use crate::lsp::handlers::{call_hierarchy, type_hierarchy};
use crate::{AnalyzerConfig, FilesystemProject, Project, WorkspaceAnalyzer};
use chrono::Utc;
use lsp_types::{
    CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
    PartialResultParams, Position, TextDocumentIdentifier, TextDocumentPositionParams,
    TypeHierarchyPrepareParams, TypeHierarchySubtypesParams, TypeHierarchySupertypesParams, Uri,
    WorkDoneProgressParams,
};
use serde_json::{Value, json};
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Instant;

#[derive(Debug, Clone)]
pub struct RunRequest {
    pub manifest_path: PathBuf,
    pub repo_cache_dir: PathBuf,
    pub selected_repo: Option<String>,
    pub max_files: Option<usize>,
}

pub fn run_benchmark(
    manifest: &BenchmarkManifest,
    request: &RunRequest,
) -> Result<BenchmarkRunReport, String> {
    let selected_repo = request.selected_repo.as_deref();
    let selected_targets: Vec<_> = manifest
        .repos
        .iter()
        .filter(|repo| selected_repo.is_none_or(|name| repo.name == name))
        .collect();

    if selected_targets.is_empty() {
        return Err(match selected_repo {
            Some(name) => format!("manifest contains no repo named `{name}`"),
            None => "manifest contains no repos to run".to_string(),
        });
    }

    let bifrost_commit = current_bifrost_commit();
    let mut repos = Vec::with_capacity(selected_targets.len());
    for target in selected_targets {
        repos.push(run_repo(target, manifest, request)?);
    }

    Ok(BenchmarkRunReport {
        generated_at: Utc::now().to_rfc3339(),
        manifest_path: request.manifest_path.display().to_string(),
        bifrost_commit,
        selected_repo: request.selected_repo.clone(),
        max_files: request.max_files,
        repos,
    })
}

fn run_repo(
    target: &BenchmarkRepoTarget,
    manifest: &BenchmarkManifest,
    request: &RunRequest,
) -> Result<BenchmarkRepoReport, String> {
    let checkout_path = prepare_repo(target, &request.repo_cache_dir)?;
    let workspace_path = match request.max_files {
        Some(max_files) => {
            prepare_subset_workspace(&checkout_path, &request.repo_cache_dir, target, max_files)?
        }
        None => checkout_path.clone(),
    };
    let mut scenario_reports = Vec::with_capacity(target.scenarios.len());

    if target
        .scenario_set()
        .contains(&BenchmarkScenario::WorkspaceBuild)
    {
        scenario_reports.push(run_workspace_build(target, manifest, &workspace_path));
    }

    let mcp_scenarios: Vec<_> = target
        .scenarios
        .iter()
        .copied()
        .filter(|scenario| {
            !matches!(
                scenario,
                BenchmarkScenario::WorkspaceBuild
                    | BenchmarkScenario::CallHierarchy
                    | BenchmarkScenario::TypeHierarchy
            )
        })
        .collect();
    if !mcp_scenarios.is_empty() {
        match McpSession::start(&workspace_path).and_then(|mut session| {
            session.initialize()?;
            Ok(session)
        }) {
            Ok(mut session) => {
                for scenario in mcp_scenarios {
                    scenario_reports.push(run_mcp_scenario(
                        target,
                        manifest,
                        &mut session,
                        scenario,
                    ));
                }
            }
            Err(err) => {
                for scenario in mcp_scenarios {
                    scenario_reports.push(ScenarioReport::from_timings(
                        scenario,
                        ScenarioTransport::Mcp,
                        false,
                        Vec::new(),
                        Vec::new(),
                        Some(format!(
                            "failed to start MCP session for `{}`: {err}",
                            target.name
                        )),
                    ));
                }
            }
        }
    }

    if target
        .scenario_set()
        .contains(&BenchmarkScenario::CallHierarchy)
    {
        scenario_reports.push(run_hierarchy_scenario(
            target,
            manifest,
            &workspace_path,
            BenchmarkScenario::CallHierarchy,
        ));
    }

    if target
        .scenario_set()
        .contains(&BenchmarkScenario::TypeHierarchy)
    {
        scenario_reports.push(run_hierarchy_scenario(
            target,
            manifest,
            &workspace_path,
            BenchmarkScenario::TypeHierarchy,
        ));
    }

    Ok(BenchmarkRepoReport {
        name: target.name.clone(),
        url: target.url.clone(),
        commit: target.commit.clone(),
        checkout_path,
        workspace_path,
        subset_max_files: request.max_files,
        scenarios: scenario_reports,
    })
}

fn run_hierarchy_scenario(
    target: &BenchmarkRepoTarget,
    manifest: &BenchmarkManifest,
    checkout_path: &Path,
    scenario: BenchmarkScenario,
) -> ScenarioReport {
    let mut warmup_durations_ms = Vec::with_capacity(manifest.warmup_iterations);
    let mut measured_durations_ms = Vec::with_capacity(manifest.measured_iterations);

    for _ in 0..manifest.warmup_iterations {
        match measure_hierarchy_scenario(target, checkout_path, scenario) {
            Ok(duration) => warmup_durations_ms.push(duration),
            Err(err) => {
                return ScenarioReport::from_timings(
                    scenario,
                    ScenarioTransport::Direct,
                    false,
                    warmup_durations_ms,
                    measured_durations_ms,
                    Some(err),
                );
            }
        }
    }

    for _ in 0..manifest.measured_iterations {
        match measure_hierarchy_scenario(target, checkout_path, scenario) {
            Ok(duration) => measured_durations_ms.push(duration),
            Err(err) => {
                return ScenarioReport::from_timings(
                    scenario,
                    ScenarioTransport::Direct,
                    false,
                    warmup_durations_ms,
                    measured_durations_ms,
                    Some(err),
                );
            }
        }
    }

    ScenarioReport::from_timings(
        scenario,
        ScenarioTransport::Direct,
        true,
        warmup_durations_ms,
        measured_durations_ms,
        None,
    )
}

fn measure_hierarchy_scenario(
    target: &BenchmarkRepoTarget,
    checkout_path: &Path,
    scenario: BenchmarkScenario,
) -> Result<f64, String> {
    let selected_languages = target
        .language_set()
        .into_iter()
        .map(|language| language.analyzer_language())
        .collect::<BTreeSet<_>>();
    let project: Arc<dyn Project> =
        Arc::new(FilesystemProject::new(checkout_path).map_err(|err| {
            format!(
                "failed to open workspace `{}`: {err}",
                checkout_path.display()
            )
        })?);

    let start = Instant::now();
    let workspace = if selected_languages.is_empty() {
        WorkspaceAnalyzer::build(Arc::clone(&project), AnalyzerConfig::default())
    } else {
        WorkspaceAnalyzer::build_for_languages(
            Arc::clone(&project),
            AnalyzerConfig::default(),
            &selected_languages,
        )
    };

    match scenario {
        BenchmarkScenario::CallHierarchy => {
            for query in &target.call_hierarchy_queries {
                run_call_hierarchy_query(&workspace, project.as_ref(), checkout_path, query)?;
            }
        }
        BenchmarkScenario::TypeHierarchy => {
            for query in &target.type_hierarchy_queries {
                run_type_hierarchy_query(&workspace, project.as_ref(), checkout_path, query)?;
            }
        }
        _ => {
            return Err(format!(
                "scenario `{}` is not a hierarchy scenario",
                scenario.label()
            ));
        }
    }

    Ok(elapsed_ms(start))
}

fn run_call_hierarchy_query(
    workspace: &WorkspaceAnalyzer,
    project: &dyn Project,
    checkout_path: &Path,
    query: &HierarchyQueryTarget,
) -> Result<(), String> {
    let params = call_hierarchy_prepare_params(checkout_path, &query.selector)?;
    let items = call_hierarchy::prepare(workspace, project, &params)
        .filter(|items| !items.is_empty())
        .ok_or_else(|| {
            format!(
                "call_hierarchy prepare returned no item for `{}`",
                query.selector.path
            )
        })?;
    let item = items
        .into_iter()
        .next()
        .expect("non-empty call hierarchy item list");

    let incoming = call_hierarchy::incoming_calls(
        workspace,
        project,
        &CallHierarchyIncomingCallsParams {
            item: item.clone(),
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
        },
    )
    .ok_or_else(|| {
        format!(
            "call_hierarchy incomingCalls failed for `{}`",
            query.selector.path
        )
    })?;
    if incoming.len() < query.min_incoming {
        return Err(format!(
            "call_hierarchy incomingCalls for `{}` returned {} result(s), expected at least {}",
            query.selector.path,
            incoming.len(),
            query.min_incoming
        ));
    }

    let outgoing = call_hierarchy::outgoing_calls(
        workspace,
        project,
        &CallHierarchyOutgoingCallsParams {
            item,
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
        },
    )
    .ok_or_else(|| {
        format!(
            "call_hierarchy outgoingCalls failed for `{}`",
            query.selector.path
        )
    })?;
    if outgoing.len() < query.min_outgoing {
        return Err(format!(
            "call_hierarchy outgoingCalls for `{}` returned {} result(s), expected at least {}",
            query.selector.path,
            outgoing.len(),
            query.min_outgoing
        ));
    }

    Ok(())
}

fn run_type_hierarchy_query(
    workspace: &WorkspaceAnalyzer,
    project: &dyn Project,
    checkout_path: &Path,
    query: &HierarchyQueryTarget,
) -> Result<(), String> {
    let params = type_hierarchy_prepare_params(checkout_path, &query.selector)?;
    let items = type_hierarchy::prepare(workspace, project, &params)
        .filter(|items| !items.is_empty())
        .ok_or_else(|| {
            format!(
                "type_hierarchy prepare returned no item for `{}`",
                query.selector.path
            )
        })?;
    let item = items
        .into_iter()
        .next()
        .expect("non-empty type hierarchy item list");

    let supertypes = type_hierarchy::supertypes(
        workspace,
        project,
        &TypeHierarchySupertypesParams {
            item: item.clone(),
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
        },
    )
    .ok_or_else(|| {
        format!(
            "type_hierarchy supertypes failed for `{}`",
            query.selector.path
        )
    })?;
    if supertypes.len() < query.min_supertypes {
        return Err(format!(
            "type_hierarchy supertypes for `{}` returned {} result(s), expected at least {}",
            query.selector.path,
            supertypes.len(),
            query.min_supertypes
        ));
    }

    let subtypes = type_hierarchy::subtypes(
        workspace,
        project,
        &TypeHierarchySubtypesParams {
            item,
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
        },
    )
    .ok_or_else(|| {
        format!(
            "type_hierarchy subtypes failed for `{}`",
            query.selector.path
        )
    })?;
    if subtypes.len() < query.min_subtypes {
        return Err(format!(
            "type_hierarchy subtypes for `{}` returned {} result(s), expected at least {}",
            query.selector.path,
            subtypes.len(),
            query.min_subtypes
        ));
    }

    Ok(())
}

fn call_hierarchy_prepare_params(
    checkout_path: &Path,
    selector: &BenchmarkLocationSelector,
) -> Result<CallHierarchyPrepareParams, String> {
    Ok(CallHierarchyPrepareParams {
        text_document_position_params: text_document_position_params(checkout_path, selector)?,
        work_done_progress_params: WorkDoneProgressParams::default(),
    })
}

fn type_hierarchy_prepare_params(
    checkout_path: &Path,
    selector: &BenchmarkLocationSelector,
) -> Result<TypeHierarchyPrepareParams, String> {
    Ok(TypeHierarchyPrepareParams {
        text_document_position_params: text_document_position_params(checkout_path, selector)?,
        work_done_progress_params: WorkDoneProgressParams::default(),
    })
}

fn text_document_position_params(
    checkout_path: &Path,
    selector: &BenchmarkLocationSelector,
) -> Result<TextDocumentPositionParams, String> {
    let line = selector
        .line
        .ok_or_else(|| format!("hierarchy selector `{}` is missing line", selector.path))?;
    let column = selector
        .column
        .ok_or_else(|| format!("hierarchy selector `{}` is missing column", selector.path))?;
    Ok(TextDocumentPositionParams {
        text_document: TextDocumentIdentifier {
            uri: file_uri(checkout_path, selector)?,
        },
        position: Position {
            line: (line - 1) as u32,
            character: (column - 1) as u32,
        },
    })
}

fn file_uri(checkout_path: &Path, selector: &BenchmarkLocationSelector) -> Result<Uri, String> {
    let path = checkout_path.join(&selector.path);
    path_to_uri_string(&path)
        .parse()
        .map_err(|err| format!("failed to convert `{}` to URI: {err}", path.display()))
}

fn run_workspace_build(
    target: &BenchmarkRepoTarget,
    manifest: &BenchmarkManifest,
    checkout_path: &Path,
) -> ScenarioReport {
    let mut warmup_durations_ms = Vec::with_capacity(manifest.warmup_iterations);
    let mut measured_durations_ms = Vec::with_capacity(manifest.measured_iterations);
    let selected_languages = target
        .language_set()
        .into_iter()
        .map(|language| language.analyzer_language())
        .collect::<BTreeSet<_>>();

    for _ in 0..manifest.warmup_iterations {
        match measure_workspace_build(checkout_path, &selected_languages) {
            Ok(duration) => warmup_durations_ms.push(duration),
            Err(err) => {
                return ScenarioReport::from_timings(
                    BenchmarkScenario::WorkspaceBuild,
                    ScenarioTransport::Direct,
                    false,
                    warmup_durations_ms,
                    measured_durations_ms,
                    Some(err),
                );
            }
        }
    }

    for _ in 0..manifest.measured_iterations {
        match measure_workspace_build(checkout_path, &selected_languages) {
            Ok(duration) => measured_durations_ms.push(duration),
            Err(err) => {
                return ScenarioReport::from_timings(
                    BenchmarkScenario::WorkspaceBuild,
                    ScenarioTransport::Direct,
                    false,
                    warmup_durations_ms,
                    measured_durations_ms,
                    Some(err),
                );
            }
        }
    }

    ScenarioReport::from_timings(
        BenchmarkScenario::WorkspaceBuild,
        ScenarioTransport::Direct,
        true,
        warmup_durations_ms,
        measured_durations_ms,
        None,
    )
}

fn measure_workspace_build(
    checkout_path: &Path,
    selected_languages: &BTreeSet<crate::Language>,
) -> Result<f64, String> {
    let project = Arc::new(FilesystemProject::new(checkout_path).map_err(|err| {
        format!(
            "failed to open workspace `{}`: {err}",
            checkout_path.display()
        )
    })?);
    let start = Instant::now();
    if selected_languages.is_empty() {
        let _workspace = WorkspaceAnalyzer::build(project, AnalyzerConfig::default());
    } else {
        let _workspace = WorkspaceAnalyzer::build_for_languages(
            project,
            AnalyzerConfig::default(),
            selected_languages,
        );
    }
    Ok(elapsed_ms(start))
}

fn run_mcp_scenario(
    target: &BenchmarkRepoTarget,
    manifest: &BenchmarkManifest,
    session: &mut McpSession,
    scenario: BenchmarkScenario,
) -> ScenarioReport {
    let mut warmup_durations_ms = Vec::with_capacity(manifest.warmup_iterations);
    let mut measured_durations_ms = Vec::with_capacity(manifest.measured_iterations);

    for _ in 0..manifest.warmup_iterations {
        let start = Instant::now();
        let outcome = session
            .call_tool(scenario.tool_name(), tool_arguments(target, scenario))
            .and_then(|result| assert_scenario_result(target, scenario, &result));
        match outcome {
            Ok(()) => warmup_durations_ms.push(elapsed_ms(start)),
            Err(err) => {
                return ScenarioReport::from_timings(
                    scenario,
                    ScenarioTransport::Mcp,
                    false,
                    warmup_durations_ms,
                    measured_durations_ms,
                    Some(err),
                );
            }
        }
    }

    for _ in 0..manifest.measured_iterations {
        let start = Instant::now();
        let outcome = session
            .call_tool(scenario.tool_name(), tool_arguments(target, scenario))
            .and_then(|result| assert_scenario_result(target, scenario, &result));
        match outcome {
            Ok(()) => measured_durations_ms.push(elapsed_ms(start)),
            Err(err) => {
                return ScenarioReport::from_timings(
                    scenario,
                    ScenarioTransport::Mcp,
                    false,
                    warmup_durations_ms,
                    measured_durations_ms,
                    Some(err),
                );
            }
        }
    }

    ScenarioReport::from_timings(
        scenario,
        ScenarioTransport::Mcp,
        true,
        warmup_durations_ms,
        measured_durations_ms,
        None,
    )
}

fn tool_arguments(target: &BenchmarkRepoTarget, scenario: BenchmarkScenario) -> Value {
    match scenario {
        BenchmarkScenario::WorkspaceBuild => json!({}),
        BenchmarkScenario::SearchSymbols => json!({
            "patterns": target.search_patterns,
            "include_tests": true
        }),
        BenchmarkScenario::GetSymbolLocations => json!({
            "symbols": target.location_symbols
        }),
        BenchmarkScenario::GetSymbolAncestors => json!({
            "symbols": target.ancestor_symbols
        }),
        BenchmarkScenario::GetSummaries => json!({
            "targets": target.summary_targets
        }),
        BenchmarkScenario::MostRelevantFiles => json!({
            "seed_file_paths": target.seed_file_paths,
            "limit": 20
        }),
        BenchmarkScenario::ScanUsages => {
            let mut args = json!({
                "include_tests": true
            });
            if !target.usage_symbols.is_empty() {
                args["symbols"] = json!(target.usage_symbols);
            }
            if !target.usage_targets.is_empty() {
                args["targets"] = json!(
                    target
                        .usage_targets
                        .iter()
                        .map(location_selector_arguments)
                        .collect::<Vec<_>>()
                );
            }
            args
        }
        BenchmarkScenario::GetDefinition => json!({
            "references": target.definition_queries.iter().map(|query| {
                location_selector_arguments(&query.selector)
            }).collect::<Vec<_>>(),
        }),
        BenchmarkScenario::CallHierarchy | BenchmarkScenario::TypeHierarchy => json!({}),
    }
}

fn location_selector_arguments(selector: &BenchmarkLocationSelector) -> Value {
    json!({
        "path": selector.path,
        "line": selector.line,
        "column": selector.column,
        "start_byte": selector.start_byte,
        "end_byte": selector.end_byte
    })
}

fn assert_scenario_result(
    target: &BenchmarkRepoTarget,
    scenario: BenchmarkScenario,
    result: &Value,
) -> Result<(), String> {
    let structured = result
        .get("structuredContent")
        .ok_or_else(|| format!("tool `{}` returned no structuredContent", scenario.label()))?;
    match scenario {
        BenchmarkScenario::WorkspaceBuild => Ok(()),
        BenchmarkScenario::SearchSymbols => {
            let files = structured["files"].as_array().ok_or_else(|| {
                format!(
                    "search_symbols result missing files array for `{}`",
                    target.name
                )
            })?;
            if files.is_empty() {
                return Err(format!(
                    "search_symbols returned no files for `{}`",
                    target.name
                ));
            }
            Ok(())
        }
        BenchmarkScenario::GetSymbolLocations => {
            let locations = structured["locations"].as_array().ok_or_else(|| {
                format!(
                    "get_symbol_locations result missing locations array for `{}`",
                    target.name
                )
            })?;
            if locations.is_empty() {
                return Err(format!(
                    "get_symbol_locations returned no locations for `{}`",
                    target.name
                ));
            }
            Ok(())
        }
        BenchmarkScenario::GetSymbolAncestors => {
            let ancestors = structured["ancestors"].as_array().ok_or_else(|| {
                format!(
                    "get_symbol_ancestors result missing ancestors array for `{}`",
                    target.name
                )
            })?;
            if ancestors.is_empty() {
                return Err(format!(
                    "get_symbol_ancestors returned no results for `{}`",
                    target.name
                ));
            }
            let has_ancestor = ancestors.iter().any(|entry| {
                entry["ancestors"]
                    .as_array()
                    .is_some_and(|items| !items.is_empty())
            });
            if !has_ancestor {
                return Err(format!(
                    "get_symbol_ancestors returned no ancestor entries for `{}`",
                    target.name
                ));
            }
            Ok(())
        }
        BenchmarkScenario::GetSummaries => {
            if structured["not_found"]
                .as_array()
                .is_some_and(|items| !items.is_empty())
            {
                return Err(format!(
                    "get_summaries reported unresolved targets for `{}`",
                    target.name
                ));
            }
            let summaries = structured["summaries"].as_array().ok_or_else(|| {
                format!(
                    "get_summaries result missing summaries array for `{}`",
                    target.name
                )
            })?;
            let compact_files = structured["compact_symbols"]["files"].as_array();
            let has_compact_symbols = structured["degraded"].as_bool() == Some(true)
                && compact_files.is_some_and(|files| !files.is_empty());
            if summaries.is_empty() && !has_compact_symbols {
                return Err(format!(
                    "get_summaries returned no summaries or compact symbols for `{}`",
                    target.name
                ));
            }
            Ok(())
        }
        BenchmarkScenario::MostRelevantFiles => {
            let files = structured["files"].as_array().ok_or_else(|| {
                format!(
                    "most_relevant_files result missing files array for `{}`",
                    target.name
                )
            })?;
            if files.is_empty() {
                return Err(format!(
                    "most_relevant_files returned no related files for `{}`",
                    target.name
                ));
            }
            let seed_paths = target
                .seed_file_paths
                .iter()
                .map(|seed| seed.trim())
                .collect::<BTreeSet<_>>();
            let has_non_seed = files
                .iter()
                .filter_map(Value::as_str)
                .any(|path| !seed_paths.contains(path));
            if !has_non_seed {
                return Err(format!(
                    "most_relevant_files only returned seed files for `{}`",
                    target.name
                ));
            }
            Ok(())
        }
        BenchmarkScenario::ScanUsages => {
            let usages = structured["usages"].as_array().ok_or_else(|| {
                if structured["too_many_callsites"]
                    .as_array()
                    .is_some_and(|items| !items.is_empty())
                {
                    return format!(
                        "scan_usages returned too_many_callsites for `{}`",
                        target.name
                    );
                }
                format!(
                    "scan_usages result missing usages array for `{}`",
                    target.name
                )
            })?;
            if usages.is_empty() {
                return Err(format!(
                    "scan_usages returned no usages for `{}`",
                    target.name
                ));
            }
            let has_hits = usages.iter().any(|usage| {
                usage["total_hits"].as_u64().unwrap_or(0) > 0
                    || usage["files"]
                        .as_array()
                        .is_some_and(|files| !files.is_empty())
            });
            if !has_hits {
                return Err(format!(
                    "scan_usages found no call sites for `{}`",
                    target.name
                ));
            }
            Ok(())
        }
        BenchmarkScenario::GetDefinition => {
            let results = structured["results"].as_array().ok_or_else(|| {
                format!(
                    "get_definition result missing results array for `{}`",
                    target.name
                )
            })?;
            if results.len() != target.definition_queries.len() {
                return Err(format!(
                    "get_definition returned {} result(s) for {} query/queries in `{}`",
                    results.len(),
                    target.definition_queries.len(),
                    target.name
                ));
            }

            for (index, (query, result)) in target
                .definition_queries
                .iter()
                .zip(results.iter())
                .enumerate()
            {
                let actual_status = result["status"].as_str().ok_or_else(|| {
                    format!(
                        "get_definition result {index} missing status for `{}`",
                        target.name
                    )
                })?;
                if actual_status != query.expected_status {
                    return Err(format!(
                        "get_definition result {index} for `{}` expected status `{}` but got `{actual_status}`",
                        target.name, query.expected_status
                    ));
                }

                if let Some(expected_fqn) = query.expected_fqn.as_deref() {
                    let definition = result["definitions"]
                        .as_array()
                        .and_then(|definitions| definitions.first())
                        .and_then(|definition| definition.as_object())
                        .ok_or_else(|| {
                            format!(
                                "get_definition result {index} missing definitions object for `{}`",
                                target.name
                            )
                        })?;
                    let actual_fqn = definition.get("fqn").and_then(|value| value.as_str());
                    if actual_fqn != Some(expected_fqn) {
                        return Err(format!(
                            "get_definition result {index} for `{}` expected fqn `{expected_fqn}` but got `{}`",
                            target.name,
                            actual_fqn.unwrap_or("<missing>")
                        ));
                    }
                }
            }
            Ok(())
        }
        BenchmarkScenario::CallHierarchy | BenchmarkScenario::TypeHierarchy => Ok(()),
    }
}

fn elapsed_ms(start: Instant) -> f64 {
    start.elapsed().as_secs_f64() * 1000.0
}

fn current_bifrost_commit() -> Option<String> {
    let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let output = std::process::Command::new("git")
        .arg("rev-parse")
        .arg("HEAD")
        .current_dir(repo_root)
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    let sha = String::from_utf8(output.stdout).ok()?;
    let trimmed = sha.trim();
    (!trimmed.is_empty()).then(|| trimmed.to_string())
}
