use anyhow::Context;
use cephas::browser::{Browser, BrowserConfig};
use cephas::storage::ProfileStore;
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::collections::BTreeMap;
use std::fs;
use std::io::{self, BufRead, Read, Write};
use std::net::TcpListener;
use std::path::{Path, PathBuf};
use std::process::{Command as ProcessCommand, Stdio};
use std::thread;
use std::time::{Duration, Instant};
use uuid::Uuid;

const AGENT_SCREENSHOT_SCHEMA: &str = "cephas.agent-screenshot.v1";
const AGENT_SMOKE_SCHEMA: &str = "cephas.agent-smoke.v1";
const INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA: &str = "cephas.internal-route-trust-smoke.v1";
const LAUNCH_SAFETY_SMOKE_SCHEMA: &str = "cephas.launch-safety-smoke.v1";
const AGENT_TOOLING_SCHEMA: &str = "cephas.agent-tooling.v1";
const DIAGNOSTICS_SCHEMA: &str = "cephas.diagnostics.v1";
const SELF_TEST_SCHEMA: &str = "cephas.self-test.v1";
const MCP_PROTOCOL_VERSION: &str = "2024-11-05";
const MCP_MAX_LINE_BYTES: usize = 1024 * 1024;
const MCP_MAX_BATCH_REQUESTS: usize = 32;
const INTERNAL_ROUTE_TRUST_ATTEMPT: &str = "cephas://settings/theme?id=glass";
const UNKNOWN_INTERNAL_LAUNCH_TARGET: &str = "cephas://unknown/internal-route";
const UNSAFE_HOME_LAUNCH_VALUE: &str = "cephas://agent";
const INTERNAL_ROUTE_TRUST_TIMEOUT: Duration = Duration::from_secs(6);
const AGENT_SCREENSHOT_SMOKE_TIMEOUT: Duration = Duration::from_secs(45);

#[derive(Debug, Parser)]
#[command(name = "cephas", version, about = "A privacy-first Rust browser shell")]
struct Cli {
    #[arg(long, global = true, help = "Use a custom profile JSON path")]
    profile: Option<PathBuf>,

    #[command(subcommand)]
    command: Option<Command>,
}

#[derive(Debug, Subcommand)]
enum Command {
    #[command(about = "Launch the desktop browser window")]
    Launch {
        #[arg(help = "URL or search terms to open first")]
        target: Option<String>,
    },
    #[command(about = "Open an interactive browser session")]
    Browse {
        #[arg(help = "URL or search terms to open first")]
        target: Option<String>,
    },
    #[command(about = "Open one page, print reader-mode text, then exit")]
    Open {
        #[arg(help = "URL or search terms")]
        target: String,
    },
    #[command(about = "Open one page, print an AI-ready reader packet, then exit")]
    AiReader {
        #[arg(help = "URL or search terms")]
        target: String,
    },
    #[command(about = "Open one page and print an agent-reader extraction artifact")]
    AgentReader {
        #[arg(
            help = "Tool: agent-brief, full-text, outline, link-map, data-signals, json-artifact"
        )]
        tool: String,
        #[arg(help = "URL or search terms")]
        target: String,
    },
    #[command(about = "Print the agent browser-command manifest")]
    AgentCommands {
        #[arg(long, help = "Print one JSON command per line")]
        jsonl: bool,
    },
    #[command(about = "Print a portable manifest for agentic code tools")]
    AgentTooling,
    #[command(about = "Run a basic stdio MCP server for code tools")]
    Mcp,
    #[command(about = "Print the UUIDv7 plugin registry")]
    Plugins {
        #[arg(long, help = "Print one plugin manifest per line")]
        jsonl: bool,
        #[command(subcommand)]
        action: Option<PluginCommand>,
    },
    #[command(about = "Render a URL or internal page and write an agent screenshot PNG")]
    AgentScreenshot {
        #[arg(help = "URL, internal page, or search terms to capture")]
        target: String,
        #[arg(short, long, default_value = "cephas-agent-screenshot.png")]
        output: PathBuf,
        #[arg(long, default_value_t = 1280)]
        width: i32,
        #[arg(long, default_value_t = 900)]
        height: i32,
        #[arg(long, help = "Print a machine-readable JSON result")]
        json: bool,
    },
    #[command(
        about = "Run visual smoke screenshots for internal Agent surfaces",
        visible_alias = "visual-smoke"
    )]
    AgentSmoke {
        #[arg(short, long, default_value = "cephas-smoke")]
        output_dir: PathBuf,
        #[arg(long, default_value_t = 1280)]
        width: i32,
        #[arg(long, default_value_t = 900)]
        height: i32,
        #[arg(long, value_name = "CASE", help = "Run only a named smoke case")]
        case: Vec<String>,
        #[arg(long, default_value_t = 1024, help = "Minimum expected PNG size")]
        min_bytes: u64,
        #[arg(long, help = "Print a machine-readable JSON report")]
        json: bool,
    },
    #[command(about = "Print profile location and basic stats")]
    Profile,
    #[command(about = "Print bounded resource diagnostics")]
    Diagnostics {
        #[arg(long, help = "Print a machine-readable JSON report")]
        json: bool,
        #[arg(long, help = "Exit non-zero when diagnostics are not ok")]
        strict: bool,
    },
    #[command(about = "Run fast built-in policy and resource self checks")]
    SelfTest {
        #[arg(long, help = "Print a machine-readable JSON report")]
        json: bool,
    },
}

#[derive(Debug, Subcommand)]
enum PluginCommand {
    #[command(about = "Print bundled project plugins available for local install")]
    Available {
        #[arg(long, help = "Print one bundled plugin module per line")]
        jsonl: bool,
    },
    #[command(about = "Validate profile plugin registry and bundled module metadata")]
    Check {
        #[arg(long, help = "Fail when warnings are present, not only errors")]
        strict: bool,
        #[arg(long, help = "Alias for --strict")]
        fail_on_warning: bool,
    },
    #[command(about = "Install a bundled project plugin into this profile")]
    Install {
        #[arg(help = "UUID plugin ID or bundled module ID")]
        selector: String,
    },
    #[command(about = "Install every bundled project plugin not yet installed")]
    InstallAll,
    #[command(about = "Refresh one installed bundled plugin from its project manifest")]
    Sync {
        #[arg(help = "UUID plugin ID or bundled module ID")]
        selector: String,
    },
    #[command(about = "Refresh every installed bundled plugin from project manifests")]
    SyncAll,
    #[command(about = "Enable an installed profile plugin")]
    Enable {
        #[arg(help = "UUID plugin ID or bundled module ID")]
        selector: String,
    },
    #[command(about = "Disable an installed profile plugin")]
    Disable {
        #[arg(help = "UUID plugin ID or bundled module ID")]
        selector: String,
    },
    #[command(about = "Remove an installed profile plugin")]
    Remove {
        #[arg(help = "UUID plugin ID or bundled module ID")]
        selector: String,
    },
}

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    let profile_path = cli.profile.clone();
    let store = ProfileStore::new(cli.profile)?;
    let profile = store.load()?;

    match cli.command.unwrap_or(Command::Launch { target: None }) {
        Command::Launch { target } => {
            let fallback_profile = profile.clone();
            if let Err(err) = cephas::gui::launch(profile, store, target.clone()) {
                eprintln!("native window failed: {err:#}");
                if std::env::var_os("CEPHAS_DISABLE_EXTERNAL_FALLBACK").is_none()
                    && can_launch_external_fallback(&fallback_profile, target.as_deref())
                {
                    eprintln!("opening the system browser instead");
                    cephas::gui::launch_external(&fallback_profile, target.as_deref())
                } else {
                    Err(err).context("not opening internal or non-web target externally")
                }
            } else {
                Ok(())
            }
        }
        Command::Browse { target } => {
            let browser = Browser::new(profile, BrowserConfig::default())?;
            cephas::ui::run_interactive(browser, store, target)
        }
        Command::Open { target } => {
            let browser = Browser::new(profile, BrowserConfig::default())?;
            open_once(browser, store, target)
        }
        Command::AiReader { target } => {
            let browser = Browser::new(profile, BrowserConfig::default())?;
            ai_reader_once(browser, store, target)
        }
        Command::AgentReader { tool, target } => {
            let browser = Browser::new(profile, BrowserConfig::default())?;
            agent_reader_once(browser, store, tool, target)
        }
        Command::AgentCommands { jsonl } => {
            let manifest = cephas::agents::agent_command_manifest();
            if jsonl {
                for entry in manifest {
                    println!("{}", serde_json::to_string(&entry)?);
                }
            } else {
                println!("{}", serde_json::to_string_pretty(&manifest)?);
            }
            Ok(())
        }
        Command::AgentTooling => {
            println!(
                "{}",
                serde_json::to_string_pretty(&agent_tooling_manifest())?
            );
            Ok(())
        }
        Command::Mcp => run_mcp_server(&profile, &store),
        Command::Plugins { jsonl, action } => plugin_command(profile, store, jsonl, action),
        Command::AgentScreenshot {
            target,
            output,
            width,
            height,
            json,
        } => agent_screenshot(&profile, target, output, width, height, json),
        Command::AgentSmoke {
            output_dir,
            width,
            height,
            case,
            min_bytes,
            json,
        } => agent_smoke(
            output_dir,
            width,
            height,
            case,
            min_bytes,
            json,
            profile_path,
        ),
        Command::Profile => {
            println!("profile: {}", store.path().display());
            println!("history entries: {}", profile.history.len());
            println!("bookmarks: {}", profile.bookmarks.len());
            println!(
                "plugins: {} / {}",
                profile.plugins.installed_count(),
                profile.plugins.max_plugins
            );
            Ok(())
        }
        Command::Diagnostics { json, strict } => {
            print_diagnostics_report(&profile, &store, json, strict)
        }
        Command::SelfTest { json } => print_self_test(json),
    }
}

fn can_launch_external_fallback(profile: &cephas::Profile, target: Option<&str>) -> bool {
    let candidate = target
        .map(str::trim)
        .filter(|target| !target.is_empty())
        .unwrap_or_else(|| profile.home.as_str());
    let Ok(url) = url::Url::parse(candidate) else {
        return true;
    };
    matches!(url.scheme(), "http" | "https")
        && !(url.scheme() == "https" && url.host_str() == Some("cephas.local"))
}

fn print_self_test(json: bool) -> anyhow::Result<()> {
    let report = self_test_report();
    if json {
        println!("{}", serde_json::to_string_pretty(&report)?);
    } else {
        for check in &report.checks {
            let status = if check.ok { "ok" } else { "FAILED" };
            println!("{status} {} - {}", check.id, check.detail);
        }
        if report.ok {
            println!("self-test ok");
        }
    }

    if report.ok {
        Ok(())
    } else {
        anyhow::bail!("self-test failed")
    }
}

fn self_test_report() -> SelfTestReport {
    let checks = vec![
        self_test_launch_route_policy(),
        self_test_profile_resource_clamps(),
        self_test_profile_home_migration(),
        self_test_plugin_resource_clamps(),
    ];
    SelfTestReport {
        schema: SELF_TEST_SCHEMA,
        ok: checks.iter().all(|check| check.ok),
        checks,
    }
}

fn self_test_check(id: &'static str, ok: bool, detail: impl Into<String>) -> SelfTestCheck {
    SelfTestCheck {
        id,
        ok,
        detail: detail.into(),
    }
}

fn self_test_launch_route_policy() -> SelfTestCheck {
    let profile = cephas::Profile::default();
    let ok = can_launch_external_fallback(&profile, Some("https://example.com"))
        && can_launch_external_fallback(&profile, Some("search terms"))
        && !can_launch_external_fallback(&profile, Some("cephas://agent"))
        && !can_launch_external_fallback(&profile, Some("https://cephas.local/agent"))
        && !can_launch_external_fallback(&profile, Some("file:///tmp/cephas"));
    self_test_check(
        "launch-route-policy",
        ok,
        "web/search targets can fall back externally; internal and file targets cannot",
    )
}

fn self_test_profile_resource_clamps() -> SelfTestCheck {
    let mut high = cephas::Profile {
        max_history: cephas::storage::DEFAULT_MAX_HISTORY + 10_000,
        max_bookmarks: cephas::storage::DEFAULT_MAX_BOOKMARKS + 10_000,
        ..cephas::Profile::default()
    };
    high.enforce_limits();

    let mut low = cephas::Profile {
        max_history: 0,
        max_bookmarks: 0,
        ..cephas::Profile::default()
    };
    low.enforce_limits();

    let ok = high.max_history == cephas::storage::DEFAULT_MAX_HISTORY
        && high.max_bookmarks == cephas::storage::DEFAULT_MAX_BOOKMARKS
        && low.max_history == 1
        && low.max_bookmarks == 1;
    self_test_check(
        "profile-resource-clamps",
        ok,
        format!(
            "history/bookmark maxima clamp to {}/{} and minimum 1",
            cephas::storage::DEFAULT_MAX_HISTORY,
            cephas::storage::DEFAULT_MAX_BOOKMARKS
        ),
    )
}

fn self_test_profile_home_migration() -> SelfTestCheck {
    let mut profile = cephas::Profile {
        home: url::Url::parse("cephas://agent").expect("valid test URL"),
        ..cephas::Profile::default()
    };
    profile.enforce_limits();
    let expected = cephas::Profile::default().home;
    let ok = profile.home == expected;
    self_test_check(
        "profile-home-migration",
        ok,
        format!("unsafe persisted home migrates to {expected}"),
    )
}

fn self_test_plugin_resource_clamps() -> SelfTestCheck {
    let builtins = cephas::plugins::PluginRegistry::default().installed.len();
    let mut high = cephas::plugins::PluginRegistry {
        max_plugins: cephas::plugins::DEFAULT_MAX_PLUGINS + 10_000,
        ..cephas::plugins::PluginRegistry::default()
    };
    high.enforce_limits();

    let mut low = cephas::plugins::PluginRegistry {
        max_plugins: 0,
        ..cephas::plugins::PluginRegistry::default()
    };
    low.enforce_limits();

    let ok = high.max_plugins == cephas::plugins::DEFAULT_MAX_PLUGINS
        && low.max_plugins == builtins
        && low.installed.len() >= builtins;
    self_test_check(
        "plugin-resource-clamps",
        ok,
        format!(
            "plugin registry max clamps to {} and at least {} built-ins",
            cephas::plugins::DEFAULT_MAX_PLUGINS,
            builtins
        ),
    )
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct AgentSmokeCase {
    name: &'static str,
    target: &'static str,
    output_file: &'static str,
    kind: AgentSmokeCaseKind,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AgentSmokeCaseKind {
    Screenshot,
    InternalRouteTrust,
    LaunchSafety(LaunchSafetyScenario),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LaunchSafetyScenario {
    UnsafeHome,
    UnknownInternalTarget,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PngDimensions {
    width: u32,
    height: u32,
}

#[derive(Debug, Deserialize, Serialize)]
struct AgentScreenshotReport {
    schema: String,
    ok: bool,
    target: String,
    output: String,
    width: i32,
    height: i32,
    bytes: Option<u64>,
    duration_ms: u64,
    error: Option<String>,
}

#[derive(Debug, Serialize)]
struct AgentSmokeReport {
    schema: &'static str,
    ok: bool,
    output_dir: String,
    width: i32,
    height: i32,
    min_bytes: u64,
    duration_ms: u64,
    cases: Vec<AgentSmokeCaseReport>,
}

#[derive(Debug, Serialize)]
struct AgentSmokeCaseReport {
    name: String,
    target: String,
    output: String,
    ok: bool,
    bytes: Option<u64>,
    duration_ms: u64,
    error: Option<String>,
}

#[derive(Debug, Serialize)]
struct InternalRouteTrustSmokeReport {
    schema: &'static str,
    ok: bool,
    source_url: String,
    attempted_route: &'static str,
    profile_path: String,
    before_theme: &'static str,
    after_theme: &'static str,
    server_saw_request: bool,
    child_status: String,
    duration_ms: u64,
    error: Option<String>,
}

#[derive(Debug, Serialize)]
struct LaunchSafetySmokeReport {
    schema: &'static str,
    ok: bool,
    case: &'static str,
    profile_path: String,
    launch_target: Option<&'static str>,
    tampered_home: Option<&'static str>,
    loaded_home: Option<String>,
    child_status: String,
    duration_ms: u64,
    error: Option<String>,
}

#[derive(Debug)]
struct BrowserChildOutcome {
    status: String,
    stderr: String,
}

#[derive(Debug, Serialize)]
struct AgentToolingManifest {
    schema: &'static str,
    package: &'static str,
    version: &'static str,
    binary: &'static str,
    commands: Vec<AgentToolCommand>,
    browser_commands: Vec<cephas::agents::AgentCommandManifestEntry>,
    internal_urls: Vec<AgentToolUrl>,
    smoke_cases: Vec<AgentToolSmokeCase>,
    human_control_routes: Vec<AgentToolRoute>,
    reader_tools: Vec<AgentToolReader>,
    resource_caps: Vec<AgentToolResourceCap>,
    diagnostics_surfaces: Vec<AgentToolDiagnosticsSurface>,
    contracts: Vec<AgentToolContract>,
    schemas: Vec<AgentToolSchema>,
    notes: Vec<&'static str>,
}

#[derive(Debug, Serialize)]
struct AgentToolCommand {
    id: &'static str,
    argv: Vec<&'static str>,
    output: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct AgentToolUrl {
    id: &'static str,
    url: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct AgentToolSmokeCase {
    name: &'static str,
    target: &'static str,
    output_file: &'static str,
}

#[derive(Debug, Serialize)]
struct AgentToolRoute {
    id: &'static str,
    route: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct AgentToolReader {
    id: &'static str,
    label: &'static str,
    description: &'static str,
}

#[derive(Debug, Clone, Copy, Serialize)]
struct AgentToolResourceCap {
    id: &'static str,
    value: u64,
    unit: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct AgentToolDiagnosticsSurface {
    id: &'static str,
    access: Vec<&'static str>,
    output: &'static str,
    scope: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct DiagnosticsReport {
    schema: &'static str,
    ok: bool,
    profile_path: String,
    profile: DiagnosticsProfile,
    session: Option<DiagnosticsSession>,
    downloads: Option<DiagnosticsDownloads>,
    plugins: DiagnosticsPlugins,
    armour: DiagnosticsArmour,
    agent_log: Option<DiagnosticsAgentLog>,
    recordings: Option<DiagnosticsRecordings>,
    resource_caps: Vec<AgentToolResourceCap>,
    resource_checks: BTreeMap<&'static str, DiagnosticsResourceCheck>,
    unobserved_resource_caps: Vec<DiagnosticsUnobservedResourceCap>,
    errors: Vec<DiagnosticsError>,
}

#[derive(Debug, Clone, Copy, Serialize)]
struct DiagnosticsUsage {
    current: u64,
    max: u64,
}

#[derive(Debug, Serialize)]
struct DiagnosticsProfile {
    home: String,
    theme: &'static str,
    ai_browsing_enabled: bool,
    media_policy: &'static str,
    media_capture_enabled: bool,
    web_acceleration: &'static str,
    history_entries: DiagnosticsUsage,
    bookmarks: DiagnosticsUsage,
}

#[derive(Debug, Serialize)]
struct DiagnosticsSession {
    windows: DiagnosticsUsage,
    tabs: DiagnosticsUsage,
    tabs_per_window: DiagnosticsUsage,
}

#[derive(Debug, Serialize)]
struct DiagnosticsDownloads {
    records: DiagnosticsUsage,
    in_progress: u64,
    paused: u64,
    completed: u64,
    cancelled: u64,
    failed: u64,
}

#[derive(Debug, Serialize)]
struct DiagnosticsPlugins {
    installed: DiagnosticsUsage,
    enabled: u64,
    planned: u64,
}

#[derive(Debug, Serialize)]
struct DiagnosticsArmour {
    site_allowlist_hosts: DiagnosticsUsage,
    blocked_hosts: DiagnosticsUsage,
}

#[derive(Debug, Serialize)]
struct DiagnosticsAgentLog {
    bytes: DiagnosticsUsage,
    archives: DiagnosticsUsage,
    total_archive_bytes: DiagnosticsUsage,
    largest_archive_bytes: DiagnosticsUsage,
}

#[derive(Debug, Serialize)]
struct DiagnosticsRecordings {
    directories: DiagnosticsUsage,
    total_bytes: u64,
}

#[derive(Debug, Serialize)]
struct DiagnosticsError {
    component: &'static str,
    error: String,
}

#[derive(Debug, Serialize)]
struct DiagnosticsResourceCheck {
    id: &'static str,
    component: &'static str,
    current: u64,
    max: u64,
    unit: &'static str,
    ok: bool,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct DiagnosticsUnobservedResourceCap {
    id: &'static str,
    component: &'static str,
    max: u64,
    unit: &'static str,
    reason: &'static str,
    surface: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct SelfTestReport {
    schema: &'static str,
    ok: bool,
    checks: Vec<SelfTestCheck>,
}

#[derive(Debug, Serialize)]
struct SelfTestCheck {
    id: &'static str,
    ok: bool,
    detail: String,
}

#[derive(Debug, Serialize)]
struct AgentToolContract {
    id: &'static str,
    path: &'static str,
    description: &'static str,
}

#[derive(Debug, Serialize)]
struct AgentToolSchema {
    id: &'static str,
    schema: &'static str,
}

#[derive(Debug, Serialize)]
struct PluginRegistryReport<'a> {
    schema: &'static str,
    enabled: bool,
    max_plugins: usize,
    installed_count: usize,
    enabled_count: usize,
    planned_count: usize,
    bundled_count: usize,
    available_count: usize,
    installed: &'a [cephas::plugins::PluginManifest],
    bundled: &'a [cephas::plugins::PluginModule],
    available: &'a [cephas::plugins::PluginModule],
}

#[derive(Debug, Serialize)]
struct PluginActionReport {
    schema: &'static str,
    action: &'static str,
    module_id: Option<String>,
    source_path: Option<String>,
    plugin: cephas::plugins::PluginManifest,
    installed_count: usize,
    enabled_count: usize,
}

#[derive(Debug, Serialize)]
struct PluginBulkActionReport {
    schema: &'static str,
    action: &'static str,
    changed_count: usize,
    plugins: Vec<cephas::plugins::PluginManifest>,
    installed_count: usize,
    enabled_count: usize,
    available_count: usize,
}

fn agent_smoke_cases() -> [AgentSmokeCase; 10] {
    [
        AgentSmokeCase {
            name: "agent",
            target: "cephas://agent",
            output_file: "agent.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "agent-docs",
            target: "cephas://agent/docs",
            output_file: "agent-docs.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "agent-document",
            target: "https://cephas.local/agent",
            output_file: "agent-document.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "start",
            target: "cephas://start",
            output_file: "start.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "settings",
            target: "cephas://settings",
            output_file: "settings.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "diagnostics",
            target: "cephas://diagnostics",
            output_file: "diagnostics.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "plugins",
            target: "cephas://plugins",
            output_file: "plugins.png",
            kind: AgentSmokeCaseKind::Screenshot,
        },
        AgentSmokeCase {
            name: "internal-route-trust",
            target: "untrusted local page -> cephas://settings/theme?id=glass",
            output_file: "internal-route-trust.json",
            kind: AgentSmokeCaseKind::InternalRouteTrust,
        },
        AgentSmokeCase {
            name: "launch-unsafe-home",
            target: "isolated profile with unsafe home -> safe launch",
            output_file: "launch-unsafe-home.json",
            kind: AgentSmokeCaseKind::LaunchSafety(LaunchSafetyScenario::UnsafeHome),
        },
        AgentSmokeCase {
            name: "launch-unknown-internal",
            target: "launch cephas://unknown/internal-route -> blocked start page",
            output_file: "launch-unknown-internal.json",
            kind: AgentSmokeCaseKind::LaunchSafety(LaunchSafetyScenario::UnknownInternalTarget),
        },
    ]
}

fn select_agent_smoke_cases(names: &[String]) -> anyhow::Result<Vec<AgentSmokeCase>> {
    let cases = agent_smoke_cases();
    if names.is_empty() {
        return Ok(cases.to_vec());
    }

    let mut selected = Vec::new();
    for name in names {
        let Some(case) = cases.iter().copied().find(|case| case.name == name) else {
            let valid = cases
                .iter()
                .map(|case| case.name)
                .collect::<Vec<_>>()
                .join(", ");
            anyhow::bail!("unknown smoke case '{name}'. Expected one of: {valid}");
        };
        selected.push(case);
    }
    Ok(selected)
}

fn agent_screenshot(
    profile: &cephas::Profile,
    target: String,
    output: PathBuf,
    width: i32,
    height: i32,
    json: bool,
) -> anyhow::Result<()> {
    let started = Instant::now();
    let result = cephas::gui::capture_webpage_screenshot(profile, &target, &output, width, height);
    let bytes = fs::metadata(&output).ok().map(|metadata| metadata.len());
    let duration_ms = elapsed_ms(started);
    match result {
        Ok(()) => {
            if json {
                let report = AgentScreenshotReport {
                    schema: AGENT_SCREENSHOT_SCHEMA.to_string(),
                    ok: true,
                    target,
                    output: output.display().to_string(),
                    width,
                    height,
                    bytes,
                    duration_ms,
                    error: None,
                };
                println!("{}", serde_json::to_string_pretty(&report)?);
            } else {
                println!("screenshot: {}", output.display());
            }
            Ok(())
        }
        Err(err) => {
            let error = format!("{err:#}");
            if json {
                let report = AgentScreenshotReport {
                    schema: AGENT_SCREENSHOT_SCHEMA.to_string(),
                    ok: false,
                    target,
                    output: output.display().to_string(),
                    width,
                    height,
                    bytes,
                    duration_ms,
                    error: Some(error.clone()),
                };
                println!("{}", serde_json::to_string_pretty(&report)?);
            }
            anyhow::bail!(error)
        }
    }
}

fn agent_smoke(
    output_dir: PathBuf,
    width: i32,
    height: i32,
    case_names: Vec<String>,
    min_bytes: u64,
    json: bool,
    profile_path: Option<PathBuf>,
) -> anyhow::Result<()> {
    let started = Instant::now();
    fs::create_dir_all(&output_dir).with_context(|| {
        format!(
            "failed to create visual smoke directory {}",
            output_dir.display()
        )
    })?;
    let cases = select_agent_smoke_cases(&case_names)?;
    let mut reports = Vec::new();

    for case in cases {
        let output = output_dir.join(case.output_file);
        let mut report = run_agent_smoke_case(case, &output, width, height, profile_path.as_ref());
        let error = report.error.take().or_else(|| {
            validate_agent_smoke_artifact(
                case.kind,
                &output,
                report.bytes,
                min_bytes,
                width,
                height,
            )
        });
        let ok = error.is_none();
        if !json {
            if ok {
                println!(
                    "smoke artifact: {} ({} bytes)",
                    report.output,
                    report.bytes.unwrap_or(0)
                );
            } else if let Some(error) = &error {
                println!("smoke failed: {}: {error}", case.name);
            }
        }
        report.ok = ok;
        report.error = error;
        reports.push(report);
    }

    let ok = reports.iter().all(|case| case.ok);
    let report = AgentSmokeReport {
        schema: AGENT_SMOKE_SCHEMA,
        ok,
        output_dir: output_dir.display().to_string(),
        width,
        height,
        min_bytes,
        duration_ms: elapsed_ms(started),
        cases: reports,
    };
    if json {
        println!("{}", serde_json::to_string_pretty(&report)?);
    } else if ok {
        println!("visual smoke ok: {}", output_dir.display());
    }

    if ok {
        Ok(())
    } else {
        let failed = report.cases.iter().filter(|case| !case.ok).count();
        anyhow::bail!("visual smoke failed: {failed} case(s) failed")
    }
}

fn run_agent_smoke_case(
    case: AgentSmokeCase,
    output: &PathBuf,
    width: i32,
    height: i32,
    profile_path: Option<&PathBuf>,
) -> AgentSmokeCaseReport {
    match case.kind {
        AgentSmokeCaseKind::InternalRouteTrust => {
            return run_internal_route_trust_smoke_case(case, output);
        }
        AgentSmokeCaseKind::LaunchSafety(scenario) => {
            return run_launch_safety_smoke_case(case, scenario, output);
        }
        AgentSmokeCaseKind::Screenshot => {}
    }

    let started = Instant::now();
    let mut command = match std::env::current_exe() {
        Ok(path) => ProcessCommand::new(path),
        Err(err) => {
            return AgentSmokeCaseReport {
                name: case.name.to_string(),
                target: case.target.to_string(),
                output: output.display().to_string(),
                ok: false,
                bytes: None,
                duration_ms: elapsed_ms(started),
                error: Some(format!("failed to resolve current executable: {err}")),
            };
        }
    };
    if let Some(profile_path) = profile_path {
        command.arg("--profile").arg(profile_path);
    }
    command
        .arg("agent-screenshot")
        .arg(case.target)
        .arg("--output")
        .arg(output)
        .arg("--width")
        .arg(width.to_string())
        .arg("--height")
        .arg(height.to_string())
        .arg("--json");

    let child = match command_output_with_timeout(command, AGENT_SCREENSHOT_SMOKE_TIMEOUT) {
        Ok((child, false)) => child,
        Ok((child, true)) => {
            let stderr = String::from_utf8_lossy(&child.stderr).trim().to_string();
            return AgentSmokeCaseReport {
                name: case.name.to_string(),
                target: case.target.to_string(),
                output: output.display().to_string(),
                ok: false,
                bytes: fs::metadata(output).ok().map(|metadata| metadata.len()),
                duration_ms: elapsed_ms(started),
                error: Some(format!(
                    "screenshot subprocess killed after {}s; stderr: {}",
                    AGENT_SCREENSHOT_SMOKE_TIMEOUT.as_secs(),
                    bounded_child_output(&stderr)
                )),
            };
        }
        Err(err) => {
            return AgentSmokeCaseReport {
                name: case.name.to_string(),
                target: case.target.to_string(),
                output: output.display().to_string(),
                ok: false,
                bytes: None,
                duration_ms: elapsed_ms(started),
                error: Some(format!("failed to run screenshot subprocess: {err}")),
            };
        }
    };

    let stdout = String::from_utf8_lossy(&child.stdout).trim().to_string();
    let stderr = String::from_utf8_lossy(&child.stderr).trim().to_string();
    let parsed = serde_json::from_str::<AgentScreenshotReport>(&stdout);
    match parsed {
        Ok(report) => {
            let error = if child.status.success() && report.ok {
                None
            } else {
                Some(report.error.unwrap_or_else(|| {
                    format!("screenshot subprocess exited with {}", child.status)
                }))
            };
            AgentSmokeCaseReport {
                name: case.name.to_string(),
                target: case.target.to_string(),
                output: report.output,
                ok: error.is_none(),
                bytes: report.bytes,
                duration_ms: elapsed_ms(started),
                error,
            }
        }
        Err(err) => {
            let mut detail = format!("failed to parse screenshot JSON: {err}");
            if !stdout.is_empty() {
                detail.push_str(&format!("; stdout: {stdout}"));
            }
            if !stderr.is_empty() {
                detail.push_str(&format!("; stderr: {stderr}"));
            }
            AgentSmokeCaseReport {
                name: case.name.to_string(),
                target: case.target.to_string(),
                output: output.display().to_string(),
                ok: false,
                bytes: fs::metadata(output).ok().map(|metadata| metadata.len()),
                duration_ms: elapsed_ms(started),
                error: Some(detail),
            }
        }
    }
}

fn command_output_with_timeout(
    mut command: ProcessCommand,
    timeout: Duration,
) -> anyhow::Result<(std::process::Output, bool)> {
    command.stdout(Stdio::piped()).stderr(Stdio::piped());
    let mut child = command.spawn().context("failed to run subprocess")?;
    let started = Instant::now();
    loop {
        if child
            .try_wait()
            .context("failed to poll subprocess")?
            .is_some()
        {
            let output = child
                .wait_with_output()
                .context("failed to collect subprocess output")?;
            return Ok((output, false));
        }
        if started.elapsed() >= timeout {
            let _ = child.kill();
            let output = child
                .wait_with_output()
                .context("failed to collect killed subprocess output")?;
            return Ok((output, true));
        }
        thread::sleep(Duration::from_millis(50));
    }
}

fn validate_agent_smoke_artifact(
    kind: AgentSmokeCaseKind,
    output: &Path,
    bytes: Option<u64>,
    min_bytes: u64,
    width: i32,
    height: i32,
) -> Option<String> {
    if kind == AgentSmokeCaseKind::Screenshot {
        return validate_smoke_screenshot_artifact(output, bytes, min_bytes, width, height)
            .err()
            .map(|err| format!("{err:#}"));
    }

    bytes
        .is_none()
        .then(|| format!("smoke artifact missing {}", output.display()))
}

fn validate_smoke_screenshot_artifact(
    output: &Path,
    bytes: Option<u64>,
    min_bytes: u64,
    width: i32,
    height: i32,
) -> anyhow::Result<()> {
    let Some(size) = bytes else {
        anyhow::bail!("smoke artifact missing {}", output.display());
    };
    if size < min_bytes {
        anyhow::bail!(
            "suspiciously small screenshot {} ({} bytes)",
            output.display(),
            size
        );
    }

    let dimensions = read_png_dimensions(output)
        .with_context(|| format!("failed to validate screenshot PNG {}", output.display()))?;
    let expected = PngDimensions {
        width: width.clamp(360, 4096) as u32,
        height: height.clamp(360, 4096) as u32,
    };
    if png_device_scale(dimensions, expected).is_none() {
        anyhow::bail!(
            "screenshot dimensions {}x{} did not match expected {}x{} at a bounded integer device scale for {}",
            dimensions.width,
            dimensions.height,
            expected.width,
            expected.height,
            output.display()
        );
    }

    Ok(())
}

fn png_device_scale(dimensions: PngDimensions, expected: PngDimensions) -> Option<u32> {
    if expected.width == 0 || expected.height == 0 {
        return None;
    }
    if !dimensions.width.is_multiple_of(expected.width)
        || !dimensions.height.is_multiple_of(expected.height)
    {
        return None;
    }
    let width_scale = dimensions.width / expected.width;
    let height_scale = dimensions.height / expected.height;
    (width_scale == height_scale && (1..=4).contains(&width_scale)).then_some(width_scale)
}

fn read_png_dimensions(path: &Path) -> anyhow::Result<PngDimensions> {
    const PNG_SIGNATURE: &[u8; 8] = b"\x89PNG\r\n\x1a\n";
    let mut file = fs::File::open(path)
        .with_context(|| format!("failed to open screenshot PNG {}", path.display()))?;
    let mut header = [0_u8; 33];
    file.read_exact(&mut header)
        .with_context(|| format!("failed to read screenshot PNG header {}", path.display()))?;
    if &header[0..8] != PNG_SIGNATURE {
        anyhow::bail!("artifact is not a PNG");
    }
    let ihdr_len = u32::from_be_bytes(header[8..12].try_into()?);
    if ihdr_len != 13 || &header[12..16] != b"IHDR" {
        anyhow::bail!("PNG is missing the leading IHDR chunk");
    }
    let width = u32::from_be_bytes(header[16..20].try_into()?);
    let height = u32::from_be_bytes(header[20..24].try_into()?);
    if width == 0 || height == 0 {
        anyhow::bail!("PNG has invalid zero dimensions");
    }
    Ok(PngDimensions { width, height })
}

fn run_internal_route_trust_smoke_case(
    case: AgentSmokeCase,
    output: &Path,
) -> AgentSmokeCaseReport {
    let started = Instant::now();
    let mut report = match run_internal_route_trust_smoke(output, started) {
        Ok(report) => report,
        Err(err) => InternalRouteTrustSmokeReport {
            schema: INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA,
            ok: false,
            source_url: String::new(),
            attempted_route: INTERNAL_ROUTE_TRUST_ATTEMPT,
            profile_path: String::new(),
            before_theme: cephas::BrowserTheme::Paper.id(),
            after_theme: cephas::BrowserTheme::Paper.id(),
            server_saw_request: false,
            child_status: "not-started".to_string(),
            duration_ms: elapsed_ms(started),
            error: Some(format!("{err:#}")),
        },
    };
    let write_error = write_internal_route_trust_report(output, &report).err();
    if let Some(err) = write_error {
        report.ok = false;
        report.error = Some(format!("failed to write trust smoke report: {err:#}"));
    }
    let bytes = fs::metadata(output).ok().map(|metadata| metadata.len());
    AgentSmokeCaseReport {
        name: case.name.to_string(),
        target: case.target.to_string(),
        output: output.display().to_string(),
        ok: report.ok,
        bytes,
        duration_ms: elapsed_ms(started),
        error: report.error,
    }
}

fn run_launch_safety_smoke_case(
    case: AgentSmokeCase,
    scenario: LaunchSafetyScenario,
    output: &Path,
) -> AgentSmokeCaseReport {
    let started = Instant::now();
    let mut report = match run_launch_safety_smoke(output, started, case.name, scenario) {
        Ok(report) => report,
        Err(err) => LaunchSafetySmokeReport {
            schema: LAUNCH_SAFETY_SMOKE_SCHEMA,
            ok: false,
            case: case.name,
            profile_path: String::new(),
            launch_target: scenario.launch_target(),
            tampered_home: scenario.tampered_home(),
            loaded_home: None,
            child_status: "not-started".to_string(),
            duration_ms: elapsed_ms(started),
            error: Some(format!("{err:#}")),
        },
    };
    let write_error = write_launch_safety_report(output, &report).err();
    if let Some(err) = write_error {
        report.ok = false;
        report.error = Some(format!("failed to write launch safety report: {err:#}"));
    }
    let bytes = fs::metadata(output).ok().map(|metadata| metadata.len());
    AgentSmokeCaseReport {
        name: case.name.to_string(),
        target: case.target.to_string(),
        output: output.display().to_string(),
        ok: report.ok,
        bytes,
        duration_ms: elapsed_ms(started),
        error: report.error,
    }
}

fn run_launch_safety_smoke(
    output: &Path,
    started: Instant,
    case_name: &'static str,
    scenario: LaunchSafetyScenario,
) -> anyhow::Result<LaunchSafetySmokeReport> {
    if let Some(parent) = output.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent).with_context(|| {
            format!(
                "failed to create smoke output directory {}",
                parent.display()
            )
        })?;
    }

    let smoke_id = Uuid::now_v7();
    let profile_dir = std::env::temp_dir().join(format!("cephas-launch-safety-smoke-{smoke_id}"));
    let profile_path = profile_dir.join("profile.json");
    let profile_store = ProfileStore::new(Some(profile_path.clone()))?;

    match scenario {
        LaunchSafetyScenario::UnsafeHome => {
            write_tampered_profile_home(&profile_path, UNSAFE_HOME_LAUNCH_VALUE)?;
        }
        LaunchSafetyScenario::UnknownInternalTarget => {
            profile_store.save(&cephas::Profile::default())?;
        }
    }

    let loaded_home_result = profile_store.load().map(|profile| profile.home.to_string());
    let child_result = run_browser_child(
        &profile_path,
        scenario.launch_target(),
        INTERNAL_ROUTE_TRUST_TIMEOUT,
    );
    let _ = fs::remove_dir_all(&profile_dir);

    let (loaded_home, profile_error) = match loaded_home_result {
        Ok(home) => (Some(home), None),
        Err(err) => (
            None,
            Some(format!("failed to load isolated profile: {err:#}")),
        ),
    };
    let (child_status, child_error) = match child_result {
        Ok(outcome) => {
            let error = launch_safety_child_error(&outcome);
            (outcome.status, error)
        }
        Err(err) => ("failed".to_string(), Some(format!("{err:#}"))),
    };
    let scenario_error = launch_safety_scenario_error(scenario, loaded_home.as_deref());
    let error = profile_error.or(child_error).or(scenario_error);
    let ok = error.is_none();

    Ok(LaunchSafetySmokeReport {
        schema: LAUNCH_SAFETY_SMOKE_SCHEMA,
        ok,
        case: case_name,
        profile_path: profile_path.display().to_string(),
        launch_target: scenario.launch_target(),
        tampered_home: scenario.tampered_home(),
        loaded_home,
        child_status,
        duration_ms: elapsed_ms(started),
        error,
    })
}

impl LaunchSafetyScenario {
    fn launch_target(self) -> Option<&'static str> {
        match self {
            Self::UnsafeHome => None,
            Self::UnknownInternalTarget => Some(UNKNOWN_INTERNAL_LAUNCH_TARGET),
        }
    }

    fn tampered_home(self) -> Option<&'static str> {
        match self {
            Self::UnsafeHome => Some(UNSAFE_HOME_LAUNCH_VALUE),
            Self::UnknownInternalTarget => None,
        }
    }
}

fn launch_safety_scenario_error(
    scenario: LaunchSafetyScenario,
    loaded_home: Option<&str>,
) -> Option<String> {
    match scenario {
        LaunchSafetyScenario::UnsafeHome => {
            let expected = cephas::Profile::default().home.to_string();
            (loaded_home != Some(expected.as_str())).then(|| {
                format!(
                    "unsafe persisted home was not sanitized: expected {expected}, got {}",
                    loaded_home.unwrap_or("unreadable")
                )
            })
        }
        LaunchSafetyScenario::UnknownInternalTarget => None,
    }
}

fn launch_safety_child_error(outcome: &BrowserChildOutcome) -> Option<String> {
    let stderr = outcome.stderr.to_ascii_lowercase();
    if stderr.contains("opening the system browser instead") {
        return Some("browser child attempted external fallback".to_string());
    }
    if stderr.contains("native window failed") {
        return Some(format!(
            "browser child reported native window failure: {}",
            bounded_child_output(&outcome.stderr)
        ));
    }
    if stderr.contains("not opening internal or non-web target externally") {
        return Some(format!(
            "browser child rejected launch target before GTK safety handling: {}",
            bounded_child_output(&outcome.stderr)
        ));
    }
    None
}

fn run_internal_route_trust_smoke(
    output: &Path,
    started: Instant,
) -> anyhow::Result<InternalRouteTrustSmokeReport> {
    if let Some(parent) = output.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent).with_context(|| {
            format!(
                "failed to create smoke output directory {}",
                parent.display()
            )
        })?;
    }

    let smoke_id = Uuid::now_v7();
    let profile_dir = std::env::temp_dir().join(format!("cephas-route-trust-smoke-{smoke_id}"));
    let profile_path = profile_dir.join("profile.json");
    let profile_store = ProfileStore::new(Some(profile_path.clone()))?;
    let initial_profile = cephas::Profile::default();
    profile_store.save(&initial_profile)?;
    let before_theme = initial_profile.theme.id();

    let listener = TcpListener::bind(("127.0.0.1", 0)).context("failed to bind smoke server")?;
    listener
        .set_nonblocking(true)
        .context("failed to configure smoke server")?;
    let address = listener
        .local_addr()
        .context("failed to read smoke server address")?;
    let source_url = format!("http://{address}/");
    let server = thread::spawn(move || serve_internal_route_trust_page(listener));

    let child_status_result = run_browser_child_for_route_trust(&profile_path, &source_url);
    let server_result = server
        .join()
        .unwrap_or_else(|_| Err(anyhow::anyhow!("smoke server thread panicked")));
    let profile_result = profile_store.load();
    let _ = fs::remove_dir_all(&profile_dir);

    let (child_status, child_error) = match child_status_result {
        Ok(status) => (status, None),
        Err(err) => ("failed".to_string(), Some(format!("{err:#}"))),
    };
    let (server_saw_request, server_error) = match server_result {
        Ok(value) => (value, None),
        Err(err) => (false, Some(format!("{err:#}"))),
    };
    let (after_theme, profile_error) = match profile_result {
        Ok(profile) => (profile.theme.id(), None),
        Err(err) => ("unreadable", Some(format!("{err:#}"))),
    };
    let route_blocked = after_theme == before_theme;
    let error = child_error
        .or(server_error)
        .or(profile_error)
        .or_else(|| {
            (!server_saw_request)
                .then(|| "untrusted test page was not requested by the browser".to_string())
        })
        .or_else(|| {
            (!route_blocked).then(|| {
                format!("untrusted page changed profile theme from {before_theme} to {after_theme}")
            })
        });
    let ok = error.is_none();

    Ok(InternalRouteTrustSmokeReport {
        schema: INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA,
        ok,
        source_url,
        attempted_route: INTERNAL_ROUTE_TRUST_ATTEMPT,
        profile_path: profile_path.display().to_string(),
        before_theme,
        after_theme,
        server_saw_request,
        child_status,
        duration_ms: elapsed_ms(started),
        error,
    })
}

fn serve_internal_route_trust_page(listener: TcpListener) -> anyhow::Result<bool> {
    let started = Instant::now();
    loop {
        match listener.accept() {
            Ok((mut stream, _)) => {
                let mut buffer = [0_u8; 1024];
                let _ = stream.read(&mut buffer);
                let body = internal_route_trust_html();
                write!(
                    stream,
                    "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
                    body.len(),
                    body
                )?;
                return Ok(true);
            }
            Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
                if started.elapsed() >= INTERNAL_ROUTE_TRUST_TIMEOUT {
                    return Ok(false);
                }
                thread::sleep(Duration::from_millis(25));
            }
            Err(err) => return Err(err).context("failed to accept smoke browser request"),
        }
    }
}

fn internal_route_trust_html() -> String {
    format!(
        r#"<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Cephas route trust smoke</title><meta http-equiv="refresh" content="0; url={route}"></head><body><p>Attempting blocked Cephas route.</p><script>location.href={route_json};</script></body></html>"#,
        route = INTERNAL_ROUTE_TRUST_ATTEMPT,
        route_json =
            serde_json::to_string(INTERNAL_ROUTE_TRUST_ATTEMPT).expect("serialize route smoke URL")
    )
}

fn run_browser_child_for_route_trust(
    profile_path: &Path,
    source_url: &str,
) -> anyhow::Result<String> {
    let outcome = run_browser_child(profile_path, Some(source_url), INTERNAL_ROUTE_TRUST_TIMEOUT)?;
    if outcome.stderr.is_empty() {
        Ok(outcome.status)
    } else {
        Ok(format!(
            "{}; stderr: {}",
            outcome.status,
            bounded_child_output(&outcome.stderr)
        ))
    }
}

fn run_browser_child(
    profile_path: &Path,
    target: Option<&str>,
    timeout: Duration,
) -> anyhow::Result<BrowserChildOutcome> {
    let executable = std::env::current_exe().context("failed to resolve current executable")?;
    let mut command = ProcessCommand::new(executable);
    command
        .arg("--profile")
        .arg(profile_path)
        .arg("launch")
        .env("CEPHAS_DISABLE_EXTERNAL_FALLBACK", "1")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    if let Some(target) = target {
        command.arg(target);
    }
    let mut child = command
        .spawn()
        .context("failed to launch browser child for launch safety smoke")?;
    let started = Instant::now();
    loop {
        if let Some(status) = child.try_wait().context("failed to poll browser child")? {
            let output = child
                .wait_with_output()
                .context("failed to collect browser child output")?;
            let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
            if status.success() {
                return Ok(BrowserChildOutcome {
                    status: format!("exited:{status}"),
                    stderr,
                });
            }
            anyhow::bail!(
                "browser child exited with {status}; stderr: {}",
                bounded_child_output(&stderr)
            );
        }
        if started.elapsed() >= timeout {
            let _ = child.kill();
            let output = child
                .wait_with_output()
                .context("failed to collect killed browser child output")?;
            let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
            return Ok(BrowserChildOutcome {
                status: "killed-after-timeout".to_string(),
                stderr,
            });
        }
        thread::sleep(Duration::from_millis(50));
    }
}

fn bounded_child_output(output: &str) -> String {
    const MAX_CHILD_OUTPUT_CHARS: usize = 1_000;
    let output = output.trim();
    if output.chars().count() <= MAX_CHILD_OUTPUT_CHARS {
        return output.to_string();
    }
    let mut truncated = output
        .chars()
        .take(MAX_CHILD_OUTPUT_CHARS)
        .collect::<String>();
    truncated.push_str("...[truncated]");
    truncated
}

fn write_tampered_profile_home(profile_path: &Path, home: &str) -> anyhow::Result<()> {
    if let Some(parent) = profile_path.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent)
            .with_context(|| format!("failed to create profile directory {}", parent.display()))?;
    }
    let mut value = serde_json::to_value(cephas::Profile::default())?;
    let object = value
        .as_object_mut()
        .context("default profile did not serialize as an object")?;
    object.insert("home".to_string(), Value::String(home.to_string()));
    fs::write(profile_path, serde_json::to_vec_pretty(&value)?).with_context(|| {
        format!(
            "failed to write tampered profile {}",
            profile_path.display()
        )
    })
}

fn write_internal_route_trust_report(
    output: &Path,
    report: &InternalRouteTrustSmokeReport,
) -> anyhow::Result<()> {
    if let Some(parent) = output.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent).with_context(|| {
            format!(
                "failed to create smoke output directory {}",
                parent.display()
            )
        })?;
    }
    fs::write(output, serde_json::to_vec_pretty(report)?)
        .with_context(|| format!("failed to write smoke report {}", output.display()))
}

fn write_launch_safety_report(
    output: &Path,
    report: &LaunchSafetySmokeReport,
) -> anyhow::Result<()> {
    if let Some(parent) = output.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent).with_context(|| {
            format!(
                "failed to create smoke output directory {}",
                parent.display()
            )
        })?;
    }
    fs::write(output, serde_json::to_vec_pretty(report)?)
        .with_context(|| format!("failed to write smoke report {}", output.display()))
}

fn elapsed_ms(started: Instant) -> u64 {
    started.elapsed().as_millis().min(u64::MAX as u128) as u64
}

fn agent_tooling_manifest() -> AgentToolingManifest {
    AgentToolingManifest {
        schema: AGENT_TOOLING_SCHEMA,
        package: env!("CARGO_PKG_NAME"),
        version: env!("CARGO_PKG_VERSION"),
        binary: "cephas",
        commands: vec![
            AgentToolCommand {
                id: "agent-commands",
                argv: vec!["cephas", "agent-commands"],
                output: "json",
                description: "Stable browser-command manifest.",
            },
            AgentToolCommand {
                id: "agent-commands-jsonl",
                argv: vec!["cephas", "agent-commands", "--jsonl"],
                output: "jsonl",
                description: "One browser-command manifest entry per line.",
            },
            AgentToolCommand {
                id: "agent-smoke-json",
                argv: vec!["cephas", "agent-smoke", "--output-dir", "<dir>", "--json"],
                output: AGENT_SMOKE_SCHEMA,
                description: "Machine-readable visual smoke report for internal pages.",
            },
            AgentToolCommand {
                id: "agent-screenshot-json",
                argv: vec![
                    "cephas",
                    "agent-screenshot",
                    "<target>",
                    "--output",
                    "<png>",
                    "--json",
                ],
                output: AGENT_SCREENSHOT_SCHEMA,
                description: "Machine-readable screenshot capture result.",
            },
            AgentToolCommand {
                id: "agent-reader-json-artifact",
                argv: vec!["cephas", "agent-reader", "json-artifact", "<url>"],
                output: cephas::reader::AGENT_READER_SCHEMA,
                description: "Deterministic reader artifact for ingestion, including source_body_truncated and content_truncated flags.",
            },
            AgentToolCommand {
                id: "agent-tooling",
                argv: vec!["cephas", "agent-tooling"],
                output: AGENT_TOOLING_SCHEMA,
                description: "Portable manifest for code tools integrating with Cephas.",
            },
            AgentToolCommand {
                id: "mcp-stdio",
                argv: vec!["cephas", "mcp"],
                output: "mcp-json-rpc-stdio",
                description: "Basic read-only MCP stdio server exposing Cephas discovery, diagnostics, command manifest, and plugin registry tools.",
            },
            AgentToolCommand {
                id: "diagnostics-json",
                argv: vec!["cephas", "diagnostics", "--json"],
                output: DIAGNOSTICS_SCHEMA,
                description: "Machine-readable bounded resource diagnostics with resource_checks and unobserved_resource_caps for profile, session, downloads, Agent logs, recordings, plugins, and Armour state.",
            },
            AgentToolCommand {
                id: "diagnostics-strict",
                argv: vec!["cephas", "diagnostics", "--json", "--strict"],
                output: DIAGNOSTICS_SCHEMA,
                description: "Machine-readable diagnostics that exits non-zero when any observed resource check exceeds its cap or a diagnostics component cannot be read; unobserved_resource_caps are informational.",
            },
            AgentToolCommand {
                id: "self-test-json",
                argv: vec!["cephas", "self-test", "--json"],
                output: SELF_TEST_SCHEMA,
                description: "Fast pure self checks for launch route policy, resource cap clamps, and unsafe home migration.",
            },
            AgentToolCommand {
                id: "plugins",
                argv: vec!["cephas", "plugins"],
                output: cephas::plugins::PLUGIN_REGISTRY_SCHEMA,
                description: "UUIDv7 plugin registry with capabilities, permissions, and bounded limits.",
            },
            AgentToolCommand {
                id: "plugins-jsonl",
                argv: vec!["cephas", "plugins", "--jsonl"],
                output: "jsonl",
                description: "One UUIDv7 plugin manifest per line.",
            },
            AgentToolCommand {
                id: "plugins-available",
                argv: vec!["cephas", "plugins", "available"],
                output: cephas::plugins::PLUGIN_MODULE_SCHEMA,
                description: "Bundled project plugin modules available for local installation.",
            },
            AgentToolCommand {
                id: "plugins-check",
                argv: vec!["cephas", "plugins", "check"],
                output: cephas::plugins::PLUGIN_CHECK_SCHEMA,
                description: "Validate profile plugin registry state and bundled module metadata.",
            },
            AgentToolCommand {
                id: "plugins-check-strict",
                argv: vec!["cephas", "plugins", "check", "--strict"],
                output: cephas::plugins::PLUGIN_CHECK_SCHEMA,
                description: "Validate plugin state and fail when warnings or errors are present.",
            },
            AgentToolCommand {
                id: "plugins-install",
                argv: vec!["cephas", "plugins", "install", "<plugin-id-or-module-id>"],
                output: "cephas.plugins.action.v1",
                description: "Install one bundled project plugin into the active profile.",
            },
            AgentToolCommand {
                id: "plugins-install-all",
                argv: vec!["cephas", "plugins", "install-all"],
                output: "cephas.plugins.action.v1",
                description: "Install every bundled project plugin not yet installed in the active profile.",
            },
            AgentToolCommand {
                id: "plugins-sync",
                argv: vec!["cephas", "plugins", "sync", "<plugin-id-or-module-id>"],
                output: "cephas.plugins.action.v1",
                description: "Refresh one installed bundled plugin from the project module manifest.",
            },
            AgentToolCommand {
                id: "plugins-sync-all",
                argv: vec!["cephas", "plugins", "sync-all"],
                output: "cephas.plugins.action.v1",
                description: "Refresh every installed bundled plugin from project module manifests.",
            },
            AgentToolCommand {
                id: "plugins-enable",
                argv: vec!["cephas", "plugins", "enable", "<plugin-id-or-module-id>"],
                output: "cephas.plugins.action.v1",
                description: "Enable one installed profile plugin.",
            },
            AgentToolCommand {
                id: "plugins-disable",
                argv: vec!["cephas", "plugins", "disable", "<plugin-id-or-module-id>"],
                output: "cephas.plugins.action.v1",
                description: "Disable one installed profile plugin.",
            },
            AgentToolCommand {
                id: "plugins-remove",
                argv: vec!["cephas", "plugins", "remove", "<plugin-id-or-module-id>"],
                output: "cephas.plugins.action.v1",
                description: "Remove one installed profile plugin.",
            },
        ],
        browser_commands: cephas::agents::agent_command_manifest(),
        internal_urls: vec![
            AgentToolUrl {
                id: "start",
                url: "cephas://start",
                description: "Local Start page.",
            },
            AgentToolUrl {
                id: "start-document",
                url: "https://cephas.local/start",
                description: "Document base URL for the local Start page HTML.",
            },
            AgentToolUrl {
                id: "start-theme-toggle",
                url: "cephas://start/theme",
                description: "Cycle the saved Start/browser theme; mutates the active profile.",
            },
            AgentToolUrl {
                id: "settings",
                url: "cephas://settings",
                description: "Local Settings page.",
            },
            AgentToolUrl {
                id: "settings-document",
                url: "https://cephas.local/settings",
                description: "Document base URL for the local Settings page HTML.",
            },
            AgentToolUrl {
                id: "settings-theme-preset",
                url: "cephas://settings/theme-preset?id=<preset-id>",
                description: "Apply one bounded built-in custom theme preset to the active profile.",
            },
            AgentToolUrl {
                id: "diagnostics-page",
                url: "cephas://diagnostics",
                description: "Local live runtime diagnostics page with bounded counts and JSON command guidance.",
            },
            AgentToolUrl {
                id: "diagnostics-document",
                url: "https://cephas.local/diagnostics",
                description: "Document base URL for the local Diagnostics page.",
            },
            AgentToolUrl {
                id: "plugins-page",
                url: "cephas://plugins",
                description: "Local Settings-style UUIDv7 plugin registry and bundled module install page.",
            },
            AgentToolUrl {
                id: "agent",
                url: "cephas://agent",
                description: "Agent control page.",
            },
            AgentToolUrl {
                id: "agent-docs",
                url: "cephas://agent/docs",
                description: "Local agentic integration guide for code tools and agentic processes.",
            },
            AgentToolUrl {
                id: "agent-emulation",
                url: "cephas://agent#emulation",
                description: "Visual dry-run lab for every registered agent command.",
            },
            AgentToolUrl {
                id: "agent-emulate-command",
                url: "cephas://agent/emulate?command=<command-id>",
                description: "Internal route that selects one dry-run command in the Agent page lab.",
            },
            AgentToolUrl {
                id: "agent-document",
                url: "https://cephas.local/agent",
                description: "Document base URL for the Agent page HTML.",
            },
            AgentToolUrl {
                id: "agent-docs-document",
                url: "https://cephas.local/agent/docs",
                description: "Document base URL for the local Agent integration docs.",
            },
            AgentToolUrl {
                id: "plugins-document",
                url: "https://cephas.local/plugins",
                description: "Document base URL for the local Plugins page.",
            },
        ],
        smoke_cases: agent_smoke_cases()
            .into_iter()
            .map(|case| AgentToolSmokeCase {
                name: case.name,
                target: case.target,
                output_file: case.output_file,
            })
            .collect(),
        human_control_routes: vec![
            AgentToolRoute {
                id: "enable-human-control",
                route: "cephas://agent/human/enable",
                description: "Enable session-only browser-window human control.",
            },
            AgentToolRoute {
                id: "disable-human-control",
                route: "cephas://agent/human/disable",
                description: "Disable browser-window human control.",
            },
            AgentToolRoute {
                id: "human-move-pointer",
                route: "cephas://agent/human/move?x=<x>&y=<y>",
                description: "Move the in-page pointer at viewport coordinates.",
            },
            AgentToolRoute {
                id: "human-click",
                route: "cephas://agent/human/click?x=<x>&y=<y>",
                description: "Click the active normal page at viewport coordinates.",
            },
            AgentToolRoute {
                id: "human-type-text",
                route: "cephas://agent/human/type?text=<urlencoded>",
                description: "Type URL-encoded text into the focused element.",
            },
            AgentToolRoute {
                id: "human-press-key",
                route: "cephas://agent/human/key?key=<key>",
                description: "Dispatch a key press to the active page.",
            },
            AgentToolRoute {
                id: "human-scroll",
                route: "cephas://agent/human/scroll?dx=<dx>&dy=<dy>",
                description: "Scroll the active page by viewport deltas.",
            },
            AgentToolRoute {
                id: "human-wait",
                route: "cephas://agent/human/wait?ms=<milliseconds>",
                description: "Wait without changing the page.",
            },
        ],
        reader_tools: cephas::reader::AgentReaderTool::ALL
            .into_iter()
            .map(|tool| AgentToolReader {
                id: tool.id(),
                label: tool.label(),
                description: tool.description(),
            })
            .collect(),
        resource_caps: agent_tooling_resource_caps(),
        diagnostics_surfaces: vec![
            AgentToolDiagnosticsSurface {
                id: "diagnostics-json",
                access: vec![
                    "cephas diagnostics --json",
                    "cephas diagnostics --json --strict",
                    "mcp tool cephas_diagnostics",
                ],
                output: DIAGNOSTICS_SCHEMA,
                scope: "persisted/offline profile, session, downloads, plugin, Agent log, recording, Armour, and resource-cap checks",
                description: "Machine-readable diagnostics for resources that can be read without attaching to a running GTK browser window; caps that cannot be checked here are represented in unobserved_resource_caps.",
            },
            AgentToolDiagnosticsSurface {
                id: "diagnostics-live-page",
                access: vec![
                    "cephas://diagnostics",
                    "cephas agent-screenshot cephas://diagnostics --output <png> --json",
                    "cephas agent-smoke --output-dir <dir> --case diagnostics --json",
                ],
                output: "local-html; screenshot capture returns cephas.agent-screenshot.v1",
                scope: "running GTK/WebKit browser process counters including open tabs, closed-tab snapshots, active downloads, Agent buffers, save queue state, active recording, pending human-control waits, and RSS",
                description: "Stable local route for live runtime diagnostics; capture it with screenshot or smoke tools instead of scraping popovers. No standalone CLI JSON is exposed because these counters are process-local to the active browser UI.",
            },
        ],
        contracts: vec![
            AgentToolContract {
                id: "elixir-backend",
                path: "elixir_backend.MD",
                description: "Optional authenticated Elixir/Phoenix backend contract for profile/session sync, Agent event upload, visual recording upload, auth, retention, and UUIDv7 identity.",
            },
            AgentToolContract {
                id: "plugins-backend",
                path: "plugins_backend.MD",
                description: "Backend-facing plugin supply contract for UUIDv7 manifests, permissions, capabilities, registry limits, and package ingestion boundaries.",
            },
            AgentToolContract {
                id: "capture-optimization",
                path: "CAPTURE_OPTIMIZATION.md",
                description: "Capture/render handoff for screenshots, smoke output, visual recordings, and capture tooling.",
            },
        ],
        schemas: vec![
            AgentToolSchema {
                id: "agent-reader",
                schema: cephas::reader::AGENT_READER_SCHEMA,
            },
            AgentToolSchema {
                id: "agent-screenshot",
                schema: AGENT_SCREENSHOT_SCHEMA,
            },
            AgentToolSchema {
                id: "agent-smoke",
                schema: AGENT_SMOKE_SCHEMA,
            },
            AgentToolSchema {
                id: "internal-route-trust-smoke",
                schema: INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA,
            },
            AgentToolSchema {
                id: "launch-safety-smoke",
                schema: LAUNCH_SAFETY_SMOKE_SCHEMA,
            },
            AgentToolSchema {
                id: "agent-tooling",
                schema: AGENT_TOOLING_SCHEMA,
            },
            AgentToolSchema {
                id: "diagnostics",
                schema: DIAGNOSTICS_SCHEMA,
            },
            AgentToolSchema {
                id: "self-test",
                schema: SELF_TEST_SCHEMA,
            },
            AgentToolSchema {
                id: "plugins",
                schema: cephas::plugins::PLUGIN_REGISTRY_SCHEMA,
            },
            AgentToolSchema {
                id: "plugin-module",
                schema: cephas::plugins::PLUGIN_MODULE_SCHEMA,
            },
            AgentToolSchema {
                id: "plugins-check",
                schema: cephas::plugins::PLUGIN_CHECK_SCHEMA,
            },
            AgentToolSchema {
                id: "plugins-action",
                schema: "cephas.plugins.action.v1",
            },
            AgentToolSchema {
                id: "plugins-backend-registry",
                schema: cephas::plugins::PLUGIN_BACKEND_REGISTRY_SCHEMA,
            },
            AgentToolSchema {
                id: "plugins-backend-ingest-report",
                schema: cephas::plugins::PLUGIN_BACKEND_INGEST_REPORT_SCHEMA,
            },
        ],
        notes: vec![
            "Prefer JSON or JSONL command output for agentic processes.",
            "Use --profile <path> for isolated smoke runs that mutate settings or browsing state.",
            "Human-control routes require opt-in and are rejected on private or internal pages.",
            "Use the AI Inspect theme for high-contrast screenshot segmentation; use Glass for translucent human-facing chrome; use bounded custom theme presets as seeds for profile-local custom themes.",
            "PDF targets stay in the WebKit browser surface when the runtime supports internal PDF viewing; zoom uses WebKit layout zoom.",
            "Internal pages are rendered locally and should not require network access.",
            "Long-running integrations must honor bounded resource caps and use diagnostics to verify stabilization.",
            "CLI diagnostics JSON covers persisted/offline resources; live GTK runtime counters are exposed through cephas://diagnostics and can be captured with agent-screenshot or agent-smoke.",
            "Plugins are persisted with UUIDv7 IDs, explicit capabilities, explicit permissions, a bounded registry, and local bundled module install actions.",
        ],
    }
}

fn agent_tooling_resource_caps() -> Vec<AgentToolResourceCap> {
    vec![
        AgentToolResourceCap {
            id: "open-tabs",
            value: cephas::gui::MAX_OPEN_TABS as u64,
            unit: "count",
            description: "Maximum tabs retained by the desktop browser window.",
        },
        AgentToolResourceCap {
            id: "closed-tab-snapshots",
            value: cephas::gui::MAX_CLOSED_TABS as u64,
            unit: "count",
            description: "Maximum closed-tab snapshots retained for restore controls.",
        },
        AgentToolResourceCap {
            id: "history-entries",
            value: cephas::storage::DEFAULT_MAX_HISTORY as u64,
            unit: "count",
            description: "Maximum profile history entries retained locally.",
        },
        AgentToolResourceCap {
            id: "bookmark-entries",
            value: cephas::storage::DEFAULT_MAX_BOOKMARKS as u64,
            unit: "count",
            description: "Maximum profile bookmarks retained locally.",
        },
        AgentToolResourceCap {
            id: "profile-file-bytes",
            value: cephas::storage::MAX_PROFILE_FILE_BYTES,
            unit: "bytes",
            description: "Maximum persisted profile JSON bytes read before profile sanitation and caps apply.",
        },
        AgentToolResourceCap {
            id: "plugin-records",
            value: cephas::plugins::DEFAULT_MAX_PLUGINS as u64,
            unit: "count",
            description: "Maximum UUIDv7 plugin records in the profile registry.",
        },
        AgentToolResourceCap {
            id: "download-records",
            value: cephas::downloads::DEFAULT_MAX_DOWNLOAD_RECORDS as u64,
            unit: "count",
            description: "Maximum completed download records retained locally.",
        },
        AgentToolResourceCap {
            id: "active-downloads",
            value: cephas::downloads::MAX_ACTIVE_DOWNLOADS as u64,
            unit: "count",
            description: "Maximum concurrent active downloads.",
        },
        AgentToolResourceCap {
            id: "agent-delegations",
            value: cephas::agents::DEFAULT_MAX_AGENT_DELEGATIONS as u64,
            unit: "count",
            description: "Maximum active Agent delegations retained in memory.",
        },
        AgentToolResourceCap {
            id: "agent-events",
            value: cephas::agents::DEFAULT_MAX_AGENT_EVENTS as u64,
            unit: "count",
            description: "Maximum in-memory Agent events before older events are pruned.",
        },
        AgentToolResourceCap {
            id: "agent-text-bytes",
            value: cephas::agents::DEFAULT_MAX_AGENT_TEXT_BYTES as u64,
            unit: "bytes",
            description: "Maximum retained bytes for Agent labels, tasks, messages, action names, action details, and command targets.",
        },
        AgentToolResourceCap {
            id: "agent-metadata-bytes",
            value: cephas::agents::DEFAULT_MAX_AGENT_METADATA_BYTES as u64,
            unit: "bytes",
            description: "Maximum retained bytes for bounded Agent JSON metadata after depth and item limits are applied.",
        },
        AgentToolResourceCap {
            id: "agent-audit-log-bytes",
            value: cephas::agents::DEFAULT_MAX_AGENT_LOG_BYTES,
            unit: "bytes",
            description: "Maximum active Agent JSONL audit log size before rotation.",
        },
        AgentToolResourceCap {
            id: "agent-audit-log-archives",
            value: cephas::agents::DEFAULT_MAX_AGENT_LOG_ARCHIVES as u64,
            unit: "count",
            description: "Maximum rotated Agent audit log archives retained.",
        },
        AgentToolResourceCap {
            id: "agent-audit-log-archive-bytes",
            value: cephas::agents::DEFAULT_MAX_AGENT_LOG_BYTES
                * cephas::agents::DEFAULT_MAX_AGENT_LOG_ARCHIVES as u64,
            unit: "bytes",
            description: "Maximum total bytes across retained Agent audit log archives.",
        },
        AgentToolResourceCap {
            id: "agent-audit-log-largest-archive-bytes",
            value: cephas::agents::DEFAULT_MAX_AGENT_LOG_BYTES,
            unit: "bytes",
            description: "Maximum expected size of one rotated Agent audit log archive.",
        },
        AgentToolResourceCap {
            id: "recording-events",
            value: cephas::recording::DEFAULT_MAX_RECORDING_EVENTS as u64,
            unit: "count",
            description: "Maximum visual recording events per recording.",
        },
        AgentToolResourceCap {
            id: "recording-frames",
            value: cephas::recording::DEFAULT_MAX_RECORDING_FRAMES as u64,
            unit: "count",
            description: "Maximum PNG frames retained per visual recording.",
        },
        AgentToolResourceCap {
            id: "recording-text-bytes",
            value: cephas::recording::DEFAULT_MAX_RECORDING_TEXT_BYTES as u64,
            unit: "bytes",
            description: "Maximum retained bytes for visual recording titles, messages, frame reasons, and page titles.",
        },
        AgentToolResourceCap {
            id: "recording-details-bytes",
            value: cephas::recording::DEFAULT_MAX_RECORDING_DETAILS_BYTES as u64,
            unit: "bytes",
            description: "Maximum retained bytes for bounded visual recording JSON details after depth and item limits are applied.",
        },
        AgentToolResourceCap {
            id: "recording-directories",
            value: cephas::recording::DEFAULT_MAX_RECORDING_DIRECTORIES as u64,
            unit: "count",
            description: "Maximum visual recording directories retained before pruning.",
        },
        AgentToolResourceCap {
            id: "armour-site-allowlist-hosts",
            value: cephas::privacy::DEFAULT_MAX_SITE_ALLOWLIST as u64,
            unit: "count",
            description: "Maximum Armour per-site allowlist hosts.",
        },
        AgentToolResourceCap {
            id: "armour-blocked-hosts",
            value: cephas::privacy::DEFAULT_MAX_BLOCKED_HOSTS as u64,
            unit: "count",
            description: "Maximum Armour blocked host rules, including built-ins.",
        },
        AgentToolResourceCap {
            id: "discovered-subresources",
            value: cephas::privacy::MAX_DISCOVERED_SUBRESOURCES as u64,
            unit: "count",
            description: "Maximum discovered subresources retained in a privacy report.",
        },
        AgentToolResourceCap {
            id: "navigation-stack-entries",
            value: cephas::browser::DEFAULT_MAX_NAVIGATION_STACK as u64,
            unit: "count",
            description: "Maximum terminal-browser back or forward stack entries.",
        },
        AgentToolResourceCap {
            id: "terminal-command-line-bytes",
            value: cephas::ui::MAX_TERMINAL_COMMAND_LINE_BYTES as u64,
            unit: "bytes",
            description: "Maximum interactive terminal command line bytes read before oversized input is discarded.",
        },
        AgentToolResourceCap {
            id: "page-body-bytes",
            value: cephas::browser::DEFAULT_MAX_PAGE_BODY_BYTES as u64,
            unit: "bytes",
            description: "Maximum page body bytes read by terminal and GUI reader fetches.",
        },
        AgentToolResourceCap {
            id: "session-windows",
            value: cephas::session::MAX_SESSION_WINDOWS as u64,
            unit: "count",
            description: "Maximum session windows persisted in session snapshots.",
        },
        AgentToolResourceCap {
            id: "session-tabs-per-window",
            value: cephas::session::MAX_SESSION_TABS_PER_WINDOW as u64,
            unit: "count",
            description: "Maximum tabs persisted per session window.",
        },
        AgentToolResourceCap {
            id: "session-file-bytes",
            value: cephas::session::MAX_SESSION_FILE_BYTES,
            unit: "bytes",
            description: "Maximum persisted session JSON bytes read before restore limits apply.",
        },
        AgentToolResourceCap {
            id: "downloads-file-bytes",
            value: cephas::downloads::MAX_DOWNLOADS_FILE_BYTES,
            unit: "bytes",
            description: "Maximum persisted downloads JSON bytes read before download limits apply.",
        },
        AgentToolResourceCap {
            id: "save-queue-jobs",
            value: cephas::gui::SAVE_QUEUE_CAPACITY as u64,
            unit: "count",
            description: "Maximum queued async save jobs before callers fall back to synchronous saves.",
        },
        AgentToolResourceCap {
            id: "pending-human-waits",
            value: cephas::gui::MAX_PENDING_HUMAN_WAITS as u64,
            unit: "count",
            description: "Maximum queued browser-window human-control wait timers.",
        },
        AgentToolResourceCap {
            id: "ai-reader-content-bytes",
            value: cephas::reader::DEFAULT_AI_READER_MAX_CONTENT_BYTES as u64,
            unit: "bytes",
            description: "Maximum extracted AI Reader content bytes.",
        },
        AgentToolResourceCap {
            id: "ai-reader-headings",
            value: cephas::reader::DEFAULT_AI_READER_MAX_HEADINGS as u64,
            unit: "count",
            description: "Maximum headings retained in AI Reader documents.",
        },
        AgentToolResourceCap {
            id: "agent-reader-links",
            value: cephas::reader::DEFAULT_AGENT_READER_MAX_LINKS as u64,
            unit: "count",
            description: "Maximum links retained in Agent reader link maps.",
        },
        AgentToolResourceCap {
            id: "agent-reader-signal-values",
            value: cephas::reader::DEFAULT_AGENT_READER_MAX_SIGNAL_VALUES as u64,
            unit: "count",
            description: "Maximum metadata signal values retained per Agent reader artifact.",
        },
        AgentToolResourceCap {
            id: "agent-reader-tables",
            value: cephas::reader::DEFAULT_AGENT_READER_MAX_TABLES as u64,
            unit: "count",
            description: "Maximum tables retained in Agent reader data signals.",
        },
        AgentToolResourceCap {
            id: "agent-reader-table-rows",
            value: cephas::reader::DEFAULT_AGENT_READER_MAX_TABLE_ROWS as u64,
            unit: "count",
            description: "Maximum rows retained per Agent reader table.",
        },
        AgentToolResourceCap {
            id: "agent-reader-table-cells",
            value: cephas::reader::DEFAULT_AGENT_READER_MAX_TABLE_CELLS as u64,
            unit: "count",
            description: "Maximum cells retained per Agent reader table row.",
        },
        AgentToolResourceCap {
            id: "mcp-request-line-bytes",
            value: MCP_MAX_LINE_BYTES as u64,
            unit: "bytes",
            description: "Maximum one-line JSON-RPC request bytes accepted by the read-only MCP stdio bridge.",
        },
        AgentToolResourceCap {
            id: "mcp-batch-requests",
            value: MCP_MAX_BATCH_REQUESTS as u64,
            unit: "count",
            description: "Maximum JSON-RPC requests accepted in one MCP batch.",
        },
    ]
}

fn diagnostics_report(profile: &cephas::Profile, store: &ProfileStore) -> DiagnosticsReport {
    let mut errors = Vec::new();
    let session = match store.session_store().load() {
        Ok(session) => {
            let windows = session.windows.len() as u64;
            let tabs_per_window = session
                .windows
                .iter()
                .map(|window| window.tabs.len() as u64)
                .max()
                .unwrap_or(0);
            let tabs = session
                .windows
                .iter()
                .map(|window| window.tabs.len() as u64)
                .sum::<u64>();
            Some(DiagnosticsSession {
                windows: DiagnosticsUsage {
                    current: windows,
                    max: cephas::session::MAX_SESSION_WINDOWS as u64,
                },
                tabs: DiagnosticsUsage {
                    current: tabs,
                    max: (cephas::session::MAX_SESSION_WINDOWS
                        * cephas::session::MAX_SESSION_TABS_PER_WINDOW)
                        as u64,
                },
                tabs_per_window: DiagnosticsUsage {
                    current: tabs_per_window,
                    max: cephas::session::MAX_SESSION_TABS_PER_WINDOW as u64,
                },
            })
        }
        Err(err) => {
            errors.push(DiagnosticsError {
                component: "session",
                error: err.to_string(),
            });
            None
        }
    };
    let downloads = match store.download_store().load() {
        Ok(downloads) => {
            let mut in_progress = 0_u64;
            let mut paused = 0_u64;
            let mut completed = 0_u64;
            let mut cancelled = 0_u64;
            let mut failed = 0_u64;
            for record in downloads.records() {
                match &record.status {
                    cephas::downloads::DownloadStatus::InProgress => in_progress += 1,
                    cephas::downloads::DownloadStatus::Paused => paused += 1,
                    cephas::downloads::DownloadStatus::Completed => completed += 1,
                    cephas::downloads::DownloadStatus::Cancelled => cancelled += 1,
                    cephas::downloads::DownloadStatus::Failed(_) => failed += 1,
                }
            }
            Some(DiagnosticsDownloads {
                records: DiagnosticsUsage {
                    current: downloads.records().count() as u64,
                    max: downloads.max_records() as u64,
                },
                in_progress,
                paused,
                completed,
                cancelled,
                failed,
            })
        }
        Err(err) => {
            errors.push(DiagnosticsError {
                component: "downloads",
                error: err.to_string(),
            });
            None
        }
    };
    let agent_log =
        match cephas::agents::AgentLogStore::new(None).and_then(|store| store.diagnostics()) {
            Ok(log) => Some(DiagnosticsAgentLog {
                bytes: DiagnosticsUsage {
                    current: log.bytes,
                    max: log.max_bytes,
                },
                archives: DiagnosticsUsage {
                    current: log.archives as u64,
                    max: log.max_archives as u64,
                },
                total_archive_bytes: DiagnosticsUsage {
                    current: log.total_archive_bytes,
                    max: log.max_total_archive_bytes,
                },
                largest_archive_bytes: DiagnosticsUsage {
                    current: log.largest_archive_bytes,
                    max: log.max_archive_bytes,
                },
            }),
            Err(err) => {
                errors.push(DiagnosticsError {
                    component: "agent-log",
                    error: err.to_string(),
                });
                None
            }
        };
    let recordings = match cephas::recording::BrowserRecordingStore::new(None)
        .and_then(|store| store.diagnostics())
    {
        Ok(recordings) => Some(DiagnosticsRecordings {
            directories: DiagnosticsUsage {
                current: recordings.directories as u64,
                max: recordings.max_directories as u64,
            },
            total_bytes: recordings.total_bytes,
        }),
        Err(err) => {
            errors.push(DiagnosticsError {
                component: "recordings",
                error: err.to_string(),
            });
            None
        }
    };

    let resource_caps = agent_tooling_resource_caps();
    let resource_checks = diagnostics_resource_checks(
        &resource_caps,
        profile,
        session.as_ref(),
        downloads.as_ref(),
        agent_log.as_ref(),
        recordings.as_ref(),
    );
    let unobserved_resource_caps =
        diagnostics_unobserved_resource_caps(&resource_caps, &resource_checks);
    let ok = errors.is_empty() && resource_checks.values().all(|check| check.ok);

    DiagnosticsReport {
        schema: DIAGNOSTICS_SCHEMA,
        ok,
        profile_path: store.path().display().to_string(),
        profile: DiagnosticsProfile {
            home: profile.home.to_string(),
            theme: profile.theme.id(),
            ai_browsing_enabled: profile.ai_browsing_enabled,
            media_policy: profile.media_playback.id(),
            media_capture_enabled: profile.media_playback.allows_capture(),
            web_acceleration: profile.web_acceleration.id(),
            history_entries: DiagnosticsUsage {
                current: profile.history.len() as u64,
                max: profile.max_history.max(1) as u64,
            },
            bookmarks: DiagnosticsUsage {
                current: profile.bookmarks.len() as u64,
                max: profile.max_bookmarks.max(1) as u64,
            },
        },
        session,
        downloads,
        plugins: DiagnosticsPlugins {
            installed: DiagnosticsUsage {
                current: profile.plugins.installed_count() as u64,
                max: profile.plugins.max_plugins.max(1) as u64,
            },
            enabled: profile.plugins.enabled_count() as u64,
            planned: profile.plugins.planned_count() as u64,
        },
        armour: DiagnosticsArmour {
            site_allowlist_hosts: DiagnosticsUsage {
                current: profile.shields.site_allowlist.len() as u64,
                max: cephas::privacy::DEFAULT_MAX_SITE_ALLOWLIST as u64,
            },
            blocked_hosts: DiagnosticsUsage {
                current: profile.shields.blocked_hosts.len() as u64,
                max: cephas::privacy::DEFAULT_MAX_BLOCKED_HOSTS as u64,
            },
        },
        agent_log,
        recordings,
        resource_caps,
        resource_checks,
        unobserved_resource_caps,
        errors,
    }
}

fn diagnostics_unobserved_resource_caps(
    caps: &[AgentToolResourceCap],
    checks: &BTreeMap<&'static str, DiagnosticsResourceCheck>,
) -> Vec<DiagnosticsUnobservedResourceCap> {
    caps.iter()
        .filter(|cap| !checks.contains_key(cap.id))
        .map(|cap| {
            let (component, reason, surface) = diagnostics_unobserved_cap_metadata(cap.id);
            DiagnosticsUnobservedResourceCap {
                id: cap.id,
                component,
                max: cap.value,
                unit: cap.unit,
                reason,
                surface,
                description: cap.description,
            }
        })
        .collect()
}

fn diagnostics_unobserved_cap_metadata(id: &str) -> (&'static str, &'static str, &'static str) {
    match id {
        "open-tabs" | "closed-tab-snapshots" | "save-queue-jobs" | "pending-human-waits" => (
            "browser-ui",
            "live-runtime-only",
            "cephas://diagnostics or agent-smoke --output-dir <dir> --case diagnostics --json",
        ),
        "agent-delegations" | "agent-events" => (
            "agent-runtime",
            "live-runtime-only",
            "cephas://diagnostics or agent-smoke --output-dir <dir> --case diagnostics --json",
        ),
        "agent-text-bytes" | "agent-metadata-bytes" => (
            "agent-runtime",
            "per-event-limit",
            "Agent delegation and event creation",
        ),
        "recording-events" | "recording-frames" => (
            "recording-runtime",
            "live-runtime-only",
            "cephas://diagnostics or agent-smoke --output-dir <dir> --case diagnostics --json",
        ),
        "recording-text-bytes" | "recording-details-bytes" => (
            "recording-runtime",
            "per-event-limit",
            "visual recording event creation",
        ),
        "discovered-subresources" => (
            "privacy-report",
            "per-operation-limit",
            "privacy report generation",
        ),
        "navigation-stack-entries" => (
            "terminal-browser",
            "live-runtime-only",
            "interactive terminal browser diagnostics",
        ),
        "terminal-command-line-bytes" => (
            "terminal-browser",
            "per-input-limit",
            "interactive terminal command input",
        ),
        "page-body-bytes" => ("fetch", "per-operation-limit", "page and reader fetches"),
        "profile-file-bytes" => ("profile", "per-file-limit", "profile load"),
        "session-file-bytes" => ("session", "per-file-limit", "session restore load"),
        "downloads-file-bytes" => ("downloads", "per-file-limit", "downloads registry load"),
        "mcp-request-line-bytes" | "mcp-batch-requests" => (
            "mcp-stdio",
            "per-request-limit",
            "read-only MCP stdio request handling",
        ),
        "ai-reader-content-bytes" | "ai-reader-headings" => (
            "ai-reader",
            "per-artifact-limit",
            "ai-reader artifact output",
        ),
        "agent-reader-links"
        | "agent-reader-signal-values"
        | "agent-reader-tables"
        | "agent-reader-table-rows"
        | "agent-reader-table-cells" => (
            "agent-reader",
            "per-artifact-limit",
            "agent-reader artifact output",
        ),
        _ => (
            "resource-cap",
            "not-observed-by-persisted-diagnostics",
            "agent-tooling.resource_caps",
        ),
    }
}

fn diagnostics_resource_checks(
    caps: &[AgentToolResourceCap],
    profile: &cephas::Profile,
    session: Option<&DiagnosticsSession>,
    downloads: Option<&DiagnosticsDownloads>,
    agent_log: Option<&DiagnosticsAgentLog>,
    recordings: Option<&DiagnosticsRecordings>,
) -> BTreeMap<&'static str, DiagnosticsResourceCheck> {
    let mut checks = BTreeMap::new();
    push_diagnostics_usage_check(
        &mut checks,
        caps,
        "profile",
        "history-entries",
        DiagnosticsUsage {
            current: profile.history.len() as u64,
            max: profile.max_history.max(1) as u64,
        },
    );
    push_diagnostics_usage_check(
        &mut checks,
        caps,
        "profile",
        "bookmark-entries",
        DiagnosticsUsage {
            current: profile.bookmarks.len() as u64,
            max: profile.max_bookmarks.max(1) as u64,
        },
    );
    push_diagnostics_usage_check(
        &mut checks,
        caps,
        "plugins",
        "plugin-records",
        DiagnosticsUsage {
            current: profile.plugins.installed_count() as u64,
            max: profile.plugins.max_plugins.max(1) as u64,
        },
    );
    push_diagnostics_usage_check(
        &mut checks,
        caps,
        "armour",
        "armour-site-allowlist-hosts",
        DiagnosticsUsage {
            current: profile.shields.site_allowlist.len() as u64,
            max: cephas::privacy::DEFAULT_MAX_SITE_ALLOWLIST as u64,
        },
    );
    push_diagnostics_usage_check(
        &mut checks,
        caps,
        "armour",
        "armour-blocked-hosts",
        DiagnosticsUsage {
            current: profile.shields.blocked_hosts.len() as u64,
            max: cephas::privacy::DEFAULT_MAX_BLOCKED_HOSTS as u64,
        },
    );
    if let Some(session) = session {
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "session",
            "session-windows",
            session.windows,
        );
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "session",
            "session-tabs-per-window",
            session.tabs_per_window,
        );
    }
    if let Some(downloads) = downloads {
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "downloads",
            "download-records",
            downloads.records,
        );
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "downloads",
            "active-downloads",
            DiagnosticsUsage {
                current: downloads.in_progress,
                max: cephas::downloads::MAX_ACTIVE_DOWNLOADS as u64,
            },
        );
    }
    if let Some(agent_log) = agent_log {
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "agent-log",
            "agent-audit-log-bytes",
            agent_log.bytes,
        );
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "agent-log",
            "agent-audit-log-archives",
            agent_log.archives,
        );
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "agent-log",
            "agent-audit-log-archive-bytes",
            agent_log.total_archive_bytes,
        );
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "agent-log",
            "agent-audit-log-largest-archive-bytes",
            agent_log.largest_archive_bytes,
        );
    }
    if let Some(recordings) = recordings {
        push_diagnostics_usage_check(
            &mut checks,
            caps,
            "recordings",
            "recording-directories",
            recordings.directories,
        );
    }
    checks
}

fn push_diagnostics_usage_check(
    checks: &mut BTreeMap<&'static str, DiagnosticsResourceCheck>,
    caps: &[AgentToolResourceCap],
    component: &'static str,
    id: &'static str,
    usage: DiagnosticsUsage,
) {
    let Some(cap) = caps.iter().find(|cap| cap.id == id) else {
        return;
    };
    checks.insert(
        id,
        DiagnosticsResourceCheck {
            id: cap.id,
            component,
            current: usage.current,
            max: usage.max,
            unit: cap.unit,
            ok: usage.current <= usage.max,
            description: cap.description,
        },
    );
}

fn print_diagnostics_report(
    profile: &cephas::Profile,
    store: &ProfileStore,
    json: bool,
    strict: bool,
) -> anyhow::Result<()> {
    let report = diagnostics_report(profile, store);
    if json {
        println!("{}", serde_json::to_string_pretty(&report)?);
        if diagnostics_should_fail(&report, strict) {
            return Err(diagnostics_failure(&report));
        }
        return Ok(());
    }

    println!("diagnostics: {}", report.profile_path);
    println!("  status: {}", if report.ok { "ok" } else { "attention" });
    let ok_checks = report
        .resource_checks
        .values()
        .filter(|check| check.ok)
        .count();
    println!(
        "  resource checks: {ok_checks} / {} ok",
        report.resource_checks.len()
    );
    if !report.unobserved_resource_caps.is_empty() {
        println!(
            "  unobserved resource caps: {} (see JSON unobserved_resource_caps)",
            report.unobserved_resource_caps.len()
        );
    }
    println!(
        "  history: {} / {}",
        report.profile.history_entries.current, report.profile.history_entries.max
    );
    println!(
        "  bookmarks: {} / {}",
        report.profile.bookmarks.current, report.profile.bookmarks.max
    );
    println!(
        "  plugins: {} / {}, {} enabled, {} planned",
        report.plugins.installed.current,
        report.plugins.installed.max,
        report.plugins.enabled,
        report.plugins.planned
    );
    if let Some(session) = &report.session {
        println!(
            "  session windows: {} / {}",
            session.windows.current, session.windows.max
        );
        println!(
            "  session tabs: {} / {}",
            session.tabs.current, session.tabs.max
        );
    }
    if let Some(downloads) = &report.downloads {
        println!(
            "  download records: {} / {}",
            downloads.records.current, downloads.records.max
        );
        println!(
            "  download states: {} in progress, {} paused, {} complete, {} cancelled, {} failed",
            downloads.in_progress,
            downloads.paused,
            downloads.completed,
            downloads.cancelled,
            downloads.failed
        );
    }
    println!(
        "  Armour allowlist: {} / {}",
        report.armour.site_allowlist_hosts.current, report.armour.site_allowlist_hosts.max
    );
    println!(
        "  Armour blocked hosts: {} / {}",
        report.armour.blocked_hosts.current, report.armour.blocked_hosts.max
    );
    if let Some(agent_log) = &report.agent_log {
        println!(
            "  Agent audit log: {} / {} bytes, {} / {} archives, {} / {} archive bytes, largest {} / {} bytes",
            agent_log.bytes.current,
            agent_log.bytes.max,
            agent_log.archives.current,
            agent_log.archives.max,
            agent_log.total_archive_bytes.current,
            agent_log.total_archive_bytes.max,
            agent_log.largest_archive_bytes.current,
            agent_log.largest_archive_bytes.max
        );
    }
    if let Some(recordings) = &report.recordings {
        println!(
            "  recordings: {} / {} directories, {} bytes",
            recordings.directories.current, recordings.directories.max, recordings.total_bytes
        );
    }
    for check in report.resource_checks.values().filter(|check| !check.ok) {
        println!(
            "  {} over cap: {} / {} {}",
            check.id, check.current, check.max, check.unit
        );
    }
    for error in &report.errors {
        println!(
            "  {} diagnostics unavailable: {}",
            error.component, error.error
        );
    }
    if diagnostics_should_fail(&report, strict) {
        return Err(diagnostics_failure(&report));
    }
    Ok(())
}

fn diagnostics_should_fail(report: &DiagnosticsReport, strict: bool) -> bool {
    strict && !report.ok
}

fn diagnostics_failure(report: &DiagnosticsReport) -> anyhow::Error {
    let failed_checks = report
        .resource_checks
        .values()
        .filter(|check| !check.ok)
        .count();
    anyhow::anyhow!(
        "diagnostics failed: {failed_checks} resource checks over cap, {} collection errors",
        report.errors.len()
    )
}

fn run_mcp_server(profile: &cephas::Profile, store: &ProfileStore) -> anyhow::Result<()> {
    let stdin = io::stdin();
    let mut stdin = stdin.lock();
    let mut stdout = io::stdout();
    while let Some(line) = read_bounded_line(&mut stdin, MCP_MAX_LINE_BYTES)? {
        let line = match line {
            Ok(line) => line,
            Err(()) => {
                let response = mcp_error_response(
                    None,
                    -32600,
                    format!("request line exceeds {MCP_MAX_LINE_BYTES} bytes"),
                );
                serde_json::to_writer(&mut stdout, &response)?;
                stdout.write_all(b"\n")?;
                stdout.flush()?;
                continue;
            }
        };
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        let response = match serde_json::from_str::<Value>(line) {
            Ok(message) => mcp_handle_message(profile, store, message),
            Err(err) => Some(mcp_error_response(
                None,
                -32700,
                format!("parse error: {err}"),
            )),
        };
        if let Some(response) = response {
            serde_json::to_writer(&mut stdout, &response)?;
            stdout.write_all(b"\n")?;
            stdout.flush()?;
        }
    }
    Ok(())
}

fn read_bounded_line<R: BufRead>(
    reader: &mut R,
    max_bytes: usize,
) -> io::Result<Option<Result<String, ()>>> {
    let mut line = Vec::new();
    let mut overflow = false;
    loop {
        let available = reader.fill_buf()?;
        if available.is_empty() {
            return if line.is_empty() && !overflow {
                Ok(None)
            } else if overflow {
                Ok(Some(Err(())))
            } else {
                Ok(Some(Ok(String::from_utf8_lossy(&line).into_owned())))
            };
        }

        let take = available
            .iter()
            .position(|byte| *byte == b'\n')
            .map(|index| index + 1)
            .unwrap_or(available.len());
        if !overflow {
            let remaining = max_bytes.saturating_sub(line.len());
            if take <= remaining {
                line.extend_from_slice(&available[..take]);
            } else {
                line.extend_from_slice(&available[..remaining]);
                overflow = true;
            }
        }
        let found_newline = available[..take].last() == Some(&b'\n');
        reader.consume(take);
        if found_newline {
            return if overflow {
                Ok(Some(Err(())))
            } else {
                Ok(Some(Ok(String::from_utf8_lossy(&line).into_owned())))
            };
        }
    }
}

fn mcp_handle_message(
    profile: &cephas::Profile,
    store: &ProfileStore,
    message: Value,
) -> Option<Value> {
    match message {
        Value::Array(messages) => {
            if messages.len() > MCP_MAX_BATCH_REQUESTS {
                return Some(mcp_error_response(
                    None,
                    -32600,
                    format!("batch request exceeds {MCP_MAX_BATCH_REQUESTS} messages"),
                ));
            }
            let responses = messages
                .into_iter()
                .filter_map(|message| mcp_handle_request(profile, store, message))
                .collect::<Vec<_>>();
            if responses.is_empty() {
                None
            } else {
                Some(Value::Array(responses))
            }
        }
        message => mcp_handle_request(profile, store, message),
    }
}

fn mcp_handle_request(
    profile: &cephas::Profile,
    store: &ProfileStore,
    request: Value,
) -> Option<Value> {
    let Some(object) = request.as_object() else {
        return Some(mcp_error_response(
            None,
            -32600,
            "invalid request".to_string(),
        ));
    };
    let id = object.get("id").cloned();
    let Some(method) = object.get("method").and_then(Value::as_str) else {
        return id.map(|id| mcp_error_response(Some(id), -32600, "missing method".to_string()));
    };
    let id = id?;
    if object.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
        return Some(mcp_error_response(
            Some(id),
            -32600,
            "expected jsonrpc 2.0".to_string(),
        ));
    }

    match method {
        "initialize" => Some(mcp_success_response(
            id,
            json!({
                "protocolVersion": MCP_PROTOCOL_VERSION,
                "serverInfo": {
                    "name": "cephas",
                    "version": env!("CARGO_PKG_VERSION")
                },
                "capabilities": {
                    "tools": {
                        "listChanged": false
                    }
                }
            }),
        )),
        "ping" => Some(mcp_success_response(id, json!({}))),
        "tools/list" => Some(mcp_success_response(
            id,
            json!({ "tools": mcp_tool_definitions() }),
        )),
        "tools/call" => Some(
            match mcp_tool_call_result(profile, store, object.get("params")) {
                Ok(result) => mcp_success_response(id, result),
                Err(err) => mcp_error_response(Some(id), -32602, err.to_string()),
            },
        ),
        "resources/list" => Some(mcp_success_response(id, json!({ "resources": [] }))),
        "prompts/list" => Some(mcp_success_response(id, json!({ "prompts": [] }))),
        _ => Some(mcp_error_response(
            Some(id),
            -32601,
            format!("unknown method '{method}'"),
        )),
    }
}

fn mcp_success_response(id: Value, result: Value) -> Value {
    json!({
        "jsonrpc": "2.0",
        "id": id,
        "result": result
    })
}

fn mcp_error_response(id: Option<Value>, code: i64, message: String) -> Value {
    json!({
        "jsonrpc": "2.0",
        "id": id.unwrap_or(Value::Null),
        "error": {
            "code": code,
            "message": message
        }
    })
}

fn mcp_tool_definitions() -> Vec<Value> {
    [
        (
            "cephas_agent_tooling",
            "Return Cephas' portable agent-tooling manifest as JSON.",
        ),
        (
            "cephas_agent_commands",
            "Return the stable browser-command manifest as JSON.",
        ),
        (
            "cephas_diagnostics",
            "Return bounded resource diagnostics as JSON.",
        ),
        (
            "cephas_plugins",
            "Return the UUIDv7 profile plugin registry and bundled module catalog as JSON.",
        ),
    ]
    .into_iter()
    .map(|(name, description)| {
        json!({
            "name": name,
            "description": description,
            "inputSchema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
            }
        })
    })
    .collect()
}

fn mcp_tool_call_result(
    profile: &cephas::Profile,
    store: &ProfileStore,
    params: Option<&Value>,
) -> anyhow::Result<Value> {
    let params = params
        .and_then(Value::as_object)
        .context("tools/call params must be an object")?;
    let name = params
        .get("name")
        .and_then(Value::as_str)
        .context("tools/call params.name must be a string")?;
    let text = match name {
        "cephas_agent_tooling" => serde_json::to_string_pretty(&agent_tooling_manifest())?,
        "cephas_agent_commands" => {
            serde_json::to_string_pretty(&cephas::agents::agent_command_manifest())?
        }
        "cephas_diagnostics" => serde_json::to_string_pretty(&diagnostics_report(profile, store))?,
        "cephas_plugins" => plugin_registry_report_json(profile)?,
        other => anyhow::bail!("unknown MCP tool '{other}'"),
    };
    Ok(json!({
        "content": [
            {
                "type": "text",
                "text": text
            }
        ],
        "isError": false
    }))
}

fn plugin_registry_report_json(profile: &cephas::Profile) -> anyhow::Result<String> {
    let bundled = cephas::plugins::bundled_plugin_modules();
    let available = cephas::plugins::available_bundled_plugin_modules(&profile.plugins);
    let report = PluginRegistryReport {
        schema: cephas::plugins::PLUGIN_REGISTRY_SCHEMA,
        enabled: profile.plugins.enabled,
        max_plugins: profile.plugins.max_plugins,
        installed_count: profile.plugins.installed_count(),
        enabled_count: profile.plugins.enabled_count(),
        planned_count: profile.plugins.planned_count(),
        bundled_count: bundled.len(),
        available_count: available.len(),
        installed: &profile.plugins.installed,
        bundled: &bundled,
        available: &available,
    };
    Ok(serde_json::to_string_pretty(&report)?)
}

fn plugin_command(
    mut profile: cephas::Profile,
    store: ProfileStore,
    jsonl: bool,
    action: Option<PluginCommand>,
) -> anyhow::Result<()> {
    let Some(action) = action else {
        return print_plugins(&profile, jsonl);
    };

    match action {
        PluginCommand::Available { jsonl } => print_bundled_plugins(&profile, jsonl),
        PluginCommand::Check {
            strict,
            fail_on_warning,
        } => print_plugin_check(&profile, strict, fail_on_warning),
        PluginCommand::Install { selector } => {
            let id = resolve_plugin_selector(&selector)?;
            let plugin = profile.plugins.install_bundled(id)?;
            store.save(&profile)?;
            print_plugin_action("install", plugin, &profile)
        }
        PluginCommand::InstallAll => {
            let plugins = profile.plugins.install_all_available_bundled()?;
            store.save(&profile)?;
            print_plugin_bulk_action("install-all", plugins, &profile)
        }
        PluginCommand::Sync { selector } => {
            let id = resolve_plugin_selector(&selector)?;
            let plugin = profile.plugins.sync_bundled(id)?;
            store.save(&profile)?;
            print_plugin_action("sync", plugin, &profile)
        }
        PluginCommand::SyncAll => {
            let plugins = profile.plugins.sync_all_installed_bundled()?;
            store.save(&profile)?;
            print_plugin_bulk_action("sync-all", plugins, &profile)
        }
        PluginCommand::Enable { selector } => {
            let id = resolve_plugin_selector(&selector)?;
            let plugin = profile
                .plugins
                .set_status(id, cephas::plugins::PluginStatus::Enabled)?;
            store.save(&profile)?;
            print_plugin_action("enable", plugin, &profile)
        }
        PluginCommand::Disable { selector } => {
            let id = resolve_plugin_selector(&selector)?;
            let plugin = profile
                .plugins
                .set_status(id, cephas::plugins::PluginStatus::Disabled)?;
            store.save(&profile)?;
            print_plugin_action("disable", plugin, &profile)
        }
        PluginCommand::Remove { selector } => {
            let id = resolve_plugin_selector(&selector)?;
            let plugin = profile.plugins.remove(id)?;
            store.save(&profile)?;
            print_plugin_action("remove", plugin, &profile)
        }
    }
}

fn print_plugins(profile: &cephas::Profile, jsonl: bool) -> anyhow::Result<()> {
    if jsonl {
        for plugin in &profile.plugins.installed {
            println!("{}", serde_json::to_string(plugin)?);
        }
        return Ok(());
    }

    let bundled = cephas::plugins::bundled_plugin_modules();
    let available = cephas::plugins::available_bundled_plugin_modules(&profile.plugins);
    let report = PluginRegistryReport {
        schema: cephas::plugins::PLUGIN_REGISTRY_SCHEMA,
        enabled: profile.plugins.enabled,
        max_plugins: profile.plugins.max_plugins,
        installed_count: profile.plugins.installed_count(),
        enabled_count: profile.plugins.enabled_count(),
        planned_count: profile.plugins.planned_count(),
        bundled_count: bundled.len(),
        available_count: available.len(),
        installed: &profile.plugins.installed,
        bundled: &bundled,
        available: &available,
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

fn print_bundled_plugins(profile: &cephas::Profile, jsonl: bool) -> anyhow::Result<()> {
    let bundled = cephas::plugins::available_bundled_plugin_modules(&profile.plugins);
    if jsonl {
        for module in &bundled {
            println!("{}", serde_json::to_string(module)?);
        }
    } else {
        println!("{}", serde_json::to_string_pretty(&bundled)?);
    }
    Ok(())
}

fn print_plugin_check(
    profile: &cephas::Profile,
    strict: bool,
    fail_on_warning: bool,
) -> anyhow::Result<()> {
    let report = cephas::plugins::check_plugin_registry(&profile.plugins);
    println!("{}", serde_json::to_string_pretty(&report)?);
    if plugin_check_should_fail(&report, strict, fail_on_warning) {
        anyhow::bail!(
            "plugin check failed: {} errors, {} warnings",
            report.error_count,
            report.warning_count
        );
    }
    Ok(())
}

fn plugin_check_should_fail(
    report: &cephas::plugins::PluginCheckReport,
    strict: bool,
    fail_on_warning: bool,
) -> bool {
    report.error_count > 0 || ((strict || fail_on_warning) && report.warning_count > 0)
}

fn resolve_plugin_selector(selector: &str) -> anyhow::Result<cephas::plugins::PluginId> {
    cephas::plugins::resolve_plugin_selector(selector)
        .with_context(|| format!("unknown plugin selector '{selector}'"))
}

fn print_plugin_action(
    action: &'static str,
    plugin: cephas::plugins::PluginManifest,
    profile: &cephas::Profile,
) -> anyhow::Result<()> {
    let module = cephas::plugins::bundled_plugin_module_for_manifest(plugin.id);
    let report = PluginActionReport {
        schema: "cephas.plugins.action.v1",
        action,
        module_id: module.as_ref().map(|module| module.module_id.clone()),
        source_path: module.as_ref().map(|module| module.source_path.clone()),
        plugin,
        installed_count: profile.plugins.installed_count(),
        enabled_count: profile.plugins.enabled_count(),
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

fn print_plugin_bulk_action(
    action: &'static str,
    plugins: Vec<cephas::plugins::PluginManifest>,
    profile: &cephas::Profile,
) -> anyhow::Result<()> {
    let report = PluginBulkActionReport {
        schema: "cephas.plugins.action.v1",
        action,
        changed_count: plugins.len(),
        plugins,
        installed_count: profile.plugins.installed_count(),
        enabled_count: profile.plugins.enabled_count(),
        available_count: cephas::plugins::available_bundled_plugin_modules(&profile.plugins).len(),
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

fn open_once(mut browser: Browser, store: ProfileStore, target: String) -> anyhow::Result<()> {
    {
        let page = browser
            .open(&target)
            .with_context(|| format!("failed to open {target}"))?;
        cephas::ui::print_page(page);
    }
    store.save(&browser.profile)?;
    Ok(())
}

fn ai_reader_once(mut browser: Browser, store: ProfileStore, target: String) -> anyhow::Result<()> {
    {
        let page = browser
            .open(&target)
            .with_context(|| format!("failed to open {target}"))?;
        let document = cephas::reader::ai_reader_document(&page.article, &page.url);
        println!("{}", cephas::reader::format_ai_reader_markdown(&document));
    }
    store.save(&browser.profile)?;
    Ok(())
}

fn agent_reader_once(
    mut browser: Browser,
    store: ProfileStore,
    tool: String,
    target: String,
) -> anyhow::Result<()> {
    let tool = cephas::reader::AgentReaderTool::from_id(&tool).with_context(|| {
        let tools = cephas::reader::AgentReaderTool::ALL
            .into_iter()
            .map(|tool| tool.id())
            .collect::<Vec<_>>()
            .join(", ");
        format!("unknown agent reader tool '{tool}'. Expected one of: {tools}")
    })?;
    {
        let page = browser
            .open(&target)
            .with_context(|| format!("failed to open {target}"))?;
        let artifact = cephas::reader::agent_reader_artifact(
            &page.article,
            &page.body,
            &page.url,
            tool,
            page.body_truncated,
        );
        println!("{}", cephas::reader::format_agent_reader_output(&artifact));
    }
    store.save(&browser.profile)?;
    Ok(())
}

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

    #[test]
    fn smoke_case_selection_defaults_to_all_cases() {
        let cases = select_agent_smoke_cases(&[]).unwrap();
        assert_eq!(cases.len(), 10);
        assert_eq!(cases[0].name, "agent");
        assert_eq!(cases[1].name, "agent-docs");
        assert_eq!(cases[2].name, "agent-document");
        assert_eq!(cases[3].name, "start");
        assert_eq!(cases[4].name, "settings");
        assert_eq!(cases[5].name, "diagnostics");
        assert_eq!(cases[6].name, "plugins");
        assert_eq!(cases[7].name, "internal-route-trust");
        assert_eq!(cases[7].kind, AgentSmokeCaseKind::InternalRouteTrust);
        assert_eq!(cases[8].name, "launch-unsafe-home");
        assert_eq!(
            cases[8].kind,
            AgentSmokeCaseKind::LaunchSafety(LaunchSafetyScenario::UnsafeHome)
        );
        assert_eq!(cases[9].name, "launch-unknown-internal");
        assert_eq!(
            cases[9].kind,
            AgentSmokeCaseKind::LaunchSafety(LaunchSafetyScenario::UnknownInternalTarget)
        );
    }

    #[test]
    fn smoke_case_selection_filters_by_name() {
        let cases = select_agent_smoke_cases(&["start".to_string()]).unwrap();
        assert_eq!(
            cases,
            vec![AgentSmokeCase {
                name: "start",
                target: "cephas://start",
                output_file: "start.png",
                kind: AgentSmokeCaseKind::Screenshot,
            }]
        );
    }

    #[test]
    fn smoke_case_selection_rejects_unknown_names() {
        let err = select_agent_smoke_cases(&["missing".to_string()]).unwrap_err();
        assert!(err.to_string().contains("unknown smoke case 'missing'"));
        assert!(err.to_string().contains("agent-document"));
        assert!(err.to_string().contains("launch-unknown-internal"));
    }

    #[test]
    fn smoke_report_serializes_suite_and_case_duration() {
        let case = AgentSmokeCaseReport {
            name: "settings".to_string(),
            target: "cephas://settings".to_string(),
            output: "/tmp/cephas-smoke/settings.png".to_string(),
            ok: true,
            bytes: Some(1234),
            duration_ms: 42,
            error: None,
        };
        let report = AgentSmokeReport {
            schema: AGENT_SMOKE_SCHEMA,
            ok: true,
            output_dir: "/tmp/cephas-smoke".to_string(),
            width: 1280,
            height: 900,
            min_bytes: 1024,
            duration_ms: 84,
            cases: vec![case],
        };

        let json = serde_json::to_string(&report).unwrap();

        assert!(json.contains("\"duration_ms\":84"));
        assert!(json.contains("\"duration_ms\":42"));
    }

    #[test]
    fn internal_route_trust_smoke_page_targets_mutating_route() {
        let html = internal_route_trust_html();

        assert!(html.contains("Cephas route trust smoke"));
        assert!(html.contains(INTERNAL_ROUTE_TRUST_ATTEMPT));
        assert!(html.contains("location.href="));
    }

    #[test]
    fn internal_route_trust_report_serializes_schema() {
        let report = InternalRouteTrustSmokeReport {
            schema: INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA,
            ok: true,
            source_url: "http://127.0.0.1:1/".to_string(),
            attempted_route: INTERNAL_ROUTE_TRUST_ATTEMPT,
            profile_path: "/tmp/profile.json".to_string(),
            before_theme: "paper",
            after_theme: "paper",
            server_saw_request: true,
            child_status: "killed-after-timeout".to_string(),
            duration_ms: 77,
            error: None,
        };

        let json = serde_json::to_string(&report).unwrap();

        assert!(json.contains(INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA));
        assert!(json.contains(INTERNAL_ROUTE_TRUST_ATTEMPT));
        assert!(json.contains("\"server_saw_request\":true"));
    }

    #[test]
    fn launch_safety_report_serializes_schema() {
        let report = LaunchSafetySmokeReport {
            schema: LAUNCH_SAFETY_SMOKE_SCHEMA,
            ok: true,
            case: "launch-unknown-internal",
            profile_path: "/tmp/profile.json".to_string(),
            launch_target: Some(UNKNOWN_INTERNAL_LAUNCH_TARGET),
            tampered_home: None,
            loaded_home: Some("https://www.google.com/".to_string()),
            child_status: "killed-after-timeout".to_string(),
            duration_ms: 77,
            error: None,
        };

        let json = serde_json::to_string(&report).unwrap();

        assert!(json.contains(LAUNCH_SAFETY_SMOKE_SCHEMA));
        assert!(json.contains(UNKNOWN_INTERNAL_LAUNCH_TARGET));
        assert!(json.contains("\"case\":\"launch-unknown-internal\""));
    }

    #[test]
    fn launch_safety_tampered_home_loads_as_safe_home() {
        let dir = tempfile::tempdir().unwrap();
        let profile_path = dir.path().join("profile.json");
        write_tampered_profile_home(&profile_path, UNSAFE_HOME_LAUNCH_VALUE).unwrap();
        let store = ProfileStore::new(Some(profile_path)).unwrap();
        let loaded = store.load().unwrap();

        assert_eq!(loaded.home, cephas::Profile::default().home);
        assert!(
            launch_safety_scenario_error(
                LaunchSafetyScenario::UnsafeHome,
                Some(loaded.home.as_str())
            )
            .is_none()
        );
    }

    #[test]
    fn screenshot_report_serializes_duration() {
        let report = AgentScreenshotReport {
            schema: AGENT_SCREENSHOT_SCHEMA.to_string(),
            ok: true,
            target: "cephas://settings".to_string(),
            output: "/tmp/cephas-settings.png".to_string(),
            width: 1280,
            height: 900,
            bytes: Some(1234),
            duration_ms: 77,
            error: None,
        };

        let json = serde_json::to_string(&report).unwrap();

        assert!(json.contains("\"duration_ms\":77"));
    }

    #[test]
    fn smoke_png_validation_checks_header_and_dimensions() {
        let dir = tempfile::tempdir().unwrap();
        let png = dir.path().join("valid.png");
        fs::write(&png, png_header(2560, 1800)).unwrap();

        assert!(validate_smoke_screenshot_artifact(&png, Some(2048), 1024, 1280, 900).is_ok());
        assert_eq!(
            read_png_dimensions(&png).unwrap(),
            PngDimensions {
                width: 2560,
                height: 1800,
            }
        );
        assert_eq!(
            png_device_scale(
                PngDimensions {
                    width: 2560,
                    height: 1800,
                },
                PngDimensions {
                    width: 1280,
                    height: 900,
                },
            ),
            Some(2)
        );
    }

    #[test]
    fn smoke_png_validation_rejects_bad_artifacts() {
        let dir = tempfile::tempdir().unwrap();
        let invalid = dir.path().join("invalid.png");
        let wrong_size = dir.path().join("wrong-size.png");
        fs::write(&invalid, b"not a png with enough bytes to read").unwrap();
        fs::write(&wrong_size, png_header(2560, 900)).unwrap();

        assert!(
            validate_smoke_screenshot_artifact(&invalid, Some(2048), 1024, 1280, 900)
                .unwrap_err()
                .to_string()
                .contains("failed to validate screenshot PNG")
        );
        assert!(
            validate_smoke_screenshot_artifact(&wrong_size, Some(2048), 1024, 1280, 900)
                .unwrap_err()
                .to_string()
                .contains("bounded integer device scale")
        );
        assert!(
            validate_smoke_screenshot_artifact(&wrong_size, Some(10), 1024, 1280, 900)
                .unwrap_err()
                .to_string()
                .contains("suspiciously small screenshot")
        );
    }

    fn png_header(width: u32, height: u32) -> Vec<u8> {
        let mut bytes = Vec::new();
        bytes.extend_from_slice(b"\x89PNG\r\n\x1a\n");
        bytes.extend_from_slice(&13_u32.to_be_bytes());
        bytes.extend_from_slice(b"IHDR");
        bytes.extend_from_slice(&width.to_be_bytes());
        bytes.extend_from_slice(&height.to_be_bytes());
        bytes.extend_from_slice(&[8, 6, 0, 0, 0]);
        bytes.extend_from_slice(&0_u32.to_be_bytes());
        bytes
    }

    #[test]
    fn external_fallback_rejects_internal_and_non_web_targets() {
        let mut profile = cephas::Profile::default();
        assert!(can_launch_external_fallback(
            &profile,
            Some("https://example.com")
        ));
        assert!(can_launch_external_fallback(&profile, Some("search terms")));
        assert!(!can_launch_external_fallback(
            &profile,
            Some("cephas://agent")
        ));
        assert!(!can_launch_external_fallback(
            &profile,
            Some("https://cephas.local/agent")
        ));
        assert!(!can_launch_external_fallback(
            &profile,
            Some("file:///tmp/x")
        ));

        profile.home = url::Url::parse("cephas://start").unwrap();
        assert!(!can_launch_external_fallback(&profile, None));
    }

    #[test]
    fn self_test_report_passes_and_serializes_schema() {
        let report = self_test_report();
        assert_eq!(report.schema, SELF_TEST_SCHEMA);
        assert!(report.ok);
        assert!(report.checks.len() >= 4);
        assert!(report.checks.iter().any(|check| {
            check.id == "launch-route-policy" && check.detail.contains("internal")
        }));
        assert!(report.checks.iter().any(|check| {
            check.id == "profile-resource-clamps" && check.detail.contains("history")
        }));
        assert!(report.checks.iter().any(|check| {
            check.id == "profile-home-migration" && check.detail.contains("https://")
        }));
        assert!(report.checks.iter().any(|check| {
            check.id == "plugin-resource-clamps" && check.detail.contains("plugin")
        }));

        let json = serde_json::to_string(&report).unwrap();
        assert!(json.contains(SELF_TEST_SCHEMA));
        assert!(json.contains("\"ok\":true"));
    }

    #[test]
    fn plugin_registry_report_includes_available_module_sources() {
        let profile = cephas::Profile::default();
        let bundled = cephas::plugins::bundled_plugin_modules();
        let available = cephas::plugins::available_bundled_plugin_modules(&profile.plugins);
        let report = PluginRegistryReport {
            schema: cephas::plugins::PLUGIN_REGISTRY_SCHEMA,
            enabled: profile.plugins.enabled,
            max_plugins: profile.plugins.max_plugins,
            installed_count: profile.plugins.installed_count(),
            enabled_count: profile.plugins.enabled_count(),
            planned_count: profile.plugins.planned_count(),
            bundled_count: bundled.len(),
            available_count: available.len(),
            installed: &profile.plugins.installed,
            bundled: &bundled,
            available: &available,
        };

        let json = serde_json::to_string(&report).unwrap();

        assert!(json.contains("\"available\""));
        assert!(json.contains("\"source_path\""));
        assert!(json.contains("plugins/reader-outline/plugin.json"));
    }

    #[test]
    fn plugin_check_exit_policy_fails_errors_and_strict_warnings() {
        let default_registry = cephas::plugins::PluginRegistry::default();
        let clean = cephas::plugins::check_plugin_registry(&default_registry);
        assert!(!plugin_check_should_fail(&clean, false, false));
        assert!(!plugin_check_should_fail(&clean, true, false));

        let module = cephas::plugins::bundled_plugin_modules()
            .first()
            .unwrap()
            .clone();
        let mut warning_registry = cephas::plugins::PluginRegistry::default();
        warning_registry
            .install_bundled(module.manifest.id)
            .unwrap();
        warning_registry
            .installed
            .iter_mut()
            .find(|plugin| plugin.id == module.manifest.id)
            .unwrap()
            .name = "Drifted module name".to_string();
        let warning = cephas::plugins::check_plugin_registry(&warning_registry);
        assert!(warning.ok);
        assert_eq!(warning.error_count, 0);
        assert!(warning.warning_count > 0);
        assert!(!plugin_check_should_fail(&warning, false, false));
        assert!(plugin_check_should_fail(&warning, true, false));
        assert!(plugin_check_should_fail(&warning, false, true));

        let broken_registry = cephas::plugins::PluginRegistry {
            max_plugins: 0,
            ..Default::default()
        };
        let broken = cephas::plugins::check_plugin_registry(&broken_registry);
        assert!(!broken.ok);
        assert!(plugin_check_should_fail(&broken, false, false));
    }

    #[test]
    fn plugin_check_strict_flags_parse() {
        let strict = Cli::try_parse_from(["cephas", "plugins", "check", "--strict"]).unwrap();
        let fail_on_warning =
            Cli::try_parse_from(["cephas", "plugins", "check", "--fail-on-warning"]).unwrap();

        match strict.command {
            Some(Command::Plugins {
                action:
                    Some(PluginCommand::Check {
                        strict,
                        fail_on_warning,
                    }),
                ..
            }) => {
                assert!(strict);
                assert!(!fail_on_warning);
            }
            other => panic!("unexpected strict command: {other:?}"),
        }
        match fail_on_warning.command {
            Some(Command::Plugins {
                action:
                    Some(PluginCommand::Check {
                        strict,
                        fail_on_warning,
                    }),
                ..
            }) => {
                assert!(!strict);
                assert!(fail_on_warning);
            }
            other => panic!("unexpected fail-on-warning command: {other:?}"),
        }
    }

    #[test]
    fn diagnostics_json_flag_parses() {
        let cli = Cli::try_parse_from(["cephas", "diagnostics", "--json", "--strict"]).unwrap();

        match cli.command {
            Some(Command::Diagnostics { json, strict }) => {
                assert!(json);
                assert!(strict);
            }
            other => panic!("unexpected diagnostics command: {other:?}"),
        }
    }

    #[test]
    fn diagnostics_report_includes_bounded_counts() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let mut profile = cephas::Profile::default();
        let example = url::Url::parse("https://example.com/").unwrap();
        profile.record_visit("Example", example.clone());
        assert!(profile.add_bookmark("Example", example.clone()));
        store.save(&profile).unwrap();

        let mut downloads = cephas::downloads::DownloadManager::default();
        let download_id = downloads
            .start(example.clone(), PathBuf::from("example.bin"), None, None)
            .unwrap();
        assert!(downloads.finish(download_id));
        store.download_store().save(&downloads).unwrap();

        let session = cephas::session::BrowserSession {
            windows: vec![cephas::session::SessionWindow {
                id: cephas::WindowId(1),
                active_tab: Some(cephas::TabId(7)),
                tabs: vec![cephas::session::SessionTab {
                    id: cephas::TabId(7),
                    url: Some(example),
                    title: "Example".to_string(),
                    pinned: false,
                    muted: false,
                    private: false,
                    zoom: 1.0,
                }],
            }],
        };
        store.session_store().save(&session).unwrap();

        let report = diagnostics_report(&profile, &store);

        assert_eq!(report.schema, DIAGNOSTICS_SCHEMA);
        assert!(report.ok);
        assert_eq!(report.profile.history_entries.current, 1);
        assert_eq!(report.profile.bookmarks.current, 1);
        assert_eq!(
            report.plugins.installed.max,
            cephas::plugins::DEFAULT_MAX_PLUGINS as u64
        );
        let session = report.session.as_ref().unwrap();
        assert_eq!(session.windows.current, 1);
        assert_eq!(session.tabs.current, 1);
        assert_eq!(session.tabs_per_window.current, 1);
        let downloads = report.downloads.as_ref().unwrap();
        assert_eq!(downloads.records.current, 1);
        assert_eq!(downloads.completed, 1);
        assert!(
            report
                .resource_caps
                .iter()
                .any(|cap| cap.id == "plugin-records")
        );
        let history_check = report.resource_checks.get("history-entries").unwrap();
        assert_eq!(history_check.current, 1);
        assert!(history_check.ok);
        let tabs_check = report
            .resource_checks
            .get("session-tabs-per-window")
            .unwrap();
        assert_eq!(tabs_check.current, 1);
        assert_eq!(tabs_check.max, 100);
        let downloads_check = report.resource_checks.get("download-records").unwrap();
        assert_eq!(downloads_check.current, 1);
        assert!(downloads_check.ok);
        assert!(
            report
                .unobserved_resource_caps
                .iter()
                .any(|cap| cap.id == "open-tabs" && cap.reason == "live-runtime-only")
        );
        assert!(
            report
                .unobserved_resource_caps
                .iter()
                .any(|cap| cap.id == "page-body-bytes" && cap.reason == "per-operation-limit")
        );
        for cap in &report.resource_caps {
            assert!(
                report.resource_checks.contains_key(cap.id)
                    || report
                        .unobserved_resource_caps
                        .iter()
                        .any(|unobserved| unobserved.id == cap.id),
                "cap {} is neither checked nor marked unobserved",
                cap.id
            );
        }

        let json = serde_json::to_string(&report).unwrap();
        assert!(json.contains(DIAGNOSTICS_SCHEMA));
        assert!(json.contains("\"resource_caps\""));
        assert!(json.contains("\"resource_checks\""));
        assert!(json.contains("\"unobserved_resource_caps\""));
        assert!(json.contains("\"history-entries\""));
    }

    #[test]
    fn diagnostics_report_flags_resource_pressure() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let url = url::Url::parse("https://example.com/").unwrap();
        let mut profile = cephas::Profile {
            max_history: 1,
            ..cephas::Profile::default()
        };
        profile.history = vec![
            cephas::HistoryEntry {
                title: "One".to_string(),
                url: url.clone(),
                visited_at: chrono::Utc::now(),
            },
            cephas::HistoryEntry {
                title: "Two".to_string(),
                url,
                visited_at: chrono::Utc::now(),
            },
        ];

        let report = diagnostics_report(&profile, &store);
        let history_check = report.resource_checks.get("history-entries").unwrap();

        assert!(!report.ok);
        assert_eq!(history_check.current, 2);
        assert_eq!(history_check.max, 1);
        assert!(!history_check.ok);
        assert!(diagnostics_should_fail(&report, true));
        assert!(!diagnostics_should_fail(&report, false));
    }

    #[test]
    fn tooling_manifest_exposes_portable_agent_surfaces() {
        let manifest = agent_tooling_manifest();
        assert_eq!(manifest.schema, AGENT_TOOLING_SCHEMA);
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "agent-smoke-json")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "agent-commands-jsonl")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "mcp-stdio")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "diagnostics-json")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "diagnostics-strict")
        );
        assert!(manifest.commands.iter().any(|command| {
            command.id == "self-test-json" && command.output == SELF_TEST_SCHEMA
        }));
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-jsonl")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-available")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-check")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-check-strict")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-install")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-install-all")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-sync-all")
        );
        assert!(
            manifest
                .commands
                .iter()
                .any(|command| command.id == "plugins-sync")
        );
        assert!(
            manifest
                .browser_commands
                .iter()
                .any(|command| command.id == "human-click")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "cephas://agent")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "cephas://agent/docs")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "cephas://start/theme")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "https://cephas.local/start")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "cephas://plugins")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "cephas://diagnostics")
        );
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url == "https://cephas.local/settings")
        );
        assert!(manifest.internal_urls.iter().any(|url| {
            url.url == "cephas://settings/theme-preset?id=<preset-id>"
                && url.description.contains("bounded")
        }));
        assert!(
            manifest
                .notes
                .iter()
                .any(|note| note.contains("bounded custom theme presets"))
        );
        assert!(
            manifest
                .smoke_cases
                .iter()
                .any(|case| case.name == "agent-docs")
        );
        assert!(
            manifest
                .smoke_cases
                .iter()
                .any(|case| case.name == "plugins" && case.output_file == "plugins.png")
        );
        assert!(
            manifest
                .smoke_cases
                .iter()
                .any(|case| case.name == "diagnostics" && case.output_file == "diagnostics.png")
        );
        assert!(
            manifest
                .smoke_cases
                .iter()
                .any(|case| case.name == "settings" && case.output_file == "settings.png")
        );
        assert!(manifest.smoke_cases.iter().any(|case| {
            case.name == "internal-route-trust"
                && case.output_file == "internal-route-trust.json"
                && case.target.contains("cephas://settings/theme")
        }));
        assert!(manifest.smoke_cases.iter().any(|case| {
            case.name == "launch-unsafe-home" && case.output_file == "launch-unsafe-home.json"
        }));
        assert!(manifest.smoke_cases.iter().any(|case| {
            case.name == "launch-unknown-internal"
                && case.output_file == "launch-unknown-internal.json"
                && case.target.contains("cephas://unknown")
        }));
        assert!(
            manifest
                .internal_urls
                .iter()
                .any(|url| url.url.starts_with("cephas://agent/emulate"))
        );
        assert!(
            manifest
                .human_control_routes
                .iter()
                .any(|route| route.route.starts_with("cephas://agent/human/click"))
        );
        assert_eq!(
            manifest.reader_tools.len(),
            cephas::reader::AgentReaderTool::ALL.len()
        );
        assert!(manifest.resource_caps.len() >= 20);
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "open-tabs" && cap.value == cephas::gui::MAX_OPEN_TABS as u64
        }));
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "agent-audit-log-bytes"
                && cap.value == cephas::agents::DEFAULT_MAX_AGENT_LOG_BYTES
                && cap.unit == "bytes"
        }));
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "agent-audit-log-archive-bytes"
                && cap.value
                    == cephas::agents::DEFAULT_MAX_AGENT_LOG_BYTES
                        * cephas::agents::DEFAULT_MAX_AGENT_LOG_ARCHIVES as u64
                && cap.unit == "bytes"
        }));
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "agent-audit-log-largest-archive-bytes"
                && cap.value == cephas::agents::DEFAULT_MAX_AGENT_LOG_BYTES
                && cap.unit == "bytes"
        }));
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "save-queue-jobs" && cap.value == cephas::gui::SAVE_QUEUE_CAPACITY as u64
        }));
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "pending-human-waits"
                && cap.value == cephas::gui::MAX_PENDING_HUMAN_WAITS as u64
        }));
        assert!(manifest.resource_caps.iter().any(|cap| {
            cap.id == "page-body-bytes"
                && cap.value == cephas::browser::DEFAULT_MAX_PAGE_BODY_BYTES as u64
        }));
        assert!(manifest.diagnostics_surfaces.iter().any(|surface| {
            surface.id == "diagnostics-json"
                && surface.output == DIAGNOSTICS_SCHEMA
                && surface.scope.contains("persisted/offline")
        }));
        assert!(manifest.diagnostics_surfaces.iter().any(|surface| {
            surface.id == "diagnostics-live-page"
                && surface
                    .access
                    .iter()
                    .any(|access| access.contains("agent-screenshot"))
                && surface.description.contains("process-local")
        }));
        assert!(
            manifest
                .contracts
                .iter()
                .any(|contract| contract.path == "elixir_backend.MD")
        );
        assert!(
            manifest
                .contracts
                .iter()
                .any(|contract| contract.path == "plugins_backend.MD")
        );
        assert!(
            manifest
                .contracts
                .iter()
                .any(|contract| contract.path == "CAPTURE_OPTIMIZATION.md")
        );
        assert!(
            manifest
                .notes
                .iter()
                .any(|note| note.contains("bounded resource caps"))
        );
        assert!(
            manifest
                .notes
                .iter()
                .any(|note| note.contains("live GTK runtime counters"))
        );
        assert!(
            manifest
                .notes
                .iter()
                .any(|note| note.contains("AI Inspect"))
        );
        assert!(
            manifest
                .notes
                .iter()
                .any(|note| note.contains("PDF targets"))
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == DIAGNOSTICS_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == SELF_TEST_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == INTERNAL_ROUTE_TRUST_SMOKE_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == LAUNCH_SAFETY_SMOKE_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == cephas::plugins::PLUGIN_REGISTRY_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == cephas::plugins::PLUGIN_MODULE_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == cephas::plugins::PLUGIN_CHECK_SCHEMA)
        );
        assert!(
            manifest
                .schemas
                .iter()
                .any(|schema| schema.schema == cephas::plugins::PLUGIN_BACKEND_REGISTRY_SCHEMA)
        );
    }

    #[test]
    fn mcp_initialize_returns_server_capabilities() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let response = mcp_handle_message(
            &cephas::Profile::default(),
            &store,
            json!({
                "jsonrpc": "2.0",
                "id": 1,
                "method": "initialize",
                "params": {}
            }),
        )
        .unwrap();

        assert_eq!(response["jsonrpc"], "2.0");
        assert_eq!(response["id"], 1);
        assert_eq!(response["result"]["protocolVersion"], MCP_PROTOCOL_VERSION);
        assert_eq!(response["result"]["serverInfo"]["name"], "cephas");
        assert!(response["result"]["capabilities"]["tools"].is_object());
    }

    #[test]
    fn mcp_tools_list_exposes_read_only_tools() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let response = mcp_handle_message(
            &cephas::Profile::default(),
            &store,
            json!({
                "jsonrpc": "2.0",
                "id": "tools",
                "method": "tools/list",
                "params": {}
            }),
        )
        .unwrap();
        let tools = response["result"]["tools"].as_array().unwrap();

        for name in [
            "cephas_agent_tooling",
            "cephas_agent_commands",
            "cephas_diagnostics",
            "cephas_plugins",
        ] {
            assert!(tools.iter().any(|tool| tool["name"] == name));
        }
        assert!(tools.iter().all(|tool| {
            tool["inputSchema"]["additionalProperties"] == false
                && tool["inputSchema"]["properties"]
                    .as_object()
                    .unwrap()
                    .is_empty()
        }));
    }

    #[test]
    fn mcp_tools_call_returns_agent_tooling_text() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let response = mcp_handle_message(
            &cephas::Profile::default(),
            &store,
            json!({
                "jsonrpc": "2.0",
                "id": 2,
                "method": "tools/call",
                "params": {
                    "name": "cephas_agent_tooling",
                    "arguments": {}
                }
            }),
        )
        .unwrap();

        assert_eq!(response["id"], 2);
        assert_eq!(response["result"]["isError"], false);
        let text = response["result"]["content"][0]["text"].as_str().unwrap();
        assert!(text.contains(AGENT_TOOLING_SCHEMA));
        assert!(text.contains("mcp-stdio"));
    }

    #[test]
    fn mcp_unknown_method_and_tool_return_json_rpc_errors() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let profile = cephas::Profile::default();
        let method_response = mcp_handle_message(
            &profile,
            &store,
            json!({
                "jsonrpc": "2.0",
                "id": 3,
                "method": "missing/method"
            }),
        )
        .unwrap();
        let tool_response = mcp_handle_message(
            &profile,
            &store,
            json!({
                "jsonrpc": "2.0",
                "id": 4,
                "method": "tools/call",
                "params": { "name": "missing_tool" }
            }),
        )
        .unwrap();

        assert_eq!(method_response["error"]["code"], -32601);
        assert!(
            method_response["error"]["message"]
                .as_str()
                .unwrap()
                .contains("unknown method")
        );
        assert_eq!(tool_response["error"]["code"], -32602);
        assert!(
            tool_response["error"]["message"]
                .as_str()
                .unwrap()
                .contains("unknown MCP tool")
        );
    }

    #[test]
    fn mcp_rejects_oversized_batches() {
        let dir = tempfile::tempdir().unwrap();
        let store = ProfileStore::new(Some(dir.path().join("profile.json"))).unwrap();
        let response = mcp_handle_message(
            &cephas::Profile::default(),
            &store,
            Value::Array(
                (0..=MCP_MAX_BATCH_REQUESTS)
                    .map(|id| {
                        json!({
                            "jsonrpc": "2.0",
                            "id": id,
                            "method": "ping"
                        })
                    })
                    .collect(),
            ),
        )
        .unwrap();

        assert_eq!(response["error"]["code"], -32600);
        assert!(
            response["error"]["message"]
                .as_str()
                .unwrap()
                .contains("batch request exceeds")
        );
    }

    #[test]
    fn bounded_line_reader_reports_overflow_and_drains_line() {
        let mut input = io::Cursor::new(b"abcdef\n{}\n".to_vec());

        assert!(read_bounded_line(&mut input, 3).unwrap().unwrap().is_err());
        assert_eq!(
            read_bounded_line(&mut input, 3).unwrap().unwrap().unwrap(),
            "{}\n"
        );
        assert!(read_bounded_line(&mut input, 3).unwrap().is_none());
    }
}
