//! Handler for `mcptest doctor`.

use anyhow::Result;

use crate::cli::args::DoctorArgs;
use crate::cli::exit_codes::{EXIT_FAILURE, EXIT_OK};
use crate::cli::handlers::doctor_auth;
use crate::cli::GlobalArgs;
use crate::commands;
use crate::config;

/// Print the doctor report based on the resolver state, then probe the
/// server when `--url` is given.
///
/// Lists which dotenv files were discovered, then a per-source variable
/// count, then a local (no-network) auth pre-flight for any
/// transport-level auth scheme the config declares. With `--url` the live
/// tool-catalog token check runs inside [`commands::run_doctor_url`] after
/// `initialize`; without a target it reports that the check was skipped (and
/// `--no-tool-tokens` disables it entirely).
pub(crate) fn doctor_command(global: &GlobalArgs, args: &DoctorArgs) -> Result<i32> {
    let cwd = std::env::current_dir()?;
    if !global.quiet {
        println!("mcptest doctor");
    }
    config::print_doctor_report(global, &cwd)?;
    if !global.quiet {
        println!("proxy: {}", global.proxy_config().summary());
    }
    // Local auth pre-flight: validates configured mTLS / SigV4 / Web Bot
    // Auth from files and environment only. A failed check makes doctor
    // exit non-zero so a misconfigured cert or missing credential is caught
    // before the operator runs against the server. `--probe` opts into live
    // network probes per scheme; without it the pre-flight is
    // entirely offline.
    let auth_ok = doctor_auth::run_auth_preflight(global, &cwd, args.probe);
    // Local test-readiness inventory: every MCP server declared across the
    // supported clients, identity / transport / presence-of-auth only, secrets
    // redacted. Read-only and offline.
    if !global.quiet {
        print!("{}", commands::render_discovery_inventory());
    }
    // Tool-catalog token check messaging for the disabled / no-target cases.
    // When `--url` is supplied and the check is enabled, the live `tools/list`
    // run happens inside `run_doctor_url` below and renders its own block.
    if args.no_tool_tokens {
        if !global.quiet {
            println!("tool catalog token check disabled (--no-tool-tokens)");
        }
    } else if args.url.is_none() && !global.quiet {
        println!(
            "tool catalog token check skipped: no --url target (pass --url to run the live \
             tools/list check); tokenizer={}",
            args.tokenizer
        );
    }
    // `--wait-for-ready` on doctor polls the `--url` target until it accepts
    // connections before the network probe runs, so a server still starting up
    // does not read as a hard failure. With no `--url` there is nothing to wait
    // for. A timeout or non-retryable error aborts with the readiness exit code.
    if let Some(budget) = global.wait_for_ready {
        match &args.url {
            Some(url) => {
                if let Some(exit) =
                    crate::cli::handlers::readiness::await_url_ready(url, budget, global.quiet)
                {
                    return Ok(exit);
                }
            }
            None if !global.quiet => {
                println!("wait-for-ready: no --url to probe; skipping");
            }
            None => {}
        }
    }
    if let Some(base_url) = &args.url {
        let auth = commands::DoctorAuth {
            bearer_token: args
                .bearer_token_env
                .as_ref()
                .and_then(|name| std::env::var(name).ok()),
            bearer_token_env: args.bearer_token_env.clone(),
        };
        // Enable the live tool-catalog token check unless the operator opted
        // out; the tokenizer name comes from `--tokenizer` (default cl100k_base).
        let tokenizer = if args.no_tool_tokens {
            None
        } else {
            Some(args.tokenizer.as_str())
        };
        let url_exit = commands::run_doctor_url(base_url, auth.clone(), global.quiet, tokenizer)?;
        // The migration doctor is a separate one-shot probe that
        // runs after the regular URL pipeline, so a network failure surfaces
        // there first and we never run the migration check against an
        // unreachable server. The migration verdict folds into the exit code
        // the same way the regular probe's does.
        let migration_exit = if let Some(target_version) = &args.target_version {
            commands::run_migration_check(
                base_url,
                auth,
                target_version,
                commands::MigrationCheckFormat::Pretty,
            )?
        } else {
            0
        };
        // A failed local pre-flight is reported non-zero even when the URL
        // probe itself passed, so neither failure mode hides the other.
        return Ok(
            if url_exit != EXIT_OK || migration_exit != EXIT_OK || !auth_ok {
                EXIT_FAILURE
            } else {
                EXIT_OK
            },
        );
    }
    if args.target_version.is_some() && !global.quiet {
        println!("target-version: skipped (requires `--url`)");
    }
    Ok(if auth_ok { EXIT_OK } else { EXIT_FAILURE })
}
