use rmcp::ErrorData;
use serde_json::Value;

use crate::server::helpers::{get_bool, get_int, get_str, get_str_array};
use crate::tools::LeanCtxServer;

fn auto_degrade_read_mode(mode: &str) -> String {
    use crate::core::degradation_policy::DegradationVerdictV1;
    let profile = crate::core::profiles::active_profile();
    if !profile.degradation.enforce_effective() {
        return mode.to_string();
    }
    let policy = crate::core::degradation_policy::evaluate_v1_for_tool("ctx_read", None);
    match policy.decision.verdict {
        DegradationVerdictV1::Ok => mode.to_string(),
        DegradationVerdictV1::Warn => match mode {
            "full" => "map".to_string(),
            other => other.to_string(),
        },
        DegradationVerdictV1::Throttle => match mode {
            "full" | "map" => "signatures".to_string(),
            other => other.to_string(),
        },
        DegradationVerdictV1::Block => "signatures".to_string(),
    }
}

impl LeanCtxServer {
    pub(crate) async fn dispatch_read_tools(
        &self,
        name: &str,
        args: Option<&serde_json::Map<String, Value>>,
        minimal: bool,
    ) -> Result<String, ErrorData> {
        Ok(match name {
            "ctx_read" => {
                let path = match get_str(args, "path") {
                    Some(p) => self
                        .resolve_path(&p)
                        .await
                        .map_err(|e| ErrorData::invalid_params(e, None))?,
                    None => return Err(ErrorData::invalid_params("path is required", None)),
                };
                let current_task = {
                    let session = self.session.read().await;
                    session.task.as_ref().map(|t| t.description.clone())
                };
                let task_ref = current_task.as_deref();
                let profile = crate::core::profiles::active_profile();
                let mut mode = if let Some(m) = get_str(args, "mode") {
                    m
                } else if profile.read.default_mode_effective() == "auto" {
                    let cache = self.cache.read().await;
                    crate::tools::ctx_smart_read::select_mode_with_task(&cache, &path, task_ref)
                } else {
                    profile.read.default_mode_effective().to_string()
                };
                let mut fresh = get_bool(args, "fresh").unwrap_or(false);
                let start_line = get_int(args, "start_line");
                if let Some(sl) = start_line {
                    let sl = sl.max(1_i64);
                    mode = format!("lines:{sl}-999999");
                    fresh = true;
                }
                let mode = if crate::tools::ctx_read::is_instruction_file(&path) {
                    "full".to_string()
                } else {
                    auto_degrade_read_mode(&mode)
                };
                let stale = self.is_prompt_cache_stale().await;
                let effective_mode = LeanCtxServer::upgrade_mode_if_stale(&mode, stale).to_string();
                if mode.starts_with("lines:") {
                    fresh = true;
                }
                if stale && effective_mode == "full" && !fresh {
                    fresh = true;
                }
                if crate::core::binary_detect::is_binary_file(&path) {
                    let msg = crate::core::binary_detect::binary_file_message(&path);
                    return Err(ErrorData::invalid_params(msg, None));
                }
                {
                    let cap = crate::core::limits::max_read_bytes() as u64;
                    if let Ok(meta) = std::fs::metadata(&path) {
                        if meta.len() > cap {
                            let msg = format!(
                                "File too large ({} bytes, limit {} bytes via LCTX_MAX_READ_BYTES). \
                                 Use mode=\"lines:1-100\" for partial reads or increase the limit.",
                                meta.len(), cap
                            );
                            return Err(ErrorData::invalid_params(msg, None));
                        }
                    }
                }

                let read_start = std::time::Instant::now();
                let mut cache = self.cache.write().await;
                let read_output = if fresh {
                    crate::tools::ctx_read::handle_fresh_with_task_resolved(
                        &mut cache,
                        &path,
                        &effective_mode,
                        crate::tools::CrpMode::effective(),
                        task_ref,
                    )
                } else {
                    crate::tools::ctx_read::handle_with_task_resolved(
                        &mut cache,
                        &path,
                        &effective_mode,
                        crate::tools::CrpMode::effective(),
                        task_ref,
                    )
                };
                let output = read_output.content;
                let resolved_mode = read_output.resolved_mode;
                let stale_note = if !minimal && effective_mode != mode {
                    format!("[cache stale, {mode}→{effective_mode}]\n")
                } else {
                    String::new()
                };
                let original = cache.get(&path).map_or(0, |e| e.original_tokens);
                let is_cache_hit = output.contains(" cached ");
                let output = format!("{stale_note}{output}");
                let output_tokens = crate::core::tokens::count_tokens(&output);
                let saved = original.saturating_sub(output_tokens);
                let file_ref = cache.file_ref_map().get(&path).cloned();
                drop(cache);
                let mut ensured_root: Option<String> = None;
                {
                    let mut session = self.session.write().await;
                    session.touch_file(&path, file_ref.as_deref(), &resolved_mode, original);
                    if is_cache_hit {
                        session.record_cache_hit();
                    }
                    if session.active_structured_intent.is_none()
                        && session.files_touched.len() >= 2
                    {
                        let touched: Vec<String> = session
                            .files_touched
                            .iter()
                            .map(|f| f.path.clone())
                            .collect();
                        let inferred =
                            crate::core::intent_engine::StructuredIntent::from_file_patterns(
                                &touched,
                            );
                        if inferred.confidence >= 0.4 {
                            session.active_structured_intent = Some(inferred);
                        }
                    }
                    let root_missing = session
                        .project_root
                        .as_deref()
                        .is_none_or(|r| r.trim().is_empty());
                    if root_missing {
                        if let Some(root) = crate::core::protocol::detect_project_root(&path) {
                            session.project_root = Some(root.clone());
                            ensured_root = Some(root.clone());
                            let mut current = self.agent_id.write().await;
                            if current.is_none() {
                                let mut registry =
                                    crate::core::agents::AgentRegistry::load_or_create();
                                registry.cleanup_stale(24);
                                let role = std::env::var("LEAN_CTX_AGENT_ROLE").ok();
                                let id = registry.register("mcp", role.as_deref(), &root);
                                let _ = registry.save();
                                *current = Some(id);
                            }
                        }
                    }
                }
                if let Some(root) = ensured_root.as_deref() {
                    crate::core::index_orchestrator::ensure_all_background(root);
                }
                self.record_call_with_path(
                    "ctx_read",
                    original,
                    saved,
                    Some(resolved_mode.clone()),
                    Some(&path),
                )
                .await;
                crate::core::heatmap::record_file_access(&path, original, saved);
                {
                    let mut ledger = self.ledger.write().await;
                    ledger.record(&path, &resolved_mode, original, output_tokens);
                    ledger.save();
                }
                {
                    let duration = read_start.elapsed();
                    let duration_us = duration.as_micros() as u64;

                    let mut stats = self.pipeline_stats.write().await;
                    stats.record(&[crate::core::pipeline::LayerMetrics::new(
                        crate::core::pipeline::LayerKind::Compression,
                        original,
                        output_tokens,
                        duration_us,
                    )]);
                    stats.save();

                    if let Some(ref ir_lock) = self.context_ir {
                        let client_name = { self.client_name.read().await.clone() };
                        let agent_id = { self.agent_id.read().await.clone() };
                        let mut ir = ir_lock.write().await;
                        ir.record(crate::core::context_ir::RecordIrInput {
                            kind: crate::core::context_ir::ContextIrSourceKindV1::Read,
                            tool: "ctx_read",
                            client_name: if client_name.trim().is_empty() {
                                None
                            } else {
                                Some(client_name)
                            },
                            agent_id,
                            path: Some(&path),
                            command: None,
                            pattern: None,
                            input_tokens: original,
                            output_tokens,
                            duration,
                            content_excerpt: &output,
                        });
                        ir.save();
                    }
                }
                {
                    let sig =
                        crate::core::mode_predictor::FileSignature::from_path(&path, original);
                    let density = if output_tokens > 0 {
                        original as f64 / output_tokens as f64
                    } else {
                        1.0
                    };
                    let outcome = crate::core::mode_predictor::ModeOutcome {
                        mode: resolved_mode.clone(),
                        tokens_in: original,
                        tokens_out: output_tokens,
                        density: density.min(1.0),
                    };
                    let project_root = {
                        let session = self.session.read().await;
                        session
                            .project_root
                            .clone()
                            .unwrap_or_else(|| ".".to_string())
                    };
                    let mut predictor = crate::core::mode_predictor::ModePredictor::new();
                    predictor.set_project_root(&project_root);
                    predictor.record(sig, outcome);
                    predictor.save();

                    let ext = std::path::Path::new(&path)
                        .extension()
                        .and_then(|e| e.to_str())
                        .unwrap_or("")
                        .to_string();
                    let thresholds = crate::core::adaptive_thresholds::thresholds_for_path(&path);
                    let cache = self.cache.read().await;
                    let stats = cache.get_stats();
                    let feedback_outcome = crate::core::feedback::CompressionOutcome {
                        session_id: format!("{}", std::process::id()),
                        language: ext,
                        entropy_threshold: thresholds.bpe_entropy,
                        jaccard_threshold: thresholds.jaccard,
                        total_turns: stats.total_reads as u32,
                        tokens_saved: saved as u64,
                        tokens_original: original as u64,
                        cache_hits: stats.cache_hits as u32,
                        total_reads: stats.total_reads as u32,
                        task_completed: true,
                        timestamp: chrono::Local::now().to_rfc3339(),
                    };
                    drop(cache);
                    let mut store = crate::core::feedback::FeedbackStore::load();
                    store.project_root = Some(project_root.clone());
                    store.record_outcome(feedback_outcome);
                }
                output
            }
            "ctx_multi_read" => {
                let raw_paths = get_str_array(args, "paths")
                    .ok_or_else(|| ErrorData::invalid_params("paths array is required", None))?;
                let mut paths = Vec::with_capacity(raw_paths.len());
                let cap = crate::core::limits::max_read_bytes() as u64;
                for p in raw_paths {
                    let resolved = self
                        .resolve_path(&p)
                        .await
                        .map_err(|e| ErrorData::invalid_params(e, None))?;
                    if crate::core::binary_detect::is_binary_file(&resolved) {
                        continue;
                    }
                    if let Ok(meta) = std::fs::metadata(&resolved) {
                        if meta.len() > cap {
                            continue;
                        }
                    }
                    paths.push(resolved);
                }
                if paths.is_empty() {
                    return Err(ErrorData::invalid_params(
                        "all paths are binary or exceed the size limit",
                        None,
                    ));
                }
                let mode = get_str(args, "mode").unwrap_or_else(|| {
                    let p = crate::core::profiles::active_profile();
                    let dm = p.read.default_mode_effective();
                    if dm == "auto" {
                        "full".to_string()
                    } else {
                        dm.to_string()
                    }
                });
                let current_task = {
                    let session = self.session.read().await;
                    session.task.as_ref().map(|t| t.description.clone())
                };
                let mut cache = self.cache.write().await;
                let output = crate::tools::ctx_multi_read::handle_with_task(
                    &mut cache,
                    &paths,
                    &mode,
                    crate::tools::CrpMode::effective(),
                    current_task.as_deref(),
                );
                let mut total_original: usize = 0;
                for path in &paths {
                    total_original = total_original
                        .saturating_add(cache.get(path).map_or(0, |e| e.original_tokens));
                }
                let tokens = crate::core::tokens::count_tokens(&output);
                drop(cache);
                self.record_call(
                    "ctx_multi_read",
                    total_original,
                    total_original.saturating_sub(tokens),
                    Some(mode),
                )
                .await;
                output
            }
            "ctx_smart_read" => {
                let path = match get_str(args, "path") {
                    Some(p) => self
                        .resolve_path(&p)
                        .await
                        .map_err(|e| ErrorData::invalid_params(e, None))?,
                    None => return Err(ErrorData::invalid_params("path is required", None)),
                };
                if crate::core::binary_detect::is_binary_file(&path) {
                    let msg = crate::core::binary_detect::binary_file_message(&path);
                    return Err(ErrorData::invalid_params(msg, None));
                }
                {
                    let cap = crate::core::limits::max_read_bytes() as u64;
                    if let Ok(meta) = std::fs::metadata(&path) {
                        if meta.len() > cap {
                            let msg = format!(
                                "File too large ({} bytes, limit {} bytes via LCTX_MAX_READ_BYTES). \
                                 Use mode=\"lines:1-100\" for partial reads or increase the limit.",
                                meta.len(), cap
                            );
                            return Err(ErrorData::invalid_params(msg, None));
                        }
                    }
                }
                let mut cache = self.cache.write().await;
                let output = crate::tools::ctx_smart_read::handle(
                    &mut cache,
                    &path,
                    crate::tools::CrpMode::effective(),
                );
                let original = cache.get(&path).map_or(0, |e| e.original_tokens);
                let tokens = crate::core::tokens::count_tokens(&output);
                drop(cache);
                self.record_call_with_path(
                    "ctx_smart_read",
                    original,
                    original.saturating_sub(tokens),
                    Some("auto".to_string()),
                    Some(&path),
                )
                .await;
                output
            }
            "ctx_delta" => {
                let path = match get_str(args, "path") {
                    Some(p) => self
                        .resolve_path(&p)
                        .await
                        .map_err(|e| ErrorData::invalid_params(e, None))?,
                    None => return Err(ErrorData::invalid_params("path is required", None)),
                };
                let mut cache = self.cache.write().await;
                let output = crate::tools::ctx_delta::handle(&mut cache, &path);
                let original = cache.get(&path).map_or(0, |e| e.original_tokens);
                let tokens = crate::core::tokens::count_tokens(&output);
                drop(cache);
                {
                    let mut session = self.session.write().await;
                    session.mark_modified(&path);
                }
                self.record_call_with_path(
                    "ctx_delta",
                    original,
                    original.saturating_sub(tokens),
                    Some("delta".to_string()),
                    Some(&path),
                )
                .await;
                output
            }
            "ctx_edit" => {
                let path = match get_str(args, "path") {
                    Some(p) => self
                        .resolve_path(&p)
                        .await
                        .map_err(|e| ErrorData::invalid_params(e, None))?,
                    None => return Err(ErrorData::invalid_params("path is required", None)),
                };
                let old_string = get_str(args, "old_string").unwrap_or_default();
                let new_string = get_str(args, "new_string")
                    .ok_or_else(|| ErrorData::invalid_params("new_string is required", None))?;
                let replace_all = args
                    .as_ref()
                    .and_then(|a| a.get("replace_all"))
                    .and_then(serde_json::Value::as_bool)
                    .unwrap_or(false);
                let create = args
                    .as_ref()
                    .and_then(|a| a.get("create"))
                    .and_then(serde_json::Value::as_bool)
                    .unwrap_or(false);
                let expected_md5 = get_str(args, "expected_md5");
                let expected_size =
                    get_int(args, "expected_size").and_then(|v| u64::try_from(v).ok());
                let expected_mtime_ms =
                    get_int(args, "expected_mtime_ms").and_then(|v| u64::try_from(v).ok());
                let backup = get_bool(args, "backup").unwrap_or(false);
                let backup_path = match get_str(args, "backup_path") {
                    Some(p) => Some(
                        self.resolve_path(&p)
                            .await
                            .map_err(|e| ErrorData::invalid_params(e, None))?,
                    ),
                    None => None,
                };
                let evidence = get_bool(args, "evidence").unwrap_or(true);
                let diff_max_lines = get_int(args, "diff_max_lines")
                    .and_then(|v| usize::try_from(v.max(0)).ok())
                    .unwrap_or(200);
                let allow_lossy_utf8 = get_bool(args, "allow_lossy_utf8").unwrap_or(false);

                let mut cache = self.cache.write().await;
                let output = crate::tools::ctx_edit::handle(
                    &mut cache,
                    &crate::tools::ctx_edit::EditParams {
                        path: path.clone(),
                        old_string,
                        new_string,
                        replace_all,
                        create,
                        expected_md5,
                        expected_size,
                        expected_mtime_ms,
                        backup,
                        backup_path,
                        evidence,
                        diff_max_lines,
                        allow_lossy_utf8,
                    },
                );
                drop(cache);

                {
                    let mut session = self.session.write().await;
                    session.mark_modified(&path);
                }
                self.record_call_with_path("ctx_edit", 0, 0, None, Some(&path))
                    .await;
                output
            }
            "ctx_fill" => {
                let raw_paths = get_str_array(args, "paths")
                    .ok_or_else(|| ErrorData::invalid_params("paths array is required", None))?;
                let mut paths = Vec::with_capacity(raw_paths.len());
                for p in raw_paths {
                    paths.push(
                        self.resolve_path(&p)
                            .await
                            .map_err(|e| ErrorData::invalid_params(e, None))?,
                    );
                }
                let budget = get_int(args, "budget")
                    .ok_or_else(|| ErrorData::invalid_params("budget is required", None))?
                    as usize;
                let task = get_str(args, "task");
                let mut cache = self.cache.write().await;
                let output = crate::tools::ctx_fill::handle(
                    &mut cache,
                    &paths,
                    budget,
                    crate::tools::CrpMode::effective(),
                    task.as_deref(),
                );
                drop(cache);
                self.record_call("ctx_fill", 0, 0, Some(format!("budget:{budget}")))
                    .await;
                output
            }
            _ => unreachable!("dispatch_read_tools called with unknown tool: {name}"),
        })
    }
}
