// SPDX-License-Identifier: MIT
#![cfg_attr(all(windows, not(test)), windows_subsystem = "windows")]

use fs2::FileExt;
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet};
use std::env;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::net::{SocketAddr, TcpStream};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Mutex;
use std::time::Duration;
use tauri::menu::MenuBuilder;
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
use tauri::{Manager, Runtime, State};

const MAIN_WINDOW_LABEL: &str = "main";
const TRAY_ID: &str = "cortex-tray";
const TRAY_SHOW_ID: &str = "tray_show";
const TRAY_HIDE_ID: &str = "tray_hide";
const TRAY_QUIT_ID: &str = "tray_quit";
const DEV_DAEMON_TARGET_DIR: &str = "target-control-center-dev";
const RELEASE_DAEMON_TARGET_DIR: &str = "target-control-center-release";
const DEFAULT_DAEMON_PORT: u16 = 7437;
const DAEMON_REACHABILITY_TIMEOUT_MS: u64 = 400;
const DAEMON_CONNECT_TIMEOUT_MS: u64 = 1_200;
const DAEMON_READ_TIMEOUT_MS: u64 = 10_000;
const DAEMON_WRITE_TIMEOUT_MS: u64 = 3_000;
const DAEMON_MIN_REQUEST_TIMEOUT_MS: u64 = 500;
const DAEMON_MAX_REQUEST_TIMEOUT_MS: u64 = 120_000;
const DAEMON_STOP_WAIT_MS: u64 = 3_000;
const DAEMON_WAIT_POLL_MS: u64 = 200;
const SERVICE_ENSURE_WAIT_MS: u64 = 12_000;
const LOCAL_DAEMON_LOCK_WAIT_SECS: u64 = 15;
const LOCAL_DAEMON_START_WAIT_MS: u64 = (LOCAL_DAEMON_LOCK_WAIT_SECS * 1_000) + 2_000;
const AUTH_TOKEN_WAIT_MS: u64 = 1_500;
const AUTH_TOKEN_POLL_MS: u64 = 100;
const SUPERVISOR_TICK_MS: u64 = 3_000;
const CONTROL_CENTER_LOCK_FILE: &str = "control-center.lock";
const CONTROL_CENTER_OWNER_TAG: &str = "control-center";
const PATH_BINARY_FALLBACK_ENV: &str = "CORTEX_ALLOW_PATH_BINARY_FALLBACK";
const SERVICE_ENSURE_FALLBACK_ENV: &str = "CORTEX_ALLOW_SERVICE_ENSURE_FALLBACK";
const BUDGETS_FILE_NAME: &str = "budgets.toml";
const BUDGET_ENDPOINT_NAMES: [&str; 4] = ["store", "recall", "boot", "mcp"];
const MAX_BUDGET_INTEGER: u64 = i64::MAX as u64;
#[cfg(windows)]
const CREATE_NO_WINDOW_FLAG: u32 = 0x0800_0000;

#[cfg(windows)]
fn apply_hidden_process_flags(command: &mut Command) {
    use std::os::windows::process::CommandExt;

    command.creation_flags(CREATE_NO_WINDOW_FLAG);
}

#[cfg(not(windows))]
fn apply_hidden_process_flags(_command: &mut Command) {}

#[cfg(windows)]
fn apply_hidden_daemon_process_flags(command: &mut Command) {
    use std::os::windows::process::CommandExt;

    command.creation_flags(CREATE_NO_WINDOW_FLAG);
}

#[cfg(not(windows))]
fn apply_hidden_daemon_process_flags(_command: &mut Command) {}

struct DaemonState {
    exe_path: Option<PathBuf>,
    child: Mutex<Option<Child>>,
    /// Set true by `stop()`, cleared by `ensure_local_daemon()`. Watchdog reads
    /// this to decide whether a missing daemon should be auto-respawned. Lets
    /// the user explicitly stop the daemon without immediate revival.
    intentional_stop: AtomicBool,
}

impl DaemonState {
    fn new(exe_path: Option<PathBuf>) -> Self {
        Self {
            exe_path,
            child: Mutex::new(None),
            intentional_stop: AtomicBool::new(false),
        }
    }

    fn supervisor_paused(&self) -> bool {
        self.intentional_stop.load(Ordering::SeqCst)
    }

    fn status(&self) -> Result<(bool, Option<u32>), String> {
        let mut child = self
            .child
            .lock()
            .map_err(|_| "Failed to lock managed daemon state.".to_string())?;
        let Some(managed_child) = child.as_mut() else {
            return Ok((false, None));
        };

        match managed_child.try_wait() {
            Ok(Some(_)) => {
                *child = None;
                Ok((false, None))
            }
            Ok(None) => Ok((true, Some(managed_child.id()))),
            Err(err) => {
                eprintln!(
                    "[cortex-control-center] failed to poll managed daemon process; clearing stale handle: {err}"
                );
                *child = None;
                Ok((false, None))
            }
        }
    }

    fn ensure_local_daemon(&self) -> Result<Option<u32>, String> {
        let mut child = self
            .child
            .lock()
            .map_err(|_| "Failed to lock managed daemon state.".to_string())?;
        if let Some(existing) = child.as_mut() {
            match existing.try_wait() {
                Ok(Some(_)) => {
                    *child = None;
                }
                Ok(None) => {
                    return Ok(Some(existing.id()));
                }
                Err(err) => {
                    eprintln!(
                        "[cortex-control-center] failed to poll existing managed daemon before spawn; clearing stale handle: {err}"
                    );
                    *child = None;
                }
            }
        }

        let exe_path = self.exe_path.clone().ok_or_else(|| {
            "Could not resolve Cortex daemon binary for app-managed local mode.".to_string()
        })?;
        if is_disallowed_daemon_binary_path(&exe_path) {
            return Err(format!(
                "Refusing to launch app-managed daemon from disallowed path: {}",
                exe_path.display()
            ));
        }

        let paths = resolved_cortex_paths();
        let home = paths.home.clone().ok_or_else(|| {
            "Could not resolve Cortex home path for app-managed local mode.".to_string()
        })?;
        let db = paths.db.clone().ok_or_else(|| {
            "Could not resolve Cortex database path for app-managed local mode.".to_string()
        })?;
        // App-managed mode is intentionally local-only. We always bind to loopback
        // so Control Center can own daemon lifecycle without exposing it on LAN.
        let bind = "127.0.0.1".to_string();
        let port = paths.port.unwrap_or(DEFAULT_DAEMON_PORT);

        let mut command = Command::new(&exe_path);
        command
            .arg("serve")
            .arg("--home")
            .arg(home.display().to_string())
            .arg("--db")
            .arg(db.display().to_string())
            .arg("--port")
            .arg(port.to_string())
            .arg("--bind")
            .arg(bind)
            .env("CORTEX_DAEMON_OWNER", CONTROL_CENTER_OWNER_TAG)
            .env("CORTEX_DAEMON_OWNER_SOURCE", "control-center-app")
            .env("CORTEX_DAEMON_OWNER_MODE", "app-managed-local")
            .env("CORTEX_WAIT_FOR_DAEMON_LOCK", "1")
            .env(
                "CORTEX_DAEMON_LOCK_WAIT_SECS",
                LOCAL_DAEMON_LOCK_WAIT_SECS.to_string(),
            )
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::null());
        apply_hidden_daemon_process_flags(&mut command);

        let spawned = command.spawn().map_err(|err| {
            format!(
                "Failed to spawn app-managed daemon from {}: {err}",
                exe_path.display()
            )
        })?;
        let pid = spawned.id();
        *child = Some(spawned);
        // A successful spawn implicitly arms the supervisor: any later death
        // should trigger an auto-respawn until the user explicitly stops.
        self.intentional_stop.store(false, Ordering::SeqCst);
        Ok(Some(pid))
    }

    fn stop(&self) -> Result<(), String> {
        // Pause the supervisor BEFORE killing the child so the watchdog does
        // not race in and spawn a new instance during teardown.
        self.intentional_stop.store(true, Ordering::SeqCst);
        let mut child = self
            .child
            .lock()
            .map_err(|_| "Failed to lock managed daemon state.".to_string())?;
        if let Some(managed_child) = child.as_mut() {
            match managed_child.try_wait() {
                Ok(Some(_)) => {
                    *child = None;
                }
                Ok(None) => {
                    if let Err(err) = managed_child.kill() {
                        *child = None;
                        return Err(format!("Failed to stop managed daemon process: {err}"));
                    }
                    let _ = managed_child.wait();
                    *child = None;
                }
                Err(err) => {
                    eprintln!(
                        "[cortex-control-center] failed to poll managed daemon process during stop; clearing stale handle: {err}"
                    );
                    *child = None;
                }
            }
        }
        Ok(())
    }
}

struct LifecycleState {
    explicit_quit: AtomicBool,
}

struct AppInstanceGuard {
    lock_file: File,
}

#[derive(Clone, Copy)]
struct RequestTimeouts {
    connect: Duration,
    read: Duration,
    write: Duration,
}

impl Default for LifecycleState {
    fn default() -> Self {
        Self {
            explicit_quit: AtomicBool::new(false),
        }
    }
}

impl LifecycleState {
    fn request_quit(&self) {
        self.explicit_quit.store(true, Ordering::SeqCst);
    }

    fn is_quit_requested(&self) -> bool {
        self.explicit_quit.load(Ordering::SeqCst)
    }
}

impl AppInstanceGuard {
    fn acquire() -> Result<Option<Self>, String> {
        let lock_path = control_center_lock_path()?;
        if let Some(parent) = lock_path.parent() {
            fs::create_dir_all(parent)
                .map_err(|err| format!("Failed to create {}: {err}", parent.display()))?;
        }
        let mut lock_file = OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .truncate(false)
            .open(&lock_path)
            .map_err(|err| format!("Failed to open {}: {err}", lock_path.display()))?;
        match lock_file.try_lock_exclusive() {
            Ok(()) => {
                let _ = lock_file.set_len(0);
                let _ = writeln!(lock_file, "pid={}", std::process::id());
                Ok(Some(Self { lock_file }))
            }
            Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
            Err(err) => Err(format!("Failed to lock {}: {err}", lock_path.display())),
        }
    }
}

impl Drop for AppInstanceGuard {
    fn drop(&mut self) {
        let _ = self.lock_file.unlock();
    }
}

fn control_center_lock_path() -> Result<PathBuf, String> {
    Ok(default_cortex_dir()?
        .join("runtime")
        .join(CONTROL_CENTER_LOCK_FILE))
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DaemonCommandResult {
    running: bool,
    reachable: bool,
    managed: bool,
    auth_token_ready: bool,
    pid: Option<u32>,
    message: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BudgetConfigDraft {
    enabled: bool,
    endpoints: Vec<BudgetEndpointDraft>,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BudgetEndpointDraft {
    endpoint: String,
    enabled: bool,
    limit: Option<u64>,
    window_seconds: Option<u64>,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BudgetConfigSnapshot {
    config_loaded: bool,
    enabled: bool,
    source: String,
    error: Option<BudgetConfigErrorSnapshot>,
    endpoints: BTreeMap<String, BudgetEndpointSnapshot>,
    recent_denials: usize,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BudgetEndpointSnapshot {
    limit: u64,
    window_seconds: u64,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BudgetConfigErrorSnapshot {
    code: String,
    message: String,
    endpoint: Option<String>,
    field: Option<String>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawBudgetFile {
    defaults: Option<RawBudgetDefaults>,
    endpoints: Option<BTreeMap<String, RawBudgetEndpoint>>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawBudgetDefaults {
    enabled: Option<bool>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawBudgetEndpoint {
    limit: Option<i64>,
    window_seconds: Option<i64>,
}

#[derive(Debug, Serialize)]
struct BudgetTomlFile {
    defaults: BudgetTomlDefaults,
    endpoints: BTreeMap<String, BudgetTomlEndpoint>,
}

#[derive(Debug, Serialize)]
struct BudgetTomlDefaults {
    enabled: bool,
}

#[derive(Debug, Serialize)]
struct BudgetTomlEndpoint {
    limit: u64,
    window_seconds: u64,
}

fn cortex_home() -> Result<PathBuf, String> {
    env::var_os("USERPROFILE")
        .map(PathBuf::from)
        .or_else(|| env::var_os("HOME").map(PathBuf::from))
        .or_else(dirs::home_dir)
        .ok_or_else(|| "Could not resolve home directory".to_string())
}

#[derive(Clone, Debug, Default)]
struct ResolvedCortexPaths {
    home: Option<PathBuf>,
    token: Option<PathBuf>,
    db: Option<PathBuf>,
    pid: Option<PathBuf>,
    port: Option<u16>,
    #[allow(dead_code)]
    bind: Option<String>,
}

fn default_cortex_dir() -> Result<PathBuf, String> {
    Ok(cortex_home()?.join(".cortex"))
}

fn budget_config_path() -> Result<PathBuf, String> {
    let home = resolved_cortex_paths()
        .home
        .or_else(|| default_cortex_dir().ok())
        .ok_or_else(|| "Could not resolve Cortex home path".to_string())?;
    fs::create_dir_all(&home)
        .map_err(|err| format!("Failed to create Cortex home {}: {err}", home.display()))?;
    let canonical_home = fs::canonicalize(&home)
        .map_err(|err| format!("Failed to resolve Cortex home {}: {err}", home.display()))?;
    Ok(canonical_home.join(BUDGETS_FILE_NAME))
}

fn budget_error(
    code: &str,
    message: impl Into<String>,
    endpoint: Option<String>,
    field: Option<&str>,
) -> BudgetConfigErrorSnapshot {
    BudgetConfigErrorSnapshot {
        code: code.to_string(),
        message: message.into(),
        endpoint,
        field: field.map(str::to_string),
    }
}

fn parse_budget_endpoint_name(name: &str) -> Option<String> {
    let normalized = name.trim().to_ascii_lowercase();
    BUDGET_ENDPOINT_NAMES
        .contains(&normalized.as_str())
        .then_some(normalized)
}

fn budget_source_label() -> String {
    BUDGETS_FILE_NAME.to_string()
}

fn empty_budget_snapshot(_path: &Path) -> BudgetConfigSnapshot {
    BudgetConfigSnapshot {
        config_loaded: false,
        enabled: false,
        source: budget_source_label(),
        error: None,
        endpoints: BTreeMap::new(),
        recent_denials: 0,
    }
}

fn budget_snapshot_from_contents(_path: &Path, contents: &str) -> BudgetConfigSnapshot {
    let parsed = match toml::from_str::<RawBudgetFile>(contents) {
        Ok(parsed) => parsed,
        Err(err) => {
            return BudgetConfigSnapshot {
                config_loaded: true,
                enabled: false,
                source: budget_source_label(),
                error: Some(budget_error(
                    "parse_error",
                    format!("failed to parse budgets.toml: {err}"),
                    None,
                    None,
                )),
                endpoints: BTreeMap::new(),
                recent_denials: 0,
            };
        }
    };

    let enabled = parsed
        .defaults
        .and_then(|defaults| defaults.enabled)
        .unwrap_or(true);
    let mut endpoints = BTreeMap::new();
    for (raw_name, raw_budget) in parsed.endpoints.unwrap_or_default() {
        let Some(endpoint) = parse_budget_endpoint_name(&raw_name) else {
            return BudgetConfigSnapshot {
                config_loaded: true,
                enabled: false,
                source: budget_source_label(),
                error: Some(budget_error(
                    "unknown_endpoint",
                    format!("unknown budget endpoint: {raw_name}"),
                    Some(raw_name),
                    None,
                )),
                endpoints: BTreeMap::new(),
                recent_denials: 0,
            };
        };
        let Some(limit) = raw_budget.limit else {
            return BudgetConfigSnapshot {
                config_loaded: true,
                enabled: false,
                source: budget_source_label(),
                error: Some(budget_error(
                    "missing_limit",
                    format!("budget endpoint {endpoint} is missing limit"),
                    Some(endpoint),
                    Some("limit"),
                )),
                endpoints: BTreeMap::new(),
                recent_denials: 0,
            };
        };
        if limit <= 0 {
            return BudgetConfigSnapshot {
                config_loaded: true,
                enabled: false,
                source: budget_source_label(),
                error: Some(budget_error(
                    "invalid_limit",
                    format!("budget endpoint {endpoint} limit must be a positive integer"),
                    Some(endpoint),
                    Some("limit"),
                )),
                endpoints: BTreeMap::new(),
                recent_denials: 0,
            };
        }

        let Some(window_seconds) = raw_budget.window_seconds else {
            return BudgetConfigSnapshot {
                config_loaded: true,
                enabled: false,
                source: budget_source_label(),
                error: Some(budget_error(
                    "missing_window_seconds",
                    format!("budget endpoint {endpoint} is missing window_seconds"),
                    Some(endpoint),
                    Some("window_seconds"),
                )),
                endpoints: BTreeMap::new(),
                recent_denials: 0,
            };
        };
        if window_seconds <= 0 {
            return BudgetConfigSnapshot {
                config_loaded: true,
                enabled: false,
                source: budget_source_label(),
                error: Some(budget_error(
                    "invalid_window_seconds",
                    format!("budget endpoint {endpoint} window_seconds must be a positive integer"),
                    Some(endpoint),
                    Some("window_seconds"),
                )),
                endpoints: BTreeMap::new(),
                recent_denials: 0,
            };
        }

        endpoints.insert(
            endpoint,
            BudgetEndpointSnapshot {
                limit: limit as u64,
                window_seconds: window_seconds as u64,
            },
        );
    }

    BudgetConfigSnapshot {
        config_loaded: true,
        enabled,
        source: budget_source_label(),
        error: None,
        endpoints,
        recent_denials: 0,
    }
}

fn read_budget_config_snapshot(path: &Path) -> Result<BudgetConfigSnapshot, String> {
    match fs::read_to_string(path) {
        Ok(contents) => Ok(budget_snapshot_from_contents(path, &contents)),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(empty_budget_snapshot(path)),
        Err(err) => Ok(BudgetConfigSnapshot {
            config_loaded: true,
            enabled: false,
            source: budget_source_label(),
            error: Some(budget_error(
                "io_error",
                format!("failed to read budgets.toml: {err}"),
                None,
                None,
            )),
            endpoints: BTreeMap::new(),
            recent_denials: 0,
        }),
    }
}

fn validate_budget_draft(draft: BudgetConfigDraft) -> Result<BudgetTomlFile, String> {
    let mut endpoints = BTreeMap::new();
    for raw in draft.endpoints {
        if !raw.enabled {
            continue;
        }
        let endpoint = parse_budget_endpoint_name(&raw.endpoint)
            .ok_or_else(|| format!("Unknown budget endpoint: {}", raw.endpoint))?;
        if endpoints.contains_key(&endpoint) {
            return Err(format!("Duplicate budget endpoint: {endpoint}"));
        }

        let limit = raw
            .limit
            .ok_or_else(|| format!("Budget endpoint {endpoint} is missing limit"))?;
        if limit == 0 || limit > MAX_BUDGET_INTEGER {
            return Err(format!(
                "Budget endpoint {endpoint} limit must be between 1 and {MAX_BUDGET_INTEGER}"
            ));
        }

        let window_seconds = raw
            .window_seconds
            .ok_or_else(|| format!("Budget endpoint {endpoint} is missing window_seconds"))?;
        if window_seconds == 0 || window_seconds > MAX_BUDGET_INTEGER {
            return Err(format!(
                "Budget endpoint {endpoint} window_seconds must be between 1 and {MAX_BUDGET_INTEGER}"
            ));
        }

        endpoints.insert(
            endpoint,
            BudgetTomlEndpoint {
                limit,
                window_seconds,
            },
        );
    }

    Ok(BudgetTomlFile {
        defaults: BudgetTomlDefaults {
            enabled: draft.enabled,
        },
        endpoints,
    })
}

fn write_budget_config_file(path: &Path, contents: &str) -> Result<(), String> {
    let parent = path
        .parent()
        .ok_or_else(|| format!("Invalid budget config path: {}", path.display()))?;
    fs::create_dir_all(parent)
        .map_err(|err| format!("Failed to create {}: {err}", parent.display()))?;
    let temp_path = parent.join(format!(".{}.{}.tmp", BUDGETS_FILE_NAME, std::process::id()));
    {
        let mut file = File::create(&temp_path)
            .map_err(|err| format!("Failed to create {}: {err}", temp_path.display()))?;
        file.write_all(contents.as_bytes())
            .map_err(|err| format!("Failed to write {}: {err}", temp_path.display()))?;
        file.sync_all()
            .map_err(|err| format!("Failed to flush {}: {err}", temp_path.display()))?;
    }

    if path.exists() {
        fs::remove_file(path)
            .map_err(|err| format!("Failed to replace {}: {err}", path.display()))?;
    }
    fs::rename(&temp_path, path).map_err(|err| {
        let _ = fs::remove_file(&temp_path);
        format!("Failed to save {}: {err}", path.display())
    })?;
    Ok(())
}

fn token_path() -> Result<PathBuf, String> {
    resolved_cortex_paths()
        .token
        .ok_or_else(|| "Could not resolve Cortex token path".to_string())
}

fn cortex_db_path() -> Result<PathBuf, String> {
    resolved_cortex_paths()
        .db
        .ok_or_else(|| "Could not resolve Cortex database path".to_string())
}

fn daemon_port() -> u16 {
    resolve_daemon_port()
}

#[derive(Clone, Copy, Debug, Default)]
struct CortexReachabilityProbe {
    reachable: bool,
    starting: bool,
    identity_mismatch: bool,
}

fn readiness_state_with_identity_fallback(
    status: u16,
    body: &str,
    expected_port: Option<u16>,
    expected_paths: Option<&ResolvedCortexPaths>,
) -> (Option<bool>, bool) {
    if let Some(ready) = cortex_readiness_state(status, body, expected_port, expected_paths) {
        return (Some(ready), false);
    }
    if expected_paths.is_some() {
        if let Some(ready) = cortex_readiness_state(status, body, expected_port, None) {
            return (Some(ready), true);
        }
    }
    (None, false)
}

fn health_state_with_identity_fallback(
    status: u16,
    body: &str,
    expected_port: Option<u16>,
    expected_paths: Option<&ResolvedCortexPaths>,
) -> (bool, bool) {
    if is_cortex_health_response(status, body, expected_port, expected_paths) {
        return (true, false);
    }
    if expected_paths.is_some() && is_cortex_health_response(status, body, expected_port, None) {
        return (true, true);
    }
    (false, false)
}

fn probe_cortex_reachability_with_port(port: u16, timeout_ms: u64) -> CortexReachabilityProbe {
    let expected_paths = resolved_cortex_paths();
    let readiness_response = send_cortex_request_with_port(
        port,
        "GET",
        "/readiness",
        "",
        None,
        RequestTimeouts {
            connect: Duration::from_millis(timeout_ms),
            read: Duration::from_millis(timeout_ms),
            write: Duration::from_millis(timeout_ms),
        },
    );

    if let Ok(resp) = readiness_response {
        let (readiness_state, identity_mismatch) = readiness_state_with_identity_fallback(
            resp.status,
            &resp.body,
            Some(port),
            Some(&expected_paths),
        );
        if let Some(ready) = readiness_state {
            return CortexReachabilityProbe {
                reachable: ready,
                starting: !ready,
                identity_mismatch,
            };
        }
    }

    let health_response = send_cortex_request_with_port(
        port,
        "GET",
        "/health",
        "",
        None,
        RequestTimeouts {
            connect: Duration::from_millis(timeout_ms),
            read: Duration::from_millis(timeout_ms),
            write: Duration::from_millis(timeout_ms),
        },
    );

    if let Ok(resp) = health_response {
        let (healthy, identity_mismatch) = health_state_with_identity_fallback(
            resp.status,
            &resp.body,
            Some(port),
            Some(&expected_paths),
        );
        if healthy {
            return CortexReachabilityProbe {
                reachable: true,
                starting: false,
                identity_mismatch,
            };
        }
    }

    CortexReachabilityProbe::default()
}

fn is_cortex_reachable_with_port(port: u16, timeout_ms: u64) -> bool {
    probe_cortex_reachability_with_port(port, timeout_ms).reachable
}

async fn wait_for_reachability(port: u16, target: bool, timeout: Duration) -> bool {
    tauri::async_runtime::spawn_blocking(move || {
        wait_for_reachability_blocking(port, target, timeout)
    })
    .await
    .unwrap_or(false)
}

fn wait_for_reachability_blocking(port: u16, target: bool, timeout: Duration) -> bool {
    let started = std::time::Instant::now();
    loop {
        if is_cortex_reachable_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS) == target {
            return true;
        }
        if started.elapsed() >= timeout {
            return false;
        }
        std::thread::sleep(Duration::from_millis(DAEMON_WAIT_POLL_MS));
    }
}

fn read_auth_token_once() -> Result<String, String> {
    let path = token_path()?;
    let token = fs::read_to_string(&path)
        .map_err(|err| format!("Failed to read token at {}: {err}", path.display()))?;
    Ok(token.trim().to_string())
}

fn auth_token_ready() -> bool {
    matches!(read_auth_token_once(), Ok(token) if !token.is_empty())
}

fn read_auth_token_with_retry_blocking(timeout: Duration) -> Result<String, String> {
    let path = token_path()?;
    if !is_cortex_reachable_with_port(daemon_port(), DAEMON_REACHABILITY_TIMEOUT_MS) {
        return read_auth_token_once();
    }

    let started = std::time::Instant::now();
    let mut last_error = format!("Auth token not ready at {}", path.display());

    loop {
        match fs::read_to_string(&path) {
            Ok(token) => {
                let trimmed = token.trim();
                if !trimmed.is_empty() {
                    return Ok(trimmed.to_string());
                }
                last_error = format!("Auth token file is empty at {}", path.display());
            }
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
                last_error = format!("Auth token file not found at {}", path.display());
            }
            Err(err) => {
                last_error = format!("Failed to read token at {}: {err}", path.display());
            }
        }

        if started.elapsed() >= timeout {
            return Err(last_error);
        }

        std::thread::sleep(Duration::from_millis(AUTH_TOKEN_POLL_MS));
    }
}

async fn read_auth_token_with_retry() -> Result<String, String> {
    tauri::async_runtime::spawn_blocking(move || {
        read_auth_token_with_retry_blocking(Duration::from_millis(AUTH_TOKEN_WAIT_MS))
    })
    .await
    .map_err(|err| format!("Auth token wait task failed: {err}"))?
}

fn describe_daemon_state(
    managed: bool,
    reachable: bool,
    starting: bool,
    auth_token_ready: bool,
    pid: Option<u32>,
    port: u16,
) -> String {
    if managed && reachable && auth_token_ready {
        format!("Cortex daemon running (pid {}).", pid.unwrap_or_default())
    } else if managed && reachable {
        format!(
            "Cortex daemon running (pid {}) and reachable, waiting for auth token.",
            pid.unwrap_or_default()
        )
    } else if managed && starting {
        format!(
            "Cortex daemon running (pid {}) and still starting on :{}.",
            pid.unwrap_or_default(),
            port
        )
    } else if managed {
        format!(
            "Cortex daemon running (pid {}) but not reachable on :{} yet.",
            pid.unwrap_or_default(),
            port
        )
    } else if reachable && auth_token_ready {
        "Cortex daemon reachable (external process).".to_string()
    } else if reachable {
        "Cortex daemon reachable (external process), waiting for auth token.".to_string()
    } else if starting {
        format!(
            "Cortex daemon is responding on :{} and still starting.",
            port
        )
    } else {
        "Cortex daemon is offline.".to_string()
    }
}

fn cortex_binary_name() -> &'static str {
    if cfg!(windows) {
        "cortex.exe"
    } else {
        "cortex"
    }
}

fn normalized_path_for_guard(path: &Path) -> String {
    path.to_string_lossy()
        .replace('\\', "/")
        .to_ascii_lowercase()
}

fn path_is_under_root(path: &Path, root: &Path) -> bool {
    let normalized_path = normalized_path_for_guard(path);
    let mut normalized_root = normalized_path_for_guard(root);
    if !normalized_root.ends_with('/') {
        normalized_root.push('/');
    }
    normalized_path == normalized_root.trim_end_matches('/')
        || normalized_path.starts_with(&normalized_root)
}

fn is_allowed_isolated_target_dir(segment: &str) -> bool {
    segment == DEV_DAEMON_TARGET_DIR || segment == RELEASE_DAEMON_TARGET_DIR
}

fn is_non_runtime_test_artifact_path(path: &Path) -> bool {
    let mut in_target_tree = false;

    for component in path.components() {
        let segment = component.as_os_str().to_string_lossy().to_ascii_lowercase();
        if segment.is_empty() {
            continue;
        }

        if matches!(segment.as_str(), "target-tests" | "target-test" | "nextest") {
            return true;
        }

        if segment == "target" {
            in_target_tree = true;
            continue;
        }

        if segment.starts_with("target-") {
            if !is_allowed_isolated_target_dir(&segment) {
                return true;
            }
            in_target_tree = true;
            continue;
        }

        if in_target_tree && matches!(segment.as_str(), "deps" | "build" | "incremental") {
            return true;
        }
    }

    false
}

fn is_shared_workspace_debug_runtime_path(path: &Path) -> bool {
    let file_name = path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or_default()
        .to_ascii_lowercase();
    if file_name != cortex_binary_name().to_ascii_lowercase() {
        return false;
    }

    let segments: Vec<String> = path
        .components()
        .map(|component| component.as_os_str().to_string_lossy().to_ascii_lowercase())
        .collect();
    segments
        .windows(3)
        .any(|window| window == ["daemon-rs", "target", "debug"])
}

fn is_disallowed_daemon_binary_path(path: &Path) -> bool {
    let normalized = normalized_path_for_guard(path);
    let file_name = path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or_default()
        .to_ascii_lowercase();

    if file_name.starts_with("cortex-daemon-run") {
        return true;
    }
    if normalized.contains("/daemon-lifecycle-runtime/") {
        return true;
    }
    if is_non_runtime_test_artifact_path(path) {
        return true;
    }
    if is_shared_workspace_debug_runtime_path(path) {
        return true;
    }

    let mut temp_roots = vec![std::env::temp_dir()];
    if let Ok(temp) = std::env::var("TEMP") {
        temp_roots.push(PathBuf::from(temp));
    }
    if let Ok(tmp) = std::env::var("TMP") {
        temp_roots.push(PathBuf::from(tmp));
    }
    temp_roots
        .iter()
        .any(|root| !root.as_os_str().is_empty() && path_is_under_root(path, root))
}

fn workspace_binary_candidates(home: &Path, prefer_debug: bool) -> Vec<PathBuf> {
    let daemon_root = home.join("cortex").join("daemon-rs");
    let release_path = daemon_root
        .join("target")
        .join("release")
        .join(cortex_binary_name());
    let isolated_release_path = daemon_root
        .join(RELEASE_DAEMON_TARGET_DIR)
        .join("release")
        .join(cortex_binary_name());
    let isolated_debug_path = daemon_root
        .join(DEV_DAEMON_TARGET_DIR)
        .join("debug")
        .join(cortex_binary_name());

    if prefer_debug {
        vec![isolated_debug_path, isolated_release_path, release_path]
    } else {
        vec![isolated_release_path, release_path, isolated_debug_path]
    }
}

fn resolve_binary_on_path(binary_name: &str) -> Option<PathBuf> {
    let locator = if cfg!(windows) { "where.exe" } else { "which" };
    let output = Command::new(locator).arg(binary_name).output().ok()?;
    if !output.status.success() {
        return None;
    }

    String::from_utf8_lossy(&output.stdout)
        .lines()
        .map(str::trim)
        .filter(|line| !line.is_empty())
        .filter_map(|line| {
            let candidate = PathBuf::from(line);
            if is_disallowed_daemon_binary_path(&candidate) {
                log_startup_path(
                    "resolve-binary-on-path",
                    "reject-disallowed",
                    &candidate.display().to_string(),
                );
                None
            } else {
                Some(candidate)
            }
        })
        .next()
}

fn path_binary_fallback_enabled_from_value(value: Option<&str>) -> bool {
    value
        .map(|raw| {
            let normalized = raw.trim().to_ascii_lowercase();
            matches!(normalized.as_str(), "1" | "true" | "yes" | "on")
        })
        .unwrap_or(false)
}

fn path_binary_fallback_enabled() -> bool {
    path_binary_fallback_enabled_from_value(std::env::var(PATH_BINARY_FALLBACK_ENV).ok().as_deref())
}

fn service_ensure_fallback_enabled() -> bool {
    path_binary_fallback_enabled_from_value(
        std::env::var(SERVICE_ENSURE_FALLBACK_ENV).ok().as_deref(),
    )
}

fn parse_paths_json(output: &[u8]) -> Result<ResolvedCortexPaths, String> {
    let json: serde_json::Value = serde_json::from_slice(output)
        .map_err(|err| format!("Invalid JSON from `cortex paths --json`: {err}"))?;
    let port = json
        .get("port")
        .and_then(|value| value.as_u64())
        .map(|value| {
            u16::try_from(value).map_err(|err| format!("Port value out of range ({value}): {err}"))
        })
        .transpose()?;

    Ok(ResolvedCortexPaths {
        home: json
            .get("home")
            .and_then(|value| value.as_str())
            .map(PathBuf::from),
        token: json
            .get("token")
            .and_then(|value| value.as_str())
            .map(PathBuf::from),
        db: json
            .get("db")
            .and_then(|value| value.as_str())
            .map(PathBuf::from),
        pid: json
            .get("pid")
            .and_then(|value| value.as_str())
            .map(PathBuf::from),
        port,
        bind: json
            .get("bind")
            .and_then(|value| value.as_str())
            .map(|value| value.to_string()),
    })
}

fn resolve_paths_with_binary(
    binary: impl AsRef<std::ffi::OsStr>,
) -> Result<Option<ResolvedCortexPaths>, String> {
    let mut command = Command::new(binary);
    command.args(["paths", "--json"]);
    apply_hidden_process_flags(&mut command);
    let output = command
        .output()
        .map_err(|err| format!("Failed to execute `cortex paths --json`: {err}"))?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        if stderr.is_empty() {
            return Ok(None);
        }
        return Err(format!("`cortex paths --json` failed: {stderr}"));
    }
    parse_paths_json(&output.stdout).map(Some)
}

fn fallback_cortex_paths() -> ResolvedCortexPaths {
    let cortex_dir = env::var("CORTEX_HOME")
        .ok()
        .map(PathBuf::from)
        .or_else(|| default_cortex_dir().ok());

    let port = match env::var("CORTEX_PORT") {
        Ok(value) => match value.parse::<u16>() {
            Ok(port) => Some(port),
            Err(err) => {
                eprintln!("[cortex-control-center] Invalid CORTEX_PORT '{value}': {err}");
                Some(DEFAULT_DAEMON_PORT)
            }
        },
        Err(env::VarError::NotPresent) => Some(DEFAULT_DAEMON_PORT),
        Err(err) => {
            eprintln!("[cortex-control-center] Failed to read CORTEX_PORT: {err}");
            Some(DEFAULT_DAEMON_PORT)
        }
    };

    ResolvedCortexPaths {
        home: cortex_dir.clone(),
        token: cortex_dir.as_ref().map(|dir| dir.join("cortex.token")),
        db: cortex_dir.as_ref().map(|dir| dir.join("cortex.db")),
        pid: cortex_dir.as_ref().map(|dir| dir.join("cortex.pid")),
        port,
        bind: env::var("CORTEX_BIND")
            .ok()
            .map(|value| value.trim().to_string())
            .filter(|value| !value.is_empty())
            .or_else(|| Some("127.0.0.1".to_string())),
    }
}

fn resolved_cortex_paths() -> ResolvedCortexPaths {
    if let Some(binary) = find_cortex_binary() {
        match resolve_paths_with_binary(&binary) {
            Ok(Some(paths)) => return paths,
            Ok(None) => {}
            Err(err) => eprintln!("[cortex-control-center] {err}"),
        }
    }

    if path_binary_fallback_enabled() {
        if let Some(binary) = resolve_binary_on_path("cortex") {
            match resolve_paths_with_binary(&binary) {
                Ok(Some(paths)) => return paths,
                Ok(None) => {}
                Err(err) => eprintln!("[cortex-control-center] {err}"),
            }
        }
    }

    fallback_cortex_paths()
}

fn resolve_daemon_port() -> u16 {
    resolved_cortex_paths().port.unwrap_or(DEFAULT_DAEMON_PORT)
}

fn log_startup_path(context: &str, decision: &str, detail: &str) {
    eprintln!(
        "[cortex-control-center] startup-path context={context} decision={decision} detail={detail}"
    );
}

fn installed_plugin_binary_path(home: &Path) -> PathBuf {
    home.join(".cortex").join("bin").join(cortex_binary_name())
}

fn copy_if_changed(src: &Path, dest: &Path) -> Result<(), String> {
    let needs_copy = match fs::read(dest) {
        Ok(existing) => {
            existing != fs::read(src).map_err(|e| format!("read {}: {e}", src.display()))?
        }
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => true,
        Err(err) => return Err(format!("read {}: {err}", dest.display())),
    };

    if needs_copy {
        fs::copy(src, dest)
            .map_err(|e| format!("copy {} -> {}: {e}", src.display(), dest.display()))?;
    }

    Ok(())
}

fn ensure_editor_binary_path() -> Result<PathBuf, String> {
    let home = cortex_home()?;
    let source = find_cortex_binary().ok_or_else(|| {
        "Could not find cortex binary in sidecar directory, ~/.cortex/bin/, or ~/cortex/daemon-rs/{target-control-center-dev,target-control-center-release,target}/{debug,release}/".to_string()
    })?;
    if is_disallowed_daemon_binary_path(&source) {
        return Err(format!(
            "Refusing disallowed daemon binary source path for editor registration: {}",
            source.display()
        ));
    }
    let installed = installed_plugin_binary_path(&home);

    if source != installed {
        if let Some(parent) = installed.parent() {
            fs::create_dir_all(parent).map_err(|e| format!("create {}: {e}", parent.display()))?;
        }
        copy_if_changed(&source, &installed)?;
    }

    Ok(installed)
}

fn find_cortex_binary() -> Option<PathBuf> {
    let sidecar_candidate = env::current_exe().ok().and_then(|exe| {
        exe.parent()
            .map(|dir| dir.join(cortex_binary_name()))
            .filter(|path| path.exists())
    });

    if let Ok(home) = cortex_home() {
        let plugin_path = home.join(".cortex").join("bin").join(cortex_binary_name());
        let mut candidates = Vec::new();
        if cfg!(debug_assertions) {
            // In dev builds prefer the Control Center's isolated daemon target
            // before falling back to the shared workspace target. This avoids
            // unrelated `target/debug` activity (for example MCP client shims)
            // from silently hijacking lifecycle verification.
            for candidate in workspace_binary_candidates(&home, true) {
                if !candidate.exists() {
                    continue;
                }
                if is_disallowed_daemon_binary_path(&candidate) {
                    log_startup_path(
                        "find-cortex-binary",
                        "reject-disallowed",
                        &candidate.display().to_string(),
                    );
                    continue;
                }
                return Some(candidate);
            }
            candidates.push(plugin_path);
            if let Some(sidecar) = sidecar_candidate.clone() {
                candidates.push(sidecar);
            }
        } else {
            if let Some(sidecar) = sidecar_candidate.clone() {
                candidates.push(sidecar);
            }
            candidates.push(plugin_path);
            candidates.extend(workspace_binary_candidates(&home, false));
        }

        for candidate in candidates {
            if !candidate.exists() {
                continue;
            }
            if is_disallowed_daemon_binary_path(&candidate) {
                log_startup_path(
                    "find-cortex-binary",
                    "reject-disallowed",
                    &candidate.display().to_string(),
                );
                continue;
            }
            return Some(candidate);
        }
    }

    if let Some(sidecar) =
        sidecar_candidate.filter(|candidate| !is_disallowed_daemon_binary_path(candidate))
    {
        return Some(sidecar);
    }

    if path_binary_fallback_enabled() {
        return resolve_binary_on_path(cortex_binary_name());
    }

    None
}

fn command_output_summary(output: &std::process::Output) -> String {
    let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
    match (stdout.is_empty(), stderr.is_empty()) {
        (false, false) => format!("{stdout}\n{stderr}"),
        (false, true) => stdout,
        (true, false) => stderr,
        (true, true) => "<no output>".to_string(),
    }
}

fn try_service_ensure(port: u16) -> Result<bool, String> {
    if !cfg!(windows) {
        return Ok(false);
    }

    let Some(cortex_bin) = find_cortex_binary() else {
        return Ok(false);
    };

    let mut command = Command::new(&cortex_bin);
    command.args(["service", "ensure"]);
    apply_hidden_process_flags(&mut command);
    let output = command.output().map_err(|err| {
        format!(
            "Failed to run `{}` service ensure: {err}",
            cortex_bin.display()
        )
    })?;

    if !output.status.success() {
        return Err(format!(
            "`cortex service ensure` failed: {}",
            command_output_summary(&output)
        ));
    }

    if is_cortex_reachable_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS) {
        return Ok(true);
    }

    Ok(wait_for_reachability_blocking(
        port,
        true,
        Duration::from_millis(SERVICE_ENSURE_WAIT_MS),
    ))
}

fn try_local_app_managed_ensure(
    state: &DaemonState,
    port: u16,
) -> Result<CortexReachabilityProbe, String> {
    state.ensure_local_daemon()?;
    if wait_for_reachability_blocking(
        port,
        true,
        Duration::from_millis(LOCAL_DAEMON_START_WAIT_MS),
    ) {
        return Ok(probe_cortex_reachability_with_port(
            port,
            DAEMON_REACHABILITY_TIMEOUT_MS,
        ));
    }

    // Re-probe once after the initial wait before declaring "still starting".
    // A surviving child process alone is not enough signal; it can be stale.
    let post_wait_probe = probe_cortex_reachability_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS);
    if local_probe_allows_starting_retry(&post_wait_probe) {
        return Ok(post_wait_probe);
    }

    let (_, pid) = state.status()?;
    Err(local_app_managed_start_timeout_message(state, pid, port))
}

fn local_probe_allows_starting_retry(probe: &CortexReachabilityProbe) -> bool {
    probe.reachable || probe.starting
}

fn local_app_managed_start_timeout_message(
    state: &DaemonState,
    pid: Option<u32>,
    port: u16,
) -> String {
    let base = if let Some(pid) = pid {
        format!("App-managed daemon spawned (pid {pid}) but never became reachable on :{port}.")
    } else {
        format!("App-managed daemon spawn did not produce a live daemon on :{port}.")
    };

    match state.stop() {
        Ok(()) => format!("{base} Control Center cleared the stale app-managed startup state."),
        Err(err) => format!(
            "{base} Control Center could not clear the stale app-managed startup state: {err}"
        ),
    }
}

fn show_main_window<R: Runtime>(app: &tauri::AppHandle<R>) {
    if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) {
        let _ = window.unminimize();
        let _ = window.show();
        let _ = window.set_focus();
    }
}

fn hide_main_window<R: Runtime>(app: &tauri::AppHandle<R>) {
    if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) {
        let _ = window.minimize();
        let _ = window.hide();
    }
}

fn hide_to_tray_on_close() -> bool {
    true
}

fn request_app_quit<R: Runtime>(app: &tauri::AppHandle<R>) {
    let lifecycle = app.state::<LifecycleState>();
    lifecycle.request_quit();
    app.exit(0);
}

fn shutdown_daemon<R: Runtime>(app: &tauri::AppHandle<R>) {
    let daemon_state = app.state::<DaemonState>();
    let (managed, _) = daemon_state.status().unwrap_or((false, None));
    let port = daemon_port();
    if managed && is_cortex_reachable_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS) {
        let _ = send_http_shutdown();
        let _ =
            wait_for_reachability_blocking(port, false, Duration::from_millis(DAEMON_STOP_WAIT_MS));
    }
    if managed {
        let _ = daemon_state.stop();
        let _ = flush_cortex_db_on_shutdown();
    }
}

fn flush_cortex_db_on_shutdown() -> Result<(), String> {
    let db_path = cortex_db_path()?;
    if !db_path.exists() {
        return Ok(());
    }

    let conn = Connection::open(&db_path).map_err(|err| {
        format!(
            "Failed to open DB for shutdown flush {}: {err}",
            db_path.display()
        )
    })?;
    conn.execute_batch(
        r#"
    PRAGMA journal_mode = WAL;
    PRAGMA wal_checkpoint(TRUNCATE);
    "#,
    )
    .map_err(|err| {
        format!(
            "Failed to flush WAL on shutdown {}: {err}",
            db_path.display()
        )
    })?;
    conn.close()
        .map_err(|(_, err)| format!("Failed to close DB after shutdown flush: {err}"))?;
    Ok(())
}

fn setup_tray<R: Runtime>(app: &tauri::App<R>) -> tauri::Result<()> {
    let tray_menu = MenuBuilder::new(app)
        .text(TRAY_SHOW_ID, "Show")
        .text(TRAY_HIDE_ID, "Hide / Minimize")
        .separator()
        .text(TRAY_QUIT_ID, "Quit")
        .build()?;

    let mut tray_builder = TrayIconBuilder::with_id(TRAY_ID)
        .menu(&tray_menu)
        .show_menu_on_left_click(false)
        .tooltip("Cortex Control Center")
        .on_menu_event(|app, event| match event.id().as_ref() {
            TRAY_SHOW_ID => show_main_window(app),
            TRAY_HIDE_ID => hide_main_window(app),
            TRAY_QUIT_ID => request_app_quit(app),
            _ => {}
        })
        .on_tray_icon_event(|tray, event| {
            if matches!(
                event,
                TrayIconEvent::Click {
                    button: MouseButton::Left,
                    button_state: MouseButtonState::Up,
                    ..
                }
            ) {
                show_main_window(tray.app_handle());
            }
        });

    if let Some(icon) = app.default_window_icon().cloned() {
        tray_builder = tray_builder.icon(icon);
    }

    let _tray = tray_builder.build(app)?;
    Ok(())
}

#[tauri::command]
fn quit_app(app: tauri::AppHandle) -> Result<(), String> {
    let lifecycle = app.state::<LifecycleState>();
    lifecycle.request_quit();
    app.exit(0);
    Ok(())
}

#[tauri::command]
fn hide_to_tray(app: tauri::AppHandle) -> Result<(), String> {
    hide_main_window(&app);
    Ok(())
}

#[tauri::command]
async fn daemon_status(state: State<'_, DaemonState>) -> Result<DaemonCommandResult, String> {
    let (managed, pid) = state.status()?;
    let port = daemon_port();
    let probe = tauri::async_runtime::spawn_blocking(move || {
        probe_cortex_reachability_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS)
    })
    .await
    .map_err(|err| format!("daemon_status reachability task failed: {err}"))?;
    let reachable = probe.reachable;
    let starting = probe.starting;
    let auth_token_ready = if reachable {
        tauri::async_runtime::spawn_blocking(auth_token_ready)
            .await
            .map_err(|err| format!("daemon_status token task failed: {err}"))?
    } else {
        false
    };
    let mut message =
        describe_daemon_state(managed, reachable, starting, auth_token_ready, pid, port);
    if probe.identity_mismatch {
        message.push_str(
            " Runtime identity metadata mismatch detected; using loose local daemon probe.",
        );
    }

    Ok(DaemonCommandResult {
        running: managed || reachable || starting,
        reachable,
        managed,
        auth_token_ready,
        pid,
        message,
    })
}

#[tauri::command]
async fn start_daemon(state: State<'_, DaemonState>) -> Result<DaemonCommandResult, String> {
    let port = daemon_port();
    let (managed, pid) = state.status()?;
    let probe = probe_cortex_reachability_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS);
    if probe.reachable {
        log_startup_path(
            "start_daemon",
            "existing-daemon",
            if probe.identity_mismatch {
                "daemon already reachable before start command (identity mismatch fallback)"
            } else {
                "daemon already reachable before start command"
            },
        );
        let auth_token_ready = auth_token_ready();
        let mut message = describe_daemon_state(managed, true, false, auth_token_ready, pid, port);
        if probe.identity_mismatch {
            message.push_str(
                " Runtime identity metadata mismatch detected; using loose local daemon probe.",
            );
        }
        return Ok(DaemonCommandResult {
            running: true,
            reachable: true,
            managed,
            auth_token_ready,
            pid,
            message,
        });
    }

    if probe.starting {
        log_startup_path(
            "start_daemon",
            "existing-daemon",
            if probe.identity_mismatch {
                "daemon already starting before start command (identity mismatch fallback)"
            } else {
                "daemon already starting before start command"
            },
        );
        let auth_token_ready = auth_token_ready();
        let mut message = describe_daemon_state(managed, false, true, auth_token_ready, pid, port);
        if probe.identity_mismatch {
            message.push_str(
                " Runtime identity metadata mismatch detected; using loose local daemon probe.",
            );
        }
        return Ok(DaemonCommandResult {
            running: true,
            reachable: false,
            managed,
            auth_token_ready,
            pid,
            message,
        });
    }

    match try_local_app_managed_ensure(&state, port) {
        Ok(local_probe) => {
            log_startup_path(
                "start_daemon",
                "app-managed-spawn",
                if local_probe.reachable {
                    "daemon started via Control Center local mode"
                } else {
                    "daemon spawned via Control Center local mode and is still starting"
                },
            );
            let (managed, pid) = state.status()?;
            let auth_token_ready = if local_probe.reachable {
                auth_token_ready()
            } else {
                false
            };
            let mut message = describe_daemon_state(
                managed,
                local_probe.reachable,
                local_probe.starting,
                auth_token_ready,
                pid,
                port,
            );
            if local_probe.identity_mismatch {
                message.push_str(
                    " Runtime identity metadata mismatch detected; using loose local daemon probe.",
                );
            }
            Ok(DaemonCommandResult {
                running: managed || local_probe.reachable || local_probe.starting,
                reachable: local_probe.reachable,
                managed,
                auth_token_ready,
                pid,
                message,
            })
        }
        Err(local_err) => {
            if service_ensure_fallback_enabled() {
                match try_service_ensure(port) {
                    Ok(true) => {
                        log_startup_path(
                            "start_daemon",
                            "service-ensure",
                            "daemon started or validated via service ensure after app-managed fallback",
                        );
                        let auth_token_ready = auth_token_ready();
                        Ok(DaemonCommandResult {
                            running: true,
                            reachable: true,
                            managed: false,
                            auth_token_ready,
                            pid: None,
                            message: "Daemon started via Windows service.".to_string(),
                        })
                    }
                    Ok(false) => {
                        log_startup_path("start_daemon", "blocked", "app-managed spawn failed");
                        Err(format!(
                            "App-managed local start failed and Windows service ensure was unavailable: {local_err}"
                        ))
                    }
                    Err(service_err) => {
                        log_startup_path("start_daemon", "blocked", "app-managed spawn failed");
                        Err(format!(
                            "App-managed local start failed: {local_err}. Windows service ensure failed: {service_err}"
                        ))
                    }
                }
            } else {
                log_startup_path(
                    "start_daemon",
                    "blocked",
                    "app-managed spawn failed (service fallback disabled)",
                );
                Err(format!("App-managed local start failed: {local_err}"))
            }
        }
    }
}

/// Send POST /shutdown to the daemon's HTTP endpoint (works for any daemon,
/// regardless of who spawned it). Returns Ok(()) on success or if connection
/// fails (daemon already gone).
fn send_http_shutdown() -> Result<(), String> {
    let token = read_auth_token_once().unwrap_or_default();
    let initial = send_cortex_request("POST", "/shutdown", &token, Some("{}"), None);
    if matches!(
        initial,
        Ok(FetchCortexResponse {
            status: 401 | 403,
            ..
        })
    ) {
        if let Ok(refreshed_token) =
            read_auth_token_with_retry_blocking(Duration::from_millis(AUTH_TOKEN_WAIT_MS))
        {
            if !refreshed_token.is_empty() && refreshed_token != token {
                return interpret_shutdown_response(send_cortex_request(
                    "POST",
                    "/shutdown",
                    &refreshed_token,
                    Some("{}"),
                    None,
                ));
            }
        }
    }

    interpret_shutdown_response(initial)
}

fn interpret_shutdown_response(
    response: Result<FetchCortexResponse, String>,
) -> Result<(), String> {
    match response {
        Ok(resp) if (200..300).contains(&resp.status) => Ok(()),
        Ok(resp) if resp.status == 401 || resp.status == 403 => Err(
            "Shutdown rejected by daemon authentication. Refresh the token or restart the daemon from Control Center."
                .to_string(),
        ),
        Ok(resp) => {
            let detail = extract_error_detail(&resp.body)
                .map(|value| format!(" ({value})"))
                .unwrap_or_default();
            Err(format!("Daemon shutdown failed: HTTP {}{detail}", resp.status))
        }
        Err(err) if err.starts_with("Cannot connect to daemon") => Ok(()),
        Err(err) => Err(format!("Failed to send daemon shutdown: {err}")),
    }
}

fn extract_error_detail(body: &str) -> Option<String> {
    let trimmed = body.trim();
    if trimmed.is_empty() {
        return None;
    }
    if let Ok(json) = serde_json::from_str::<serde_json::Value>(trimmed) {
        if let Some(error) = json.get("error").and_then(|value| value.as_str()) {
            return Some(error.to_string());
        }
    }
    Some(trimmed.chars().take(120).collect())
}

#[tauri::command]
async fn stop_daemon(state: State<'_, DaemonState>) -> Result<DaemonCommandResult, String> {
    let port = daemon_port();
    let (was_running, _) = state.status()?;
    let still_reachable = is_cortex_reachable_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS);
    let mut shutdown_error = None;
    if was_running && still_reachable {
        if let Err(err) = send_http_shutdown() {
            shutdown_error = Some(err);
        }
        let _ =
            wait_for_reachability(port, false, Duration::from_millis(DAEMON_STOP_WAIT_MS)).await;
    }

    let managed_stop_error = if was_running {
        state.stop().err()
    } else {
        None
    };
    let reachable = is_cortex_reachable_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS);
    if reachable {
        if let Some(err) = managed_stop_error.or(shutdown_error) {
            return Err(err);
        }
    }

    let message = if was_running && !reachable {
        "Stopped app-managed Cortex daemon.".to_string()
    } else if was_running && reachable {
        "Shutdown signal sent, daemon still shutting down...".to_string()
    } else if !was_running && still_reachable {
        "Unmanaged daemon is running; Control Center did not stop it.".to_string()
    } else {
        "Daemon is already stopped.".to_string()
    };

    Ok(DaemonCommandResult {
        running: reachable,
        reachable,
        managed: false,
        auth_token_ready: reachable && auth_token_ready(),
        pid: None,
        message,
    })
}

#[tauri::command]
async fn read_auth_token() -> Result<String, String> {
    read_auth_token_with_retry().await
}

#[tauri::command]
fn read_budget_config() -> Result<BudgetConfigSnapshot, String> {
    let path = budget_config_path()?;
    read_budget_config_snapshot(&path)
}

#[tauri::command]
fn save_budget_config(draft: BudgetConfigDraft) -> Result<BudgetConfigSnapshot, String> {
    let path = budget_config_path()?;
    let config = validate_budget_draft(draft)?;
    let contents = toml::to_string_pretty(&config)
        .map_err(|err| format!("Failed to serialize budget config: {err}"))?;
    write_budget_config_file(&path, &contents)?;
    read_budget_config_snapshot(&path)
}

// ─── HTTP Proxy (bypasses WebView2 mixed-content restrictions) ──────────────

#[tauri::command]
async fn fetch_cortex(
    path: String,
    auth_token: String,
    timeout_ms: Option<u64>,
) -> Result<FetchCortexResponse, String> {
    tauri::async_runtime::spawn_blocking(move || {
        send_cortex_request("GET", &path, &auth_token, None, timeout_ms)
    })
    .await
    .map_err(|err| format!("fetch_cortex task failed: {err}"))?
}

#[tauri::command]
async fn post_cortex(
    path: String,
    auth_token: String,
    body: String,
    timeout_ms: Option<u64>,
) -> Result<FetchCortexResponse, String> {
    tauri::async_runtime::spawn_blocking(move || {
        send_cortex_request("POST", &path, &auth_token, Some(&body), timeout_ms)
    })
    .await
    .map_err(|err| format!("post_cortex task failed: {err}"))?
}

#[tauri::command]
fn write_dev_verification_report(content: String) -> Result<String, String> {
    if !cfg!(debug_assertions) {
        return Err("Dev verification reporting is only available in debug builds.".to_string());
    }

    let report_path = env::var("CORTEX_DEV_VERIFY_REPORT_PATH")
        .map(PathBuf::from)
        .map_err(|_| "CORTEX_DEV_VERIFY_REPORT_PATH is not configured.".to_string())?;

    if let Some(parent) = report_path.parent() {
        fs::create_dir_all(parent)
            .map_err(|err| format!("Failed to create {}: {err}", parent.display()))?;
    }

    fs::write(&report_path, content)
        .map_err(|err| format!("Failed to write {}: {err}", report_path.display()))?;

    Ok(report_path.display().to_string())
}

fn send_cortex_request(
    method: &str,
    path: &str,
    auth_token: &str,
    body: Option<&str>,
    timeout_ms: Option<u64>,
) -> Result<FetchCortexResponse, String> {
    let read_timeout = Duration::from_millis(
        timeout_ms
            .unwrap_or(DAEMON_READ_TIMEOUT_MS)
            .clamp(DAEMON_MIN_REQUEST_TIMEOUT_MS, DAEMON_MAX_REQUEST_TIMEOUT_MS),
    );
    send_cortex_request_with_port(
        daemon_port(),
        method,
        path,
        auth_token,
        body,
        RequestTimeouts {
            connect: Duration::from_millis(DAEMON_CONNECT_TIMEOUT_MS),
            read: read_timeout,
            write: Duration::from_millis(DAEMON_WRITE_TIMEOUT_MS),
        },
    )
}

fn should_use_partial_response_on_read_timeout(err: &std::io::Error, response_len: usize) -> bool {
    if response_len == 0 {
        return false;
    }

    if matches!(
        err.kind(),
        std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock
    ) {
        return true;
    }

    // Windows socket timeouts are sometimes reported as WSAETIMEDOUT (10060)
    // with a non-timeout ErrorKind; treat them as timeout-equivalent.
    err.raw_os_error() == Some(10060)
}

fn send_cortex_request_with_port(
    port: u16,
    method: &str,
    path: &str,
    auth_token: &str,
    body: Option<&str>,
    timeouts: RequestTimeouts,
) -> Result<FetchCortexResponse, String> {
    use std::io::{Read, Write};

    if path.contains('\r') || path.contains('\n') {
        return Err("Invalid request path".to_string());
    }

    let mut stream =
        TcpStream::connect_timeout(&SocketAddr::from(([127, 0, 0, 1], port)), timeouts.connect)
            .map_err(|e| format!("Cannot connect to daemon: {e}"))?;
    stream
        .set_read_timeout(Some(timeouts.read))
        .map_err(|e| format!("Cannot set read timeout: {e}"))?;
    stream
        .set_write_timeout(Some(timeouts.write))
        .map_err(|e| format!("Cannot set write timeout: {e}"))?;

    let mut request =
        format!("{method} {path} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nX-Cortex-Request: true\r\n");
    if !auth_token.is_empty() {
        request.push_str(&format!("Authorization: Bearer {auth_token}\r\n"));
    }
    if let Some(payload) = body {
        request.push_str("Content-Type: application/json\r\n");
        request.push_str(&format!("Content-Length: {}\r\n", payload.len()));
    }
    request.push_str("Connection: close\r\n\r\n");
    if let Some(payload) = body {
        request.push_str(payload);
    }

    stream
        .write_all(request.as_bytes())
        .map_err(|e| format!("Write failed: {e}"))?;

    let mut response = Vec::new();
    if let Err(err) = stream.read_to_end(&mut response) {
        if !should_use_partial_response_on_read_timeout(&err, response.len()) {
            return Err(format!("Read failed: {err}"));
        }
    }

    // Split headers from body
    if let Some(pos) = find_bytes(&response, b"\r\n\r\n") {
        let headers = &response[..pos];
        let body = &response[pos + 4..];
        let headers_text = String::from_utf8_lossy(headers);
        let status = parse_status_code(&headers_text)?;
        let chunked = headers_text.lines().any(|line| {
            let lower = line.to_ascii_lowercase();
            lower.starts_with("transfer-encoding:") && lower.contains("chunked")
        });

        // Check for chunked transfer encoding
        let body_bytes = if chunked {
            decode_chunked_bytes(body)?
        } else {
            body.to_vec()
        };
        let body_text = String::from_utf8(body_bytes)
            .map_err(|e| format!("Response body is not valid UTF-8: {e}"))?;
        Ok(FetchCortexResponse {
            status,
            body: body_text,
        })
    } else {
        Err("Invalid HTTP response".to_string())
    }
}

fn normalize_runtime_path(value: &str) -> String {
    let mut normalized = value.trim().replace('\\', "/");
    while normalized.len() > 1 && normalized.ends_with('/') {
        normalized.pop();
    }
    #[cfg(windows)]
    {
        normalized = normalized.to_ascii_lowercase();
    }
    normalized
}

fn health_path_field_matches(value: Option<&serde_json::Value>, expected: Option<&Path>) -> bool {
    let Some(expected) = expected else {
        return true;
    };
    let expected = normalize_runtime_path(&expected.to_string_lossy());
    value
        .and_then(|field| field.as_str())
        .map(normalize_runtime_path)
        .is_some_and(|actual| actual == expected)
}

fn cortex_readiness_state(
    status: u16,
    body: &str,
    expected_port: Option<u16>,
    expected_paths: Option<&ResolvedCortexPaths>,
) -> Option<bool> {
    let Ok(json) = serde_json::from_str::<serde_json::Value>(body.trim()) else {
        return None;
    };

    let ready = json.get("ready").and_then(|value| value.as_bool())?;
    let runtime = json.get("runtime").and_then(|value| value.as_object())?;
    let stats = json.get("stats").and_then(|value| value.as_object())?;
    let runtime_port = runtime
        .get("port")
        .and_then(|value| value.as_u64())
        .and_then(|value| u16::try_from(value).ok());

    if let Some(expected_port) = expected_port {
        if runtime_port != Some(expected_port) {
            return None;
        }
    }

    if let Some(paths) = expected_paths {
        if !health_path_field_matches(stats.get("home"), paths.home.as_deref()) {
            return None;
        }
        if !health_path_field_matches(runtime.get("token_path"), paths.token.as_deref()) {
            return None;
        }
        if !health_path_field_matches(runtime.get("db_path"), paths.db.as_deref()) {
            return None;
        }
        if !health_path_field_matches(runtime.get("pid_path"), paths.pid.as_deref()) {
            return None;
        }
    }

    if ready && !(200..300).contains(&status) {
        return None;
    }
    if !ready && status != 503 && !(200..300).contains(&status) {
        return None;
    }

    let readiness_status = json.get("status").and_then(|value| value.as_str());
    let expected_status = if ready { "ready" } else { "starting" };
    if let Some(readiness_status) = readiness_status {
        if readiness_status != expected_status {
            return None;
        }
    }

    Some(ready)
}

fn is_cortex_health_response(
    status: u16,
    body: &str,
    expected_port: Option<u16>,
    expected_paths: Option<&ResolvedCortexPaths>,
) -> bool {
    if !(200..300).contains(&status) {
        return false;
    }

    let Ok(json) = serde_json::from_str::<serde_json::Value>(body.trim()) else {
        return false;
    };

    let health_status = json.get("status").and_then(|value| value.as_str());
    let runtime = json.get("runtime").and_then(|value| value.as_object());
    let stats = json.get("stats").and_then(|value| value.as_object());
    let runtime_port = runtime
        .and_then(|runtime| runtime.get("port"))
        .and_then(|value| value.as_u64())
        .and_then(|value| u16::try_from(value).ok());

    if let Some(expected_port) = expected_port {
        if runtime_port != Some(expected_port) {
            return false;
        }
    }

    if let Some(paths) = expected_paths {
        if !health_path_field_matches(stats.and_then(|obj| obj.get("home")), paths.home.as_deref())
        {
            return false;
        }
        if !health_path_field_matches(
            runtime.and_then(|obj| obj.get("token_path")),
            paths.token.as_deref(),
        ) {
            return false;
        }
        if !health_path_field_matches(
            runtime.and_then(|obj| obj.get("db_path")),
            paths.db.as_deref(),
        ) {
            return false;
        }
        if !health_path_field_matches(
            runtime.and_then(|obj| obj.get("pid_path")),
            paths.pid.as_deref(),
        ) {
            return false;
        }
    }

    matches!(health_status, Some("ok" | "degraded")) && runtime.is_some() && stats.is_some()
}

#[derive(Serialize)]
struct FetchCortexResponse {
    status: u16,
    body: String,
}

fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    if needle.is_empty() || haystack.len() < needle.len() {
        return None;
    }
    haystack
        .windows(needle.len())
        .position(|window| window == needle)
}

fn parse_status_code(headers: &str) -> Result<u16, String> {
    let status_line = headers
        .lines()
        .next()
        .ok_or_else(|| "Missing HTTP status line".to_string())?;
    let code = status_line
        .split_whitespace()
        .nth(1)
        .ok_or_else(|| format!("Invalid HTTP status line: {status_line}"))?;
    code.parse::<u16>()
        .map_err(|e| format!("Invalid HTTP status code '{code}': {e}"))
}

fn decode_chunked_bytes(body: &[u8]) -> Result<Vec<u8>, String> {
    let mut result = Vec::new();
    let mut remaining = body;

    loop {
        let line_end = find_bytes(remaining, b"\r\n").ok_or_else(|| {
            "Invalid chunked encoding: missing chunk size line ending".to_string()
        })?;
        let size_line = std::str::from_utf8(&remaining[..line_end])
            .map_err(|e| format!("Invalid chunk size line UTF-8: {e}"))?;
        let size_hex = size_line.split(';').next().unwrap_or("").trim();
        let size = usize::from_str_radix(size_hex, 16)
            .map_err(|e| format!("Invalid chunk size '{size_hex}': {e}"))?;

        let data_start = line_end + 2;
        if data_start > remaining.len() {
            return Err("Invalid chunked encoding: malformed chunk header".to_string());
        }
        remaining = &remaining[data_start..];

        if size == 0 {
            break;
        }

        if remaining.len() < size + 2 {
            return Err("Invalid chunked encoding: chunk truncated".to_string());
        }

        result.extend_from_slice(&remaining[..size]);
        remaining = &remaining[size..];

        if !remaining.starts_with(b"\r\n") {
            return Err("Invalid chunked encoding: missing CRLF after chunk".to_string());
        }
        remaining = &remaining[2..];
    }

    Ok(result)
}

// ─── MCP Auto-Registration ──────────────────────────────────────────────────

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct EditorDetection {
    id: String,
    name: String,
    detected: bool,
    registered: bool,
    config_path: Option<String>,
    command_path: Option<String>,
    message: String,
}

#[derive(Clone, Copy)]
enum EditorConfigKind {
    Json,
    Toml,
}

#[derive(Clone)]
struct EditorTarget {
    id: &'static str,
    name: &'static str,
    agent_name: &'static str,
    config_kind: EditorConfigKind,
    config_path: PathBuf,
    fallback_config_paths: Vec<PathBuf>,
}

fn cortex_exe_path() -> Option<PathBuf> {
    find_cortex_binary()
}

fn editor_args(target: &EditorTarget) -> [&'static str; 3] {
    ["mcp", "--agent", target.agent_name]
}

fn editor_env_pairs(_target: &EditorTarget) -> [(&'static str, &'static str); 2] {
    [
        ("CORTEX_APP_REQUIRED", "1"),
        ("CORTEX_DAEMON_OWNER_LOCAL_SPAWN", "0"),
    ]
}

fn editor_path_detected(path: &Path) -> bool {
    path.exists() || path.parent().map(|parent| parent.exists()).unwrap_or(false)
}

fn editor_config_path(target: &EditorTarget) -> PathBuf {
    if target.config_path.exists() {
        return target.config_path.clone();
    }
    for path in &target.fallback_config_paths {
        if path.exists() {
            return path.clone();
        }
    }
    target.config_path.clone()
}

fn cortex_mcp_registration(target: &EditorTarget, cortex_exe: &str) -> serde_json::Value {
    let mut env = serde_json::Map::new();
    for (key, value) in editor_env_pairs(target) {
        env.insert(
            key.to_string(),
            serde_json::Value::String(value.to_string()),
        );
    }
    env.insert(
        "CORTEX_APP_CLIENT".to_string(),
        serde_json::Value::String(target.agent_name.to_string()),
    );
    serde_json::json!({
      "command": cortex_exe,
      "args": editor_args(target),
      "env": env
    })
}

fn claude_desktop_config_path(home: &Path) -> PathBuf {
    #[cfg(windows)]
    {
        home.join("AppData")
            .join("Roaming")
            .join("Claude")
            .join("claude_desktop_config.json")
    }

    #[cfg(target_os = "macos")]
    {
        home.join("Library")
            .join("Application Support")
            .join("Claude")
            .join("claude_desktop_config.json")
    }

    #[cfg(all(unix, not(target_os = "macos")))]
    {
        home.join(".config")
            .join("Claude")
            .join("claude_desktop_config.json")
    }
}

fn editor_targets(home: &Path) -> Vec<EditorTarget> {
    vec![
        EditorTarget {
            id: "claude-code",
            name: "Claude Code",
            agent_name: "claude",
            config_kind: EditorConfigKind::Json,
            config_path: home.join(".claude").join("settings.json"),
            fallback_config_paths: Vec::new(),
        },
        EditorTarget {
            id: "claude-desktop",
            name: "Claude Desktop",
            agent_name: "claude",
            config_kind: EditorConfigKind::Json,
            config_path: claude_desktop_config_path(home),
            fallback_config_paths: Vec::new(),
        },
        EditorTarget {
            id: "cursor",
            name: "Cursor",
            agent_name: "cursor",
            config_kind: EditorConfigKind::Json,
            config_path: home.join(".cursor").join("mcp.json"),
            fallback_config_paths: Vec::new(),
        },
        EditorTarget {
            id: "codex",
            name: "Codex",
            agent_name: "codex",
            config_kind: EditorConfigKind::Toml,
            config_path: home.join(".codex").join("config.toml"),
            fallback_config_paths: Vec::new(),
        },
        EditorTarget {
            id: "gemini",
            name: "Gemini CLI",
            agent_name: "gemini",
            config_kind: EditorConfigKind::Json,
            config_path: home.join(".gemini").join("settings").join("mcp.json"),
            fallback_config_paths: vec![home.join(".gemini").join("settings.json")],
        },
        EditorTarget {
            id: "droid",
            name: "Droid",
            agent_name: "droid",
            config_kind: EditorConfigKind::Json,
            config_path: home.join(".factory").join("mcp.json"),
            fallback_config_paths: Vec::new(),
        },
    ]
}

fn editor_detected(target: &EditorTarget) -> bool {
    editor_path_detected(&target.config_path)
        || target
            .fallback_config_paths
            .iter()
            .any(|path| editor_path_detected(path))
}

fn editor_command_path(cortex_exe: Option<&str>) -> Option<String> {
    cortex_exe.map(|path| path.to_string())
}

fn editor_detection(
    target: &EditorTarget,
    detected: bool,
    registered: bool,
    cortex_exe: Option<&str>,
    message: String,
) -> EditorDetection {
    let config_path = editor_config_path(target);
    EditorDetection {
        id: target.id.into(),
        name: target.name.into(),
        detected,
        registered,
        config_path: Some(config_path.display().to_string()),
        command_path: editor_command_path(cortex_exe),
        message,
    }
}

fn json_registration_for(target: &EditorTarget, cortex_exe: &str) -> serde_json::Value {
    let mut registration = cortex_mcp_registration(target, cortex_exe);
    if let Some(object) = registration.as_object_mut() {
        match target.id {
            "gemini" => {
                object.insert("trust".into(), serde_json::Value::Bool(true));
            }
            "droid" => {
                object.insert("disabled".into(), serde_json::Value::Bool(false));
            }
            _ => {}
        }
    }
    registration
}

fn read_json_config(config_path: &Path) -> Result<serde_json::Value, String> {
    if config_path.exists() {
        let content = fs::read_to_string(config_path).map_err(|e| e.to_string())?;
        Ok(serde_json::from_str(&content).unwrap_or_else(|_| serde_json::json!({})))
    } else {
        Ok(serde_json::json!({}))
    }
}

fn read_toml_config(config_path: &Path) -> Result<toml::Value, String> {
    if config_path.exists() {
        let content = fs::read_to_string(config_path).map_err(|e| e.to_string())?;
        Ok(toml::from_str(&content).unwrap_or_else(|_| toml::Value::Table(Default::default())))
    } else {
        Ok(toml::Value::Table(Default::default()))
    }
}

fn json_args_match(config: &serde_json::Value, expected_args: &[&str]) -> bool {
    config
        .get("args")
        .and_then(|value| value.as_array())
        .map(|args| {
            args.len() == expected_args.len()
                && args
                    .iter()
                    .zip(expected_args.iter())
                    .all(|(value, expected)| value.as_str() == Some(*expected))
        })
        .unwrap_or(false)
}

fn json_env_match(config: &serde_json::Value, target: &EditorTarget) -> bool {
    let Some(env) = config.get("env").and_then(|value| value.as_object()) else {
        return false;
    };
    let policy_matches = editor_env_pairs(target)
        .iter()
        .all(|(key, expected)| env.get(*key).and_then(|value| value.as_str()) == Some(*expected));
    let client_match = env
        .get("CORTEX_APP_CLIENT")
        .and_then(|value| value.as_str())
        == Some(target.agent_name);
    policy_matches && client_match
}

fn toml_args_match(config: &toml::Value, expected_args: &[&str]) -> bool {
    config
        .get("args")
        .and_then(|value| value.as_array())
        .map(|args| {
            args.len() == expected_args.len()
                && args
                    .iter()
                    .zip(expected_args.iter())
                    .all(|(value, expected)| value.as_str() == Some(*expected))
        })
        .unwrap_or(false)
}

fn toml_env_match(config: &toml::Value, target: &EditorTarget) -> bool {
    let Some(env) = config.get("env").and_then(|value| value.as_table()) else {
        return false;
    };
    let policy_matches = editor_env_pairs(target)
        .iter()
        .all(|(key, expected)| env.get(*key).and_then(|value| value.as_str()) == Some(*expected));
    let client_match = env
        .get("CORTEX_APP_CLIENT")
        .and_then(|value| value.as_str())
        == Some(target.agent_name);
    policy_matches && client_match
}

fn is_editor_registered_at_path(
    target: &EditorTarget,
    cortex_exe: &str,
    config_path: &Path,
) -> Result<bool, String> {
    if !config_path.exists() {
        return Ok(false);
    }
    let expected_args = editor_args(target);

    match target.config_kind {
        EditorConfigKind::Json => {
            let config = read_json_config(config_path)?;
            Ok(config
                .get("mcpServers")
                .and_then(|value| value.get("cortex"))
                .map(|value| {
                    value
                        .get("command")
                        .and_then(|command| command.as_str())
                        .map(|command| command == cortex_exe)
                        .unwrap_or(false)
                        && json_args_match(value, &expected_args)
                        && json_env_match(value, target)
                })
                .unwrap_or(false))
        }
        EditorConfigKind::Toml => {
            let config = read_toml_config(config_path)?;
            Ok(config
                .get("mcp_servers")
                .and_then(|value| value.get("cortex"))
                .map(|value| {
                    value
                        .get("command")
                        .and_then(|command| command.as_str())
                        .map(|command| command == cortex_exe)
                        .unwrap_or(false)
                        && toml_args_match(value, &expected_args)
                        && toml_env_match(value, target)
                })
                .unwrap_or(false))
        }
    }
}

fn is_editor_registered(target: &EditorTarget, cortex_exe: &str) -> Result<bool, String> {
    let config_path = editor_config_path(target);
    is_editor_registered_at_path(target, cortex_exe, &config_path)
}

fn register_json_editor(
    target: &EditorTarget,
    cortex_exe: &str,
) -> Result<EditorDetection, String> {
    let config_path = editor_config_path(target);
    if !editor_detected(target) {
        return Ok(editor_detection(
            target,
            false,
            false,
            Some(cortex_exe),
            format!("{} not detected ({})", target.name, config_path.display()),
        ));
    }

    let mut config = read_json_config(&config_path)?;
    let servers = config
        .as_object_mut()
        .ok_or_else(|| format!("Invalid JSON config format in {}", config_path.display()))?
        .entry("mcpServers")
        .or_insert_with(|| serde_json::json!({}));

    let action = if is_editor_registered_at_path(target, cortex_exe, &config_path)? {
        "Already configured"
    } else if config_path.exists() {
        "Updated configuration"
    } else {
        "Configured"
    };

    servers
        .as_object_mut()
        .ok_or_else(|| format!("Invalid mcpServers format in {}", config_path.display()))?
        .insert("cortex".into(), json_registration_for(target, cortex_exe));

    if let Some(parent) = config_path.parent() {
        fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    }
    let out = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;
    fs::write(&config_path, out).map_err(|e| e.to_string())?;

    Ok(editor_detection(
        target,
        true,
        true,
        Some(cortex_exe),
        format!("{action} in {}", config_path.display()),
    ))
}

fn register_toml_editor(
    target: &EditorTarget,
    cortex_exe: &str,
) -> Result<EditorDetection, String> {
    let config_path = editor_config_path(target);
    if !editor_detected(target) {
        return Ok(editor_detection(
            target,
            false,
            false,
            Some(cortex_exe),
            format!("{} not detected ({})", target.name, config_path.display()),
        ));
    }

    let mut config = read_toml_config(&config_path)?;
    let root = config
        .as_table_mut()
        .ok_or_else(|| format!("Invalid TOML config format in {}", config_path.display()))?;
    let servers = root
        .entry("mcp_servers")
        .or_insert_with(|| toml::Value::Table(Default::default()))
        .as_table_mut()
        .ok_or_else(|| format!("Invalid [mcp_servers] format in {}", config_path.display()))?;

    let action = if is_editor_registered_at_path(target, cortex_exe, &config_path)? {
        "Already configured"
    } else if config_path.exists() {
        "Updated configuration"
    } else {
        "Configured"
    };
    let args = editor_args(target);

    let mut server = toml::map::Map::new();
    server.insert(
        "command".into(),
        toml::Value::String(cortex_exe.to_string()),
    );
    server.insert(
        "args".into(),
        toml::Value::Array(
            args.into_iter()
                .map(|value| toml::Value::String(value.to_string()))
                .collect(),
        ),
    );
    let mut env_table = toml::map::Map::new();
    for (key, value) in editor_env_pairs(target) {
        env_table.insert(key.into(), toml::Value::String(value.to_string()));
    }
    env_table.insert(
        "CORTEX_APP_CLIENT".into(),
        toml::Value::String(target.agent_name.to_string()),
    );
    server.insert("env".into(), toml::Value::Table(env_table));
    servers.insert("cortex".into(), toml::Value::Table(server));

    if let Some(parent) = config_path.parent() {
        fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    }
    let out = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
    fs::write(&config_path, out).map_err(|e| e.to_string())?;

    Ok(editor_detection(
        target,
        true,
        true,
        Some(cortex_exe),
        format!("{action} in {}", config_path.display()),
    ))
}

fn register_editor(target: &EditorTarget, cortex_exe: &str) -> Result<EditorDetection, String> {
    match target.config_kind {
        EditorConfigKind::Json => register_json_editor(target, cortex_exe),
        EditorConfigKind::Toml => register_toml_editor(target, cortex_exe),
    }
}

#[tauri::command]
fn setup_editors(editor_ids: Option<Vec<String>>) -> Result<Vec<EditorDetection>, String> {
    let cortex_exe = ensure_editor_binary_path()?;
    let exe_str = cortex_exe.to_string_lossy().to_string();
    let home = cortex_home()?;
    let targets = editor_targets(&home);
    let requested_ids = editor_ids
        .unwrap_or_default()
        .into_iter()
        .collect::<HashSet<_>>();
    let use_selection = !requested_ids.is_empty();
    let mut results = Vec::new();

    for target in targets {
        let detected = editor_detected(&target);
        if use_selection && !requested_ids.contains(target.id) {
            continue;
        }
        if !use_selection && !detected {
            continue;
        }

        match register_editor(&target, &exe_str) {
            Ok(result) => results.push(result),
            Err(err) => results.push(editor_detection(
                &target,
                detected,
                false,
                Some(&exe_str),
                format!("Configuration failed: {err}"),
            )),
        }
    }

    Ok(results)
}

#[tauri::command]
fn detect_editors() -> Result<Vec<EditorDetection>, String> {
    let home = cortex_home()?;
    let cortex_exe = cortex_exe_path();
    let cortex_exe_string = cortex_exe
        .as_ref()
        .map(|path| path.to_string_lossy().to_string());
    let mut results = Vec::new();

    for target in editor_targets(&home) {
        let detected = editor_detected(&target);
        let registered = if let Some(ref exe) = cortex_exe_string {
            is_editor_registered(&target, exe).unwrap_or(false)
        } else {
            false
        };
        let message = if cortex_exe_string.is_none() {
            "cortex.exe not found -- build daemon first".into()
        } else if registered {
            format!("Configured in {}", target.config_path.display())
        } else if detected {
            format!("Detected at {}", target.config_path.display())
        } else {
            format!("Not detected ({})", target.config_path.display())
        };

        results.push(editor_detection(
            &target,
            detected,
            registered,
            cortex_exe_string.as_deref(),
            message,
        ));
    }

    Ok(results)
}

/// Watchdog tick: respawn the daemon if it's neither reachable nor managed and
/// the user hasn't explicitly stopped it. Runs on a blocking thread on a fixed
/// cadence from `main()`'s setup hook.
fn supervisor_tick(app_handle: &tauri::AppHandle, consecutive_failures: &AtomicU32) {
    let daemon_state = app_handle.state::<DaemonState>();
    if daemon_state.supervisor_paused() {
        return;
    }

    let port = daemon_port();
    let probe = probe_cortex_reachability_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS);
    if probe.reachable || probe.starting {
        consecutive_failures.store(0, Ordering::SeqCst);
        return;
    }

    let (managed, _) = daemon_state.status().unwrap_or((false, None));
    if managed {
        // Managed child is alive but daemon HTTP isn't up yet. Give it more time
        // before declaring failure — startup can take several seconds.
        return;
    }

    let attempt = consecutive_failures.fetch_add(1, Ordering::SeqCst);
    match try_local_app_managed_ensure(&daemon_state, port) {
        Ok(_) => {
            log_startup_path(
                "supervisor",
                "respawn",
                "daemon was unreachable; supervisor respawned via app-managed local mode",
            );
            consecutive_failures.store(0, Ordering::SeqCst);
        }
        Err(err) => {
            // Throttle log noise: only log first failure and every 10th retry.
            if attempt == 0 || attempt % 10 == 0 {
                eprintln!(
                    "[cortex-control-center] supervisor respawn attempt {attempt} failed: {err}"
                );
                log_startup_path("supervisor", "respawn-failed", &err);
            }
        }
    }
}

fn bootstrap_daemon_on_startup(app_handle: &tauri::AppHandle) {
    let port = daemon_port();
    let probe = probe_cortex_reachability_with_port(port, DAEMON_REACHABILITY_TIMEOUT_MS);
    if probe.reachable {
        log_startup_path(
            "setup",
            "existing-daemon",
            if probe.identity_mismatch {
                "daemon already reachable at application startup (identity mismatch fallback)"
            } else {
                "daemon already reachable at application startup"
            },
        );
        return;
    }

    if probe.starting {
        log_startup_path(
            "setup",
            "existing-daemon",
            if probe.identity_mismatch {
                "daemon already starting at application startup (identity mismatch fallback)"
            } else {
                "daemon already starting at application startup"
            },
        );
        return;
    }

    let daemon_state = app_handle.state::<DaemonState>();
    match try_local_app_managed_ensure(&daemon_state, port) {
        Ok(local_probe) => {
            log_startup_path(
                "setup",
                "app-managed-spawn",
                if local_probe.reachable {
                    "daemon started via Control Center local mode"
                } else {
                    "daemon spawned via Control Center local mode and is still starting"
                },
            );
        }
        Err(local_err) => {
            if service_ensure_fallback_enabled() {
                match try_service_ensure(port) {
                    Ok(true) => {
                        log_startup_path(
                            "setup",
                            "service-ensure",
                            "daemon started or validated via service ensure after app-managed fallback",
                        );
                    }
                    Ok(false) => {
                        eprintln!(
                            "[cortex-control-center] app-managed local start failed at startup and Windows service ensure was unavailable: {local_err}"
                        );
                        log_startup_path("setup", "blocked", "app-managed spawn failed");
                    }
                    Err(service_err) => {
                        eprintln!(
                            "[cortex-control-center] app-managed local start failed at startup and service ensure also failed: {local_err}; {service_err}"
                        );
                        log_startup_path("setup", "blocked", "app-managed spawn failed");
                    }
                }
            } else {
                eprintln!(
                    "[cortex-control-center] app-managed local start failed at startup (service fallback disabled): {local_err}"
                );
                log_startup_path("setup", "blocked", "app-managed spawn failed");
            }
        }
    }
}

fn main() {
    let _instance_guard = match AppInstanceGuard::acquire() {
        Ok(Some(guard)) => guard,
        Ok(None) => {
            eprintln!("Cortex Control Center is already running.");
            return;
        }
        Err(err) => {
            eprintln!("Failed to initialize Cortex Control Center: {err}");
            return;
        }
    };
    let exe_path = find_cortex_binary();

    let app = tauri::Builder::default()
        .plugin(tauri_plugin_updater::Builder::new().build())
        .manage(DaemonState::new(exe_path))
        .manage(LifecycleState::default())
        .setup(|app| {
            if hide_to_tray_on_close() {
                setup_tray(app)?;
            }
            let bootstrap_handle = app.handle().clone();
            let supervisor_handle = app.handle().clone();
            tauri::async_runtime::spawn(async move {
                let _ = tauri::async_runtime::spawn_blocking(move || {
                    bootstrap_daemon_on_startup(&bootstrap_handle);
                })
                .await;
            });

            // Watchdog: re-spawn the daemon if it disappears for any reason
            // (panic, OOM, manual kill, crash). The daemon is the user's
            // memory store — it must stay up unless they explicitly stop it.
            // Runs on a plain OS thread so it survives any Tauri runtime hiccup.
            std::thread::Builder::new()
                .name("cortex-daemon-supervisor".to_string())
                .spawn(move || {
                    let consecutive_failures = AtomicU32::new(0);
                    loop {
                        std::thread::sleep(Duration::from_millis(SUPERVISOR_TICK_MS));
                        supervisor_tick(&supervisor_handle, &consecutive_failures);
                    }
                })
                .expect("failed to spawn cortex daemon supervisor thread");

            Ok(())
        })
        .on_window_event(|window, event| {
            if let tauri::WindowEvent::CloseRequested { api, .. } = event {
                let lifecycle = window.app_handle().state::<LifecycleState>();
                if hide_to_tray_on_close() && !lifecycle.is_quit_requested() {
                    api.prevent_close();
                    hide_main_window(window.app_handle());
                }
            }
        })
        .invoke_handler(tauri::generate_handler![
            daemon_status,
            start_daemon,
            stop_daemon,
            quit_app,
            hide_to_tray,
            read_auth_token,
            read_budget_config,
            save_budget_config,
            fetch_cortex,
            post_cortex,
            write_dev_verification_report,
            setup_editors,
            detect_editors
        ])
        .build(tauri::generate_context!())
        .expect("error while building cortex control center");

    app.run(|app_handle, event| match event {
        tauri::RunEvent::ExitRequested { api, .. } => {
            let lifecycle = app_handle.state::<LifecycleState>();
            if hide_to_tray_on_close() && !lifecycle.is_quit_requested() {
                api.prevent_exit();
                hide_main_window(app_handle);
            } else {
                shutdown_daemon(app_handle);
            }
        }
        tauri::RunEvent::Exit => {
            shutdown_daemon(app_handle);
        }
        _ => {}
    });
}

#[cfg(test)]
mod tests {
    use super::{
        budget_snapshot_from_contents, cortex_mcp_registration, cortex_readiness_state,
        describe_daemon_state, editor_args, editor_config_path, editor_targets,
        extract_error_detail, health_state_with_identity_fallback, hide_to_tray_on_close,
        interpret_shutdown_response, is_cortex_health_response, is_disallowed_daemon_binary_path,
        json_env_match, local_app_managed_start_timeout_message, local_probe_allows_starting_retry,
        path_binary_fallback_enabled_from_value, readiness_state_with_identity_fallback,
        should_use_partial_response_on_read_timeout, toml_env_match, validate_budget_draft,
        workspace_binary_candidates, write_budget_config_file, BudgetConfigDraft,
        BudgetEndpointDraft, CortexReachabilityProbe, DaemonState, FetchCortexResponse,
        LifecycleState, ResolvedCortexPaths,
    };
    use std::fs;
    use std::path::{Path, PathBuf};
    use std::process::{Child, Command};
    use std::sync::atomic::AtomicBool;
    use std::sync::Mutex;

    fn spawn_test_sleep_process() -> Child {
        #[cfg(windows)]
        {
            Command::new("cmd")
                .args(["/C", "ping -n 30 127.0.0.1 > NUL"])
                .spawn()
                .expect("spawn windows sleep surrogate")
        }

        #[cfg(not(windows))]
        {
            Command::new("sleep")
                .arg("30")
                .spawn()
                .expect("spawn unix sleep")
        }
    }

    #[test]
    fn close_button_policy_hides_to_tray_until_explicit_quit() {
        let lifecycle = LifecycleState::default();

        assert!(hide_to_tray_on_close());
        assert!(!lifecycle.is_quit_requested());

        lifecycle.request_quit();
        assert!(lifecycle.is_quit_requested());
    }

    #[test]
    fn workspace_binary_candidates_prefers_debug_for_dev_builds() {
        let candidates = workspace_binary_candidates(Path::new("C:/cortex-test/testuser"), true);
        assert_eq!(candidates.len(), 3);
        assert!(candidates[0]
            .to_string_lossy()
            .contains("target-control-center-dev\\debug"));
        assert!(candidates[1]
            .to_string_lossy()
            .contains("target-control-center-release\\release"));
        assert!(candidates[2].to_string_lossy().contains("target\\release"));
        assert!(candidates
            .iter()
            .all(|path| !path.to_string_lossy().contains("target\\debug")));
    }

    #[test]
    fn workspace_binary_candidates_prefers_release_for_packaged_builds() {
        let candidates = workspace_binary_candidates(Path::new("C:/cortex-test/testuser"), false);
        assert_eq!(candidates.len(), 3);
        assert!(candidates[0]
            .to_string_lossy()
            .contains("target-control-center-release\\release"));
        assert!(candidates[1].to_string_lossy().contains("target\\release"));
        assert!(candidates[2]
            .to_string_lossy()
            .contains("target-control-center-dev\\debug"));
        assert!(candidates
            .iter()
            .all(|path| !path.to_string_lossy().contains("target\\debug")));
    }

    #[test]
    fn path_binary_fallback_requires_explicit_truthy_env_value() {
        assert!(!path_binary_fallback_enabled_from_value(None));
        assert!(!path_binary_fallback_enabled_from_value(Some("")));
        assert!(!path_binary_fallback_enabled_from_value(Some("0")));
        assert!(!path_binary_fallback_enabled_from_value(Some("false")));
        assert!(path_binary_fallback_enabled_from_value(Some("1")));
        assert!(path_binary_fallback_enabled_from_value(Some("true")));
        assert!(path_binary_fallback_enabled_from_value(Some("Yes")));
        assert!(path_binary_fallback_enabled_from_value(Some("on")));
    }

    #[test]
    fn budget_editor_snapshot_parses_valid_config() {
        let snapshot = budget_snapshot_from_contents(
            Path::new("C:/cortex-test/testuser/.cortex/budgets.toml"),
            r#"
[defaults]
enabled = true

[endpoints.recall]
limit = 300
window_seconds = 60
"#,
        );

        assert!(snapshot.config_loaded);
        assert!(snapshot.enabled);
        assert_eq!(
            snapshot.error.as_ref().map(|error| error.code.as_str()),
            None
        );
        assert_eq!(snapshot.source, "budgets.toml");
        assert_eq!(snapshot.endpoints["recall"].limit, 300);
        assert_eq!(snapshot.endpoints["recall"].window_seconds, 60);
    }

    #[test]
    fn budget_editor_snapshot_returns_structured_errors() {
        let snapshot = budget_snapshot_from_contents(
            Path::new("C:/cortex-test/testuser/.cortex/budgets.toml"),
            r#"
[endpoints.unknown]
limit = 1
window_seconds = 60
"#,
        );

        let error = snapshot.error.expect("unknown endpoint should be invalid");
        assert_eq!(error.code, "unknown_endpoint");
        assert_eq!(error.endpoint.as_deref(), Some("unknown"));
        assert!(!snapshot.enabled);
    }

    #[test]
    fn budget_editor_draft_serializes_only_enabled_endpoints() {
        let config = validate_budget_draft(BudgetConfigDraft {
            enabled: true,
            endpoints: vec![
                BudgetEndpointDraft {
                    endpoint: "store".to_string(),
                    enabled: false,
                    limit: Some(120),
                    window_seconds: Some(60),
                },
                BudgetEndpointDraft {
                    endpoint: "recall".to_string(),
                    enabled: true,
                    limit: Some(42),
                    window_seconds: Some(15),
                },
            ],
        })
        .expect("draft should validate");

        assert!(config.defaults.enabled);
        assert_eq!(config.endpoints.len(), 1);
        assert_eq!(config.endpoints["recall"].limit, 42);
    }

    #[test]
    fn budget_editor_rejects_duplicate_or_invalid_endpoint_drafts() {
        let duplicate = validate_budget_draft(BudgetConfigDraft {
            enabled: true,
            endpoints: vec![
                BudgetEndpointDraft {
                    endpoint: "recall".to_string(),
                    enabled: true,
                    limit: Some(1),
                    window_seconds: Some(60),
                },
                BudgetEndpointDraft {
                    endpoint: "recall".to_string(),
                    enabled: true,
                    limit: Some(2),
                    window_seconds: Some(60),
                },
            ],
        })
        .unwrap_err();
        assert!(duplicate.contains("Duplicate budget endpoint"));

        let invalid = validate_budget_draft(BudgetConfigDraft {
            enabled: true,
            endpoints: vec![BudgetEndpointDraft {
                endpoint: "recall".to_string(),
                enabled: true,
                limit: Some(0),
                window_seconds: Some(60),
            }],
        })
        .unwrap_err();
        assert!(invalid.contains("limit must be between 1"));
    }

    #[test]
    fn budget_editor_write_replaces_file_atomically() {
        let unique = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("system time")
            .as_nanos();
        let dir = std::env::temp_dir().join(format!("cortex-budget-editor-{unique}"));
        fs::create_dir_all(&dir).expect("create temp dir");
        let path = dir.join("budgets.toml");

        write_budget_config_file(&path, "[defaults]\nenabled = true\n")
            .expect("write initial file");
        write_budget_config_file(&path, "[defaults]\nenabled = false\n")
            .expect("replace existing file");

        let contents = fs::read_to_string(&path).expect("read replaced file");
        assert!(contents.contains("enabled = false"));
        let _ = fs::remove_dir_all(&dir);
    }

    #[test]
    fn disallowed_daemon_binary_path_blocks_wrappers_temp_and_test_artifacts() {
        let wrapper = PathBuf::from(
            "C:/repo/daemon-rs/target/debug/daemon-lifecycle-runtime/cortex-daemon-run.exe",
        );
        assert!(is_disallowed_daemon_binary_path(&wrapper));

        let wrapper_name_only = PathBuf::from("C:/repo/cortex-daemon-run");
        assert!(is_disallowed_daemon_binary_path(&wrapper_name_only));

        let temp_candidate = std::env::temp_dir().join("cortex").join("cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&temp_candidate));

        let target_tests = PathBuf::from("C:/repo/daemon-rs/target-tests/debug/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&target_tests));

        let target_test = PathBuf::from("C:/repo/daemon-rs/target-test/release/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&target_test));

        let nextest = PathBuf::from("C:/repo/daemon-rs/target/nextest/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&nextest));

        let target_deps = PathBuf::from("C:/repo/daemon-rs/target/debug/deps/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&target_deps));

        let isolated_target_deps =
            PathBuf::from("C:/repo/daemon-rs/target-control-center-dev/debug/deps/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&isolated_target_deps));

        let shared_workspace_runtime = PathBuf::from("C:/repo/daemon-rs/target/debug/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&shared_workspace_runtime));

        let isolated_runtime =
            PathBuf::from("C:/repo/daemon-rs/target-control-center-dev/debug/cortex.exe");
        assert!(!is_disallowed_daemon_binary_path(&isolated_runtime));

        let isolated_release_runtime =
            PathBuf::from("C:/repo/daemon-rs/target-control-center-release/release/cortex.exe");
        assert!(!is_disallowed_daemon_binary_path(&isolated_release_runtime));

        let rtk_isolated = PathBuf::from("C:/repo/daemon-rs/target-rtk-isolated/debug/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&rtk_isolated));

        let codex_test = PathBuf::from("C:/repo/daemon-rs/target-codex-test/debug/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&codex_test));

        let target_build_script = PathBuf::from("C:/repo/daemon-rs/target/debug/build/cortex.exe");
        assert!(is_disallowed_daemon_binary_path(&target_build_script));

        let safe = PathBuf::from("C:/cortex-test/testuser/.cortex/bin/cortex.exe");
        assert!(!is_disallowed_daemon_binary_path(&safe));
    }

    #[test]
    fn local_start_timeout_cleanup_clears_managed_child_state() {
        let child = spawn_test_sleep_process();
        let pid = child.id();
        let state = DaemonState {
            exe_path: None,
            child: Mutex::new(Some(child)),
            intentional_stop: AtomicBool::new(false),
        };

        let (managed_before, _) = state.status().expect("initial status");
        assert!(managed_before);

        let message = local_app_managed_start_timeout_message(&state, Some(pid), 7437);
        assert!(message.contains("cleared the stale app-managed startup state"));

        let (managed_after, pid_after) = state.status().expect("post-cleanup status");
        assert!(!managed_after);
        assert_eq!(pid_after, None);
    }

    #[test]
    fn daemon_state_description_includes_starting_state() {
        let managed_message = describe_daemon_state(true, false, true, false, Some(42), 7437);
        assert!(managed_message.contains("still starting"));

        let external_message = describe_daemon_state(false, false, true, false, None, 7437);
        assert!(external_message.contains("still starting"));
    }

    #[test]
    fn local_probe_retry_requires_reachability_or_starting_signal() {
        assert!(local_probe_allows_starting_retry(
            &CortexReachabilityProbe {
                reachable: true,
                starting: false,
                identity_mismatch: false,
            }
        ));
        assert!(local_probe_allows_starting_retry(
            &CortexReachabilityProbe {
                reachable: false,
                starting: true,
                identity_mismatch: false,
            }
        ));
        assert!(!local_probe_allows_starting_retry(
            &CortexReachabilityProbe {
                reachable: false,
                starting: false,
                identity_mismatch: false,
            }
        ));
    }

    #[test]
    fn interpret_shutdown_response_surfaces_auth_rejection() {
        let err = interpret_shutdown_response(Ok(FetchCortexResponse {
            status: 401,
            body: "{\"error\":\"Unauthorized\"}".to_string(),
        }))
        .unwrap_err();

        assert!(err.contains("Refresh the token"));
    }

    #[test]
    fn partial_response_timeout_only_applies_when_bytes_exist() {
        let timeout = std::io::Error::new(std::io::ErrorKind::TimedOut, "timed out");
        let would_block = std::io::Error::new(std::io::ErrorKind::WouldBlock, "would block");
        let reset = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "reset");

        assert!(should_use_partial_response_on_read_timeout(&timeout, 8));
        assert!(should_use_partial_response_on_read_timeout(&would_block, 8));
        #[cfg(windows)]
        {
            let winsock_timeout = std::io::Error::from_raw_os_error(10060);
            assert!(should_use_partial_response_on_read_timeout(
                &winsock_timeout,
                8
            ));
        }
        assert!(!should_use_partial_response_on_read_timeout(&timeout, 0));
        assert!(!should_use_partial_response_on_read_timeout(&reset, 8));
    }

    #[test]
    fn extract_error_detail_prefers_json_error_field() {
        let detail = extract_error_detail("{\"error\":\"Unauthorized\"}").unwrap();
        assert_eq!(detail, "Unauthorized");
    }

    #[test]
    fn cortex_health_probe_accepts_healthy_response_shape() {
        assert!(is_cortex_health_response(
            200,
            r#"{"status":"ok","runtime":{"version":"0.5.0"},"stats":{"memories":1}}"#,
            None,
            None
        ));
        assert!(is_cortex_health_response(
            200,
            r#"{"status":"degraded","runtime":{"version":"0.5.0"},"stats":{"memories":1}}"#,
            None,
            None
        ));
    }

    #[test]
    fn cortex_health_probe_rejects_non_cortex_responses() {
        assert!(!is_cortex_health_response(
            200,
            "<html>ok</html>",
            None,
            None
        ));
        assert!(!is_cortex_health_response(
            200,
            r#"{"status":"ok"}"#,
            None,
            None
        ));
        assert!(!is_cortex_health_response(
            200,
            r#"{"status":"ok","runtime":{"version":"0.5.0"}}"#,
            None,
            None
        ));
        assert!(!is_cortex_health_response(
            503,
            r#"{"status":"ok","runtime":{}}"#,
            None,
            None
        ));
    }

    #[test]
    fn cortex_readiness_probe_accepts_ready_and_starting_payloads() {
        assert_eq!(
            cortex_readiness_state(
                200,
                r#"{"status":"ready","ready":true,"runtime":{"port":7437},"stats":{"home":"C:/cortex-test/testuser/.cortex"}}"#,
                Some(7437),
                None
            ),
            Some(true)
        );
        assert_eq!(
            cortex_readiness_state(
                503,
                r#"{"status":"starting","ready":false,"runtime":{"port":7437},"stats":{"home":"C:/cortex-test/testuser/.cortex"}}"#,
                Some(7437),
                None
            ),
            Some(false)
        );
    }

    #[test]
    fn cortex_readiness_probe_rejects_invalid_payloads() {
        assert_eq!(
            cortex_readiness_state(
                200,
                r#"{"status":"ready","runtime":{"port":7437},"stats":{"home":"C:/cortex-test/testuser/.cortex"}}"#,
                Some(7437),
                None
            ),
            None
        );
        assert_eq!(
            cortex_readiness_state(
                500,
                r#"{"status":"starting","ready":false,"runtime":{"port":7437},"stats":{"home":"C:/cortex-test/testuser/.cortex"}}"#,
                Some(7437),
                None
            ),
            None
        );
    }

    #[test]
    fn cortex_health_probe_rejects_identity_mismatch() {
        let expected = ResolvedCortexPaths {
            home: Some(PathBuf::from("C:/cortex-test/testuser/.cortex")),
            token: Some(PathBuf::from(
                "C:/cortex-test/testuser/.cortex/cortex.token",
            )),
            db: Some(PathBuf::from("C:/cortex-test/testuser/.cortex/cortex.db")),
            pid: Some(PathBuf::from("C:/cortex-test/testuser/.cortex/cortex.pid")),
            port: Some(7437),
            bind: Some("127.0.0.1".to_string()),
        };
        assert!(!is_cortex_health_response(
            200,
            r#"{"status":"ok","runtime":{"port":7437,"token_path":"C:/other/cortex.token","db_path":"C:/cortex-test/testuser/.cortex/cortex.db","pid_path":"C:/cortex-test/testuser/.cortex/cortex.pid"},"stats":{"home":"C:/cortex-test/testuser/.cortex","memories":1}}"#,
            Some(7437),
            Some(&expected)
        ));
        assert!(is_cortex_health_response(
            200,
            r#"{"status":"ok","runtime":{"port":7437,"token_path":"C:/cortex-test/testuser/.cortex/cortex.token","db_path":"C:/cortex-test/testuser/.cortex/cortex.db","pid_path":"C:/cortex-test/testuser/.cortex/cortex.pid"},"stats":{"home":"C:/cortex-test/testuser/.cortex","memories":1}}"#,
            Some(7437),
            Some(&expected)
        ));
    }

    #[test]
    fn readiness_identity_fallback_classifies_starting_payload_on_path_mismatch() {
        let expected = ResolvedCortexPaths {
            home: Some(PathBuf::from("C:/cortex-test/testuser/.cortex")),
            token: Some(PathBuf::from(
                "C:/cortex-test/testuser/.cortex/cortex.token",
            )),
            db: Some(PathBuf::from("C:/cortex-test/testuser/.cortex/cortex.db")),
            pid: Some(PathBuf::from("C:/cortex-test/testuser/.cortex/cortex.pid")),
            port: Some(7437),
            bind: Some("127.0.0.1".to_string()),
        };
        let (state, mismatch) = readiness_state_with_identity_fallback(
            503,
            r#"{"status":"starting","ready":false,"runtime":{"port":7437,"token_path":"C:/other/cortex.token","db_path":"C:/other/cortex.db","pid_path":"C:/other/cortex.pid"},"stats":{"home":"C:/other","memories":1}}"#,
            Some(7437),
            Some(&expected),
        );
        assert_eq!(state, Some(false));
        assert!(mismatch);
    }

    #[test]
    fn health_identity_fallback_detects_reachable_payload_on_path_mismatch() {
        let expected = ResolvedCortexPaths {
            home: Some(PathBuf::from("C:/cortex-test/testuser/.cortex")),
            token: Some(PathBuf::from(
                "C:/cortex-test/testuser/.cortex/cortex.token",
            )),
            db: Some(PathBuf::from("C:/cortex-test/testuser/.cortex/cortex.db")),
            pid: Some(PathBuf::from("C:/cortex-test/testuser/.cortex/cortex.pid")),
            port: Some(7437),
            bind: Some("127.0.0.1".to_string()),
        };
        let (healthy, mismatch) = health_state_with_identity_fallback(
            200,
            r#"{"status":"ok","runtime":{"port":7437,"token_path":"C:/other/cortex.token","db_path":"C:/other/cortex.db","pid_path":"C:/other/cortex.pid"},"stats":{"home":"C:/other","memories":1}}"#,
            Some(7437),
            Some(&expected),
        );
        assert!(healthy);
        assert!(mismatch);
    }

    #[test]
    fn editor_registration_uses_explicit_agent_args() {
        let home = Path::new("C:/cortex-test/testuser");
        let targets = editor_targets(home);
        let cursor = targets.iter().find(|target| target.id == "cursor").unwrap();
        let claude = targets
            .iter()
            .find(|target| target.id == "claude-code")
            .unwrap();

        assert_eq!(editor_args(cursor), ["mcp", "--agent", "cursor"]);
        assert_eq!(editor_args(claude), ["mcp", "--agent", "claude"]);
    }

    #[test]
    fn editor_registration_includes_attach_only_env_contract() {
        let home = Path::new("C:/cortex-test/testuser");
        let targets = editor_targets(home);
        let codex = targets.iter().find(|target| target.id == "codex").unwrap();

        let registration =
            cortex_mcp_registration(codex, "C:/cortex-test/testuser/.cortex/bin/cortex.exe");
        let cortex_entry = registration
            .as_object()
            .expect("registration should be an object");
        let args = cortex_entry
            .get("args")
            .and_then(|value| value.as_array())
            .expect("args should exist");
        assert_eq!(
            args.iter()
                .filter_map(|value| value.as_str())
                .collect::<Vec<_>>(),
            vec!["mcp", "--agent", "codex"]
        );

        let env = cortex_entry
            .get("env")
            .and_then(|value| value.as_object())
            .expect("env should exist");
        assert_eq!(
            env.get("CORTEX_APP_REQUIRED")
                .and_then(|value| value.as_str()),
            Some("1")
        );
        assert_eq!(
            env.get("CORTEX_DAEMON_OWNER_LOCAL_SPAWN")
                .and_then(|value| value.as_str()),
            Some("0")
        );
        assert_eq!(
            env.get("CORTEX_APP_CLIENT")
                .and_then(|value| value.as_str()),
            Some("codex")
        );
    }

    #[test]
    fn registration_matchers_require_attach_only_env_contract() {
        let home = Path::new("C:/cortex-test/testuser");
        let targets = editor_targets(home);
        let cursor = targets.iter().find(|target| target.id == "cursor").unwrap();
        let codex = targets.iter().find(|target| target.id == "codex").unwrap();

        let json_missing_env = serde_json::json!({
            "env": {
                "CORTEX_APP_REQUIRED": "1"
            }
        });
        assert!(!json_env_match(&json_missing_env, cursor));

        let json_ok = serde_json::json!({
            "env": {
                "CORTEX_APP_REQUIRED": "1",
                "CORTEX_DAEMON_OWNER_LOCAL_SPAWN": "0",
                "CORTEX_APP_CLIENT": "cursor"
            }
        });
        assert!(json_env_match(&json_ok, cursor));

        let toml_missing_env = toml::Value::Table(
            [(
                "env".to_string(),
                toml::Value::Table(
                    [(
                        "CORTEX_APP_REQUIRED".to_string(),
                        toml::Value::String("1".to_string()),
                    )]
                    .into_iter()
                    .collect(),
                ),
            )]
            .into_iter()
            .collect(),
        );
        assert!(!toml_env_match(&toml_missing_env, codex));

        let toml_ok = toml::Value::Table(
            [(
                "env".to_string(),
                toml::Value::Table(
                    [
                        (
                            "CORTEX_APP_REQUIRED".to_string(),
                            toml::Value::String("1".to_string()),
                        ),
                        (
                            "CORTEX_DAEMON_OWNER_LOCAL_SPAWN".to_string(),
                            toml::Value::String("0".to_string()),
                        ),
                        (
                            "CORTEX_APP_CLIENT".to_string(),
                            toml::Value::String("codex".to_string()),
                        ),
                    ]
                    .into_iter()
                    .collect(),
                ),
            )]
            .into_iter()
            .collect(),
        );
        assert!(toml_env_match(&toml_ok, codex));
    }

    #[test]
    fn gemini_prefers_nested_mcp_config_when_present() {
        let temp_root = std::env::temp_dir().join(format!(
            "cortex_control_center_editor_test_{}",
            std::process::id()
        ));
        let gemini_nested = temp_root.join(".gemini").join("settings").join("mcp.json");
        let gemini_legacy = temp_root.join(".gemini").join("settings.json");
        fs::create_dir_all(gemini_nested.parent().unwrap()).expect("create gemini settings dir");
        fs::write(&gemini_nested, "{}").expect("write nested gemini config");
        fs::write(&gemini_legacy, "{}").expect("write legacy gemini config");

        let targets = editor_targets(&temp_root);
        let gemini = targets.iter().find(|target| target.id == "gemini").unwrap();

        assert_eq!(editor_config_path(gemini), gemini_nested);

        let _ = fs::remove_file(gemini_nested);
        let _ = fs::remove_file(gemini_legacy);
        let _ = fs::remove_dir_all(temp_root.join(".gemini"));
        let _ = fs::remove_dir(temp_root);
    }

    #[test]
    fn claude_desktop_uses_platform_specific_config_path() {
        let home = Path::new("/tmp/cortex-home");
        let expected = if cfg!(windows) {
            home.join("AppData")
                .join("Roaming")
                .join("Claude")
                .join("claude_desktop_config.json")
        } else if cfg!(target_os = "macos") {
            home.join("Library")
                .join("Application Support")
                .join("Claude")
                .join("claude_desktop_config.json")
        } else {
            home.join(".config")
                .join("Claude")
                .join("claude_desktop_config.json")
        };

        let targets = editor_targets(home);
        let claude_desktop = targets
            .iter()
            .find(|target| target.id == "claude-desktop")
            .unwrap();

        assert_eq!(claude_desktop.config_path, expected);
    }
}
