use serde_json::{Map, Value, json};

use super::{
    TOOL_ENVELOPE_MAX_BYTES, annotate_truncation, enforce_tool_payload_limit,
    is_tool_exposed_on_public_surface, payload_reload_hint, serialized_json_size_bytes,
    summarize_json_shape, tool_text_content,
};

fn tool_uses_flattened_payload_budget(tool_name: &str) -> bool {
    is_tool_exposed_on_public_surface(tool_name)
}

pub(super) fn public_tool_forbids_payload_truncation(tool_name: &str) -> bool {
    matches!(tool_name, "read_query" | "analyze")
}

pub(super) fn finalize_tool_envelope_for_budget(tool_name: &str, envelope: Value) -> Value {
    if tool_uses_flattened_payload_budget(tool_name) {
        envelope
    } else {
        enforce_tool_payload_limit(tool_name, envelope)
    }
}

pub(super) fn build_tool_call_response(tool_name: &str, envelope: Value, is_error: bool) -> Value {
    let flattened = enforce_flattened_tool_payload_limit(
        tool_name,
        flatten_public_tool_response(tool_name, &envelope, is_error),
        &envelope,
    );
    let text = tool_text_content(tool_name, &flattened);
    let response_is_error = flattened
        .get("status")
        .and_then(Value::as_str)
        .is_some_and(|status| status.eq_ignore_ascii_case("error"));

    if response_is_error {
        json!({
            "isError": true,
            "content": [{ "type": "text", "text": text }],
            "structuredContent": flattened
        })
    } else {
        json!({
            "content": [{ "type": "text", "text": text }],
            "structuredContent": flattened
        })
    }
}

pub(super) fn flatten_public_tool_response(
    tool_name: &str,
    envelope: &Value,
    is_error: bool,
) -> Value {
    let mut output = Map::new();
    output.insert(
        "status".to_string(),
        Value::String(flattened_public_status(envelope, is_error).to_string()),
    );
    output.insert("result".to_string(), extract_public_result(envelope));
    propagate_public_result_metadata(&mut output, envelope);

    if let Some(error) = extract_public_error(envelope) {
        output.insert("error".to_string(), error);
    }

    if let Some(next_action) = extract_public_next_action(tool_name, envelope) {
        output.insert("next_action".to_string(), Value::String(next_action));
    }

    if let Some(continuation) = extract_public_continuation(tool_name, envelope) {
        output.insert("continuation".to_string(), continuation);
    }

    let warnings = collect_public_warnings(tool_name, envelope);
    if !warnings.is_empty() {
        output.insert(
            "warnings".to_string(),
            Value::Array(warnings.into_iter().map(Value::String).collect()),
        );
    }

    if let Some(user_notice) = extract_public_user_notice(envelope) {
        output.insert("user_notice".to_string(), user_notice);
    }

    Value::Object(output)
}

fn propagate_public_result_metadata(output: &mut Map<String, Value>, envelope: &Value) {
    let body = envelope
        .pointer("/data/response/body")
        .or_else(|| envelope.pointer("/response/body"));
    let Some(body) = body.and_then(Value::as_object) else {
        return;
    };
    for field in [
        "response_form",
        "form_version",
        "response_family",
        "completeness_scope",
        "response_kind",
        "execution_mode",
        "response_form_contract_ref",
    ] {
        if let Some(value) = body.get(field) {
            output.insert(field.to_string(), value.clone());
        }
    }
}

fn flattened_public_status(envelope: &Value, is_error: bool) -> &'static str {
    if is_error
        || envelope
            .get("status")
            .and_then(Value::as_str)
            .is_some_and(|status| status.eq_ignore_ascii_case("error"))
        || envelope
            .pointer("/data/response/status")
            .and_then(Value::as_u64)
            .is_some_and(|status| status >= 400)
        || envelope
            .pointer("/response/status")
            .and_then(Value::as_u64)
            .is_some_and(|status| status >= 400)
    {
        "error"
    } else {
        "ok"
    }
}

fn extract_public_result(envelope: &Value) -> Value {
    if let Some(body) = envelope.pointer("/data/response/body") {
        return body.clone();
    }
    if let Some(body) = envelope.pointer("/response/body") {
        return body.clone();
    }
    if let Some(summary) = envelope.get("data_summary") {
        return summary.clone();
    }
    if let Some(data) = envelope.get("data") {
        return data.clone();
    }
    Value::Null
}

fn extract_public_error(envelope: &Value) -> Option<Value> {
    if let Some(error) = envelope.get("error") {
        return Some(error.clone());
    }
    let status = envelope
        .pointer("/data/response/status")
        .and_then(Value::as_u64)
        .or_else(|| envelope.pointer("/response/status").and_then(Value::as_u64))?;
    if status < 400 {
        return None;
    }
    envelope
        .pointer("/data/response/body")
        .cloned()
        .or_else(|| envelope.pointer("/response/body").cloned())
}

fn extract_public_next_action(tool_name: &str, envelope: &Value) -> Option<String> {
    if tool_name == "analyze"
        && envelope
            .pointer("/data/response/body/agent_usage/answer_complete")
            .or_else(|| envelope.pointer("/response/body/agent_usage/answer_complete"))
            .and_then(Value::as_bool)
            .unwrap_or(false)
    {
        return Some(
            "Stop tool use for this analysis and write the final answer from the analyze payload. Do not call read_query or analyze_catalog for enrichment unless the payload explicitly asks for a concrete follow-up."
                .to_string(),
        );
    }

    let direct = [
        "/error/details/next_action",
        "/error/docs_hint",
        "/data/contract/continuation/agent_instruction",
        "/data/response/body/persist_intent/user_prompt",
        "/data/response/body/workflow_gate/next_action",
    ];

    for path in direct {
        if let Some(value) = envelope
            .pointer(path)
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
        {
            return Some(value.to_string());
        }
    }

    let continuation = envelope
        .pointer("/data/response/body/continuation")
        .filter(|value| !value.is_null())?;
    if continuation
        .get("has_more")
        .and_then(Value::as_bool)
        .unwrap_or(false)
    {
        Some(format!(
            "Call {tool_name} again with the returned continuation cursor on the same bounded query."
        ))
    } else {
        None
    }
}

fn extract_public_continuation(tool_name: &str, envelope: &Value) -> Option<Value> {
    if let Some(continuation) = envelope.pointer("/data/contract/continuation") {
        let status = continuation
            .get("status")
            .and_then(Value::as_str)
            .unwrap_or_default();
        if !status.is_empty() && !status.eq_ignore_ascii_case("none") {
            return Some(continuation.clone());
        }
    }

    for path in [
        "/data/response/body/continuation",
        "/response/body/continuation",
    ] {
        if let Some(continuation) = envelope.pointer(path)
            && continuation
                .get("has_more")
                .and_then(Value::as_bool)
                .unwrap_or(false)
        {
            return Some(continuation.clone());
        }
    }

    if tool_name == "analyze" {
        for path in [
            "/data/response/body/result_view/driver_results/continuation",
            "/response/body/result_view/driver_results/continuation",
            "/data/response/body/driver_results/continuation",
            "/response/body/driver_results/continuation",
        ] {
            if let Some(continuation) = envelope.pointer(path)
                && continuation
                    .get("has_more")
                    .and_then(Value::as_bool)
                    .unwrap_or(false)
            {
                return Some(continuation.clone());
            }
        }
    }

    None
}

fn collect_public_warnings(tool_name: &str, envelope: &Value) -> Vec<String> {
    let mut warnings = Vec::<String>::new();

    if envelope
        .get("status")
        .and_then(Value::as_str)
        .is_some_and(|status| status.eq_ignore_ascii_case("complete_with_fallback"))
    {
        warnings.push(
            "A compatibility fallback was applied; the result is usable, but it did not use the preferred direct path."
                .to_string(),
        );
    }

    if envelope
        .get("truncated")
        .and_then(Value::as_bool)
        .unwrap_or(false)
    {
        let message = envelope
            .pointer("/truncation/details_hint")
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(ToString::to_string)
            .unwrap_or_else(|| {
                format!("The {tool_name} result was trimmed to stay within the payload limit.")
            });
        warnings.push(message);
    }

    if let Some(items) = envelope
        .pointer("/data/response/body/warnings")
        .and_then(Value::as_array)
    {
        for item in items.iter().take(4) {
            if let Some(text) = item
                .as_str()
                .map(str::trim)
                .filter(|value| !value.is_empty())
            {
                warnings.push(text.to_string());
            }
        }
    }

    warnings.truncate(4);
    warnings
}

fn enforce_flattened_tool_payload_limit(tool: &str, flattened: Value, envelope: &Value) -> Value {
    let original_bytes = serialized_json_size_bytes(&flattened);
    if original_bytes <= TOOL_ENVELOPE_MAX_BYTES {
        return flattened;
    }

    let mut reduced = flattened.clone();
    if let Some(obj) = reduced.as_object_mut() {
        obj.remove("warnings");
        obj.remove("user_notice");
    }
    let reduced_bytes = serialized_json_size_bytes(&reduced);
    if reduced_bytes <= TOOL_ENVELOPE_MAX_BYTES {
        return reduced;
    }
    if public_tool_forbids_payload_truncation(tool) {
        return public_payload_contract_overflow(
            tool,
            &reduced,
            envelope,
            original_bytes,
            reduced_bytes,
        );
    }

    let result_summary = reduced
        .get("result")
        .map(summarize_json_shape)
        .unwrap_or(Value::Null);
    let mut fallback = Map::new();
    fallback.insert(
        "status".to_string(),
        reduced
            .get("status")
            .cloned()
            .unwrap_or_else(|| Value::String("ok".to_string())),
    );
    if let Some(error) = reduced.get("error") {
        fallback.insert("error".to_string(), error.clone());
    }
    if let Some(next_action) = reduced.get("next_action") {
        fallback.insert("next_action".to_string(), next_action.clone());
    }
    if let Some(continuation) = reduced.get("continuation") {
        fallback.insert("continuation".to_string(), continuation.clone());
    }
    for field in [
        "response_form",
        "form_version",
        "response_family",
        "completeness_scope",
        "response_kind",
        "execution_mode",
        "response_form_contract_ref",
    ] {
        if let Some(value) = reduced.get(field) {
            fallback.insert(field.to_string(), value.clone());
        }
    }
    fallback.insert("result".to_string(), result_summary);
    let hint = payload_reload_hint(tool, Some(envelope));
    fallback.insert(
        "warnings".to_string(),
        json!([format!(
            "The {tool} result exceeded the payload budget after flattening. Retry with narrower scope or continuation. {hint}"
        )]),
    );
    let mut fallback_value = Value::Object(fallback);
    let fallback_bytes = serialized_json_size_bytes(&fallback_value);
    annotate_truncation(
        &mut fallback_value,
        "flattened_summary_only",
        original_bytes,
        fallback_bytes,
        hint,
    );
    fallback_value
}

pub(super) fn public_payload_contract_overflow(
    tool: &str,
    reduced: &Value,
    envelope: &Value,
    original_bytes: usize,
    reduced_bytes: usize,
) -> Value {
    let result_summary = reduced
        .get("result")
        .map(summarize_json_shape)
        .unwrap_or(Value::Null);
    let continuation = reduced
        .get("continuation")
        .cloned()
        .or_else(|| extract_public_continuation(tool, envelope));
    let next_action = if continuation.is_some() {
        if tool == "analyze" {
            "Call analyze again with the returned continuation.next_request or result_page cursor to fetch the next bounded result page."
        } else {
            "Call read_query again with the returned continuation cursor on the same bounded query."
        }
    } else if tool == "read_query" {
        "Retry read_query with a smaller limit, narrower filters, or a lookup/detail request. The backend should expose a cursor before the payload reaches this guard."
    } else {
        "Retry analyze with a smaller result_page.limit or narrower candidate_space. The backend should expose result_page continuation before the payload reaches this guard."
    };

    let mut fallback = Map::new();
    fallback.insert("status".to_string(), Value::String("error".to_string()));
    fallback.insert("result".to_string(), Value::Null);
    fallback.insert("next_action".to_string(), json!(next_action));
    if let Some(continuation) = continuation {
        fallback.insert("continuation".to_string(), continuation);
    }
    for field in [
        "response_form",
        "form_version",
        "response_family",
        "completeness_scope",
        "response_kind",
        "execution_mode",
        "response_form_contract_ref",
    ] {
        if let Some(value) = reduced.get(field) {
            fallback.insert(field.to_string(), value.clone());
        }
    }
    fallback.insert(
        "error".to_string(),
        json!({
            "reason_code": "payload_contract_overflow",
            "message": format!("{tool} produced an oversized public response after bounded shaping."),
            "limit_bytes": TOOL_ENVELOPE_MAX_BYTES,
            "original_bytes": original_bytes,
            "reduced_bytes": reduced_bytes,
            "result_shape": result_summary,
            "expected_resolution": "Return a bounded page, cursor, result_page, or exact detail handle before the transport budget is reached."
        }),
    );
    Value::Object(fallback)
}

fn extract_public_user_notice(envelope: &Value) -> Option<Value> {
    let relay = envelope.pointer("/data/user_notice_relay")?;
    if relay
        .get("status")
        .and_then(Value::as_str)
        .is_some_and(|status| status.eq_ignore_ascii_case("relay_required"))
    {
        Some(relay.clone())
    } else {
        None
    }
}
