use crate::proxy::mappers::claude::models::*;
use crate::proxy::mappers::signature_store::get_thought_signature;
use crate::proxy::mappers::tool_result_compressor;
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};

const MAX_TOOL_RESULT_CHARS: usize = 200_000;

pub(super) struct GoogleContentsOptions<'a> {
    pub is_thinking_enabled: bool,
    pub allow_dummy_thought: bool,
    pub mapped_model: &'a str,
    pub session_id: &'a str,
    pub is_retry: bool,
}

struct BuildContentsContext<'a> {
    is_thinking_enabled: bool,
    session_id: &'a str,
    allow_dummy_thought: bool,
    is_retry: bool,
    tool_name_to_schema: &'a HashMap<String, Value>,
    mapped_model: &'a str,
    existing_tool_result_ids: &'a HashSet<String>,
}

struct BuildContentsState<'a> {
    tool_id_to_name: &'a mut HashMap<String, String>,
    last_thought_signature: &'a mut Option<String>,
    pending_tool_use_ids: &'a mut Vec<String>,
    last_user_task_text_normalized: &'a mut Option<String>,
    previous_was_tool_result: &'a mut bool,
}

fn build_contents(
    content: &MessageContent,
    is_assistant: bool,
    ctx: &BuildContentsContext<'_>,
    state: &mut BuildContentsState<'_>,
) -> Result<Vec<Value>, String> {
    let mut parts = Vec::new();
    let mut current_turn_tool_result_ids = HashSet::new();
    let mut saw_non_thinking = false;

    match content {
        MessageContent::String(text) => {
            if text != "(no content)" && !text.trim().is_empty() {
                parts.push(json!({"text": text.trim()}));
            }
        }
        MessageContent::Array(blocks) => {
            for item in blocks {
                match item {
                    ContentBlock::Text { text } => {
                        if text != "(no content)" {
                            if !is_assistant && *state.previous_was_tool_result {
                                if let Some(last_task) = state.last_user_task_text_normalized {
                                    let current_normalized =
                                        text.replace(|c: char| c.is_whitespace(), "");
                                    if !current_normalized.is_empty()
                                        && current_normalized == *last_task
                                    {
                                        tracing::info!("[Claude-Request] Dropping duplicated task text echo (len: {})", text.len());
                                        continue;
                                    }
                                }
                            }

                            parts.push(json!({"text": text}));
                            saw_non_thinking = true;
                            if !is_assistant {
                                *state.last_user_task_text_normalized =
                                    Some(text.replace(|c: char| c.is_whitespace(), ""));
                            }
                            *state.previous_was_tool_result = false;
                        }
                    }
                    ContentBlock::Thinking {
                        thinking,
                        signature,
                        ..
                    } => {
                        tracing::debug!(
                            "[DEBUG-TRANSFORM] Processing thinking block. Sig: {:?}",
                            signature
                        );
                        if saw_non_thinking || !parts.is_empty() {
                            tracing::warn!("[Claude-Request] Thinking block found at non-zero index (prev parts: {}). Downgrading to Text.", parts.len());
                            if !thinking.is_empty() {
                                parts.push(json!({
                                    "text": thinking
                                }));
                                saw_non_thinking = true;
                            }
                            continue;
                        }
                        if !ctx.is_thinking_enabled {
                            tracing::warn!("[Claude-Request] Thinking disabled. Downgrading thinking block to text.");
                            if !thinking.is_empty() {
                                parts.push(json!({
                                    "text": thinking
                                }));
                            }
                            continue;
                        }
                        if thinking.is_empty() {
                            tracing::warn!("[Claude-Request] Empty thinking block detected. Downgrading to Text.");
                            parts.push(json!({
                                "text": "..."
                            }));
                            continue;
                        }
                        if let Some(sig) = signature {
                            if sig.len() < super::thinking::MIN_SIGNATURE_LENGTH {
                                tracing::warn!(
                                    "[Thinking-Signature] Signature too short (len: {} < {}), downgrading to text.",
                                    sig.len(),
                                    super::thinking::MIN_SIGNATURE_LENGTH
                                );
                                parts.push(json!({"text": thinking}));
                                saw_non_thinking = true;
                                continue;
                            }

                            let cached_family =
                                crate::proxy::SignatureCache::global().get_signature_family(sig);

                            match cached_family {
                                Some(family) => {
                                    let compatible = !ctx.is_retry
                                        && is_model_compatible(&family, ctx.mapped_model);

                                    if !compatible {
                                        tracing::warn!(
                                            "[Thinking-Signature] {} signature (Family: {}, Target: {}). Downgrading to text.",
                                            if ctx.is_retry {
                                                "Stripping historical"
                                            } else {
                                                "Incompatible"
                                            },
                                            family,
                                            ctx.mapped_model
                                        );
                                        parts.push(json!({"text": thinking}));
                                        saw_non_thinking = true;
                                        continue;
                                    }
                                    *state.last_thought_signature = Some(sig.clone());
                                    let mut part = json!({
                                        "text": thinking,
                                        "thought": true,
                                        "thoughtSignature": sig
                                    });
                                    crate::proxy::common::json_schema::clean_json_schema(&mut part);
                                    parts.push(part);
                                }
                                None => {
                                    if sig.len() >= super::thinking::MIN_SIGNATURE_LENGTH {
                                        tracing::debug!(
                                            "[Thinking-Signature] Unknown signature origin but valid length (len: {}), using as-is for JSON tool calling.",
                                            sig.len()
                                        );
                                        *state.last_thought_signature = Some(sig.clone());
                                        let mut part = json!({
                                            "text": thinking,
                                            "thought": true,
                                            "thoughtSignature": sig
                                        });
                                        crate::proxy::common::json_schema::clean_json_schema(
                                            &mut part,
                                        );
                                        parts.push(part);
                                    } else {
                                        tracing::warn!(
                                            "[Thinking-Signature] Unknown signature origin and too short (len: {}). Downgrading to text for safety.",
                                            sig.len()
                                        );
                                        parts.push(json!({"text": thinking}));
                                        saw_non_thinking = true;
                                        continue;
                                    }
                                }
                            }
                        } else {
                            tracing::warn!(
                                "[Thinking-Signature] No signature provided. Downgrading to text."
                            );
                            parts.push(json!({"text": thinking}));
                            saw_non_thinking = true;
                        }
                    }
                    ContentBlock::RedactedThinking { data } => {
                        tracing::debug!("[Claude-Request] Degrade RedactedThinking to text");
                        parts.push(json!({
                            "text": format!("[Redacted Thinking: {}]", data)
                        }));
                        saw_non_thinking = true;
                        continue;
                    }
                    ContentBlock::Image { source, .. } => {
                        if source.source_type == "base64" {
                            parts.push(json!({
                                "inlineData": {
                                    "mimeType": source.media_type,
                                    "data": source.data
                                }
                            }));
                            saw_non_thinking = true;
                        }
                    }
                    ContentBlock::Document { source, .. } => {
                        if source.source_type == "base64" {
                            parts.push(json!({
                                "inlineData": {
                                    "mimeType": source.media_type,
                                    "data": source.data
                                }
                            }));
                            saw_non_thinking = true;
                        }
                    }
                    ContentBlock::ToolUse {
                        id,
                        name,
                        input,
                        signature,
                        ..
                    } => {
                        let mut final_input = input.clone();
                        if let Some(original_schema) = ctx.tool_name_to_schema.get(name) {
                            crate::proxy::common::json_schema::fix_tool_call_args(
                                &mut final_input,
                                original_schema,
                            );
                        }

                        let mut part = json!({
                            "functionCall": {
                                "name": name,
                                "args": final_input,
                                "id": id
                            }
                        });
                        saw_non_thinking = true;
                        if is_assistant {
                            state.pending_tool_use_ids.push(id.clone());
                        }
                        state.tool_id_to_name.insert(id.clone(), name.clone());
                        apply_tool_use_signature(&mut part, id, signature, ctx, state);
                        parts.push(part);
                    }
                    ContentBlock::ToolResult {
                        tool_use_id,
                        content,
                        is_error,
                        ..
                    } => {
                        current_turn_tool_result_ids.insert(tool_use_id.clone());
                        let func_name = state
                            .tool_id_to_name
                            .get(tool_use_id)
                            .cloned()
                            .unwrap_or_else(|| tool_use_id.clone());
                        let merged_content =
                            build_tool_result_content(content, is_error.unwrap_or(false));

                        parts.push(json!({
                            "functionResponse": {
                                "name": func_name,
                                "response": {"result": merged_content},
                                "id": tool_use_id
                            }
                        }));
                        if let Some(sig) = state.last_thought_signature.as_ref() {
                            if let Some(last_part) = parts.last_mut() {
                                last_part["thoughtSignature"] = json!(sig);
                            }
                        }
                        *state.previous_was_tool_result = true;
                    }
                    ContentBlock::ServerToolUse { .. }
                    | ContentBlock::WebSearchToolResult { .. } => {
                        continue;
                    }
                }
            }
        }
    }
    if !is_assistant && !state.pending_tool_use_ids.is_empty() {
        let missing_ids: Vec<_> = state
            .pending_tool_use_ids
            .iter()
            .filter(|id| !current_turn_tool_result_ids.contains(*id))
            .cloned()
            .collect();

        if !missing_ids.is_empty() {
            tracing::warn!("[Elastic-Recovery] Injecting {} missing tool results into User message (IDs: {:?})", missing_ids.len(), missing_ids);
            for id in missing_ids.iter().rev() {
                let name = state.tool_id_to_name.get(id).cloned().unwrap_or(id.clone());
                let synthetic_part = json!({
                    "functionResponse": {
                        "name": name,
                        "response": {
                            "result": "Tool execution interrupted. No result provided."
                        },
                        "id": id
                    }
                });
                parts.insert(0, synthetic_part);
            }
        }
        state.pending_tool_use_ids.clear();
    }
    if ctx.allow_dummy_thought && is_assistant && ctx.is_thinking_enabled {
        let has_thought_part = parts.iter().any(|p| {
            p.get("thought").and_then(|v| v.as_bool()).unwrap_or(false)
                || p.get("thoughtSignature").is_some()
                || p.get("thought").and_then(|v| v.as_str()).is_some()
        });

        if !has_thought_part {
            parts.insert(
                0,
                json!({
                    "text": "Thinking...",
                    "thought": true
                }),
            );
            tracing::debug!(
                "Injected dummy thought block for historical assistant message at index {}",
                parts.len()
            );
        } else {
            let first_is_thought = parts.first().is_some_and(|p| {
                (p.get("thought").is_some() || p.get("thoughtSignature").is_some())
                    && p.get("text").is_some()
            });

            if !first_is_thought {
                parts.insert(
                    0,
                    json!({
                        "text": "...",
                        "thought": true
                    }),
                );
                tracing::debug!("First part of model message at {} is not a valid thought block. Prepending dummy.", parts.len());
            } else if let Some(p0) = parts.get_mut(0) {
                if p0.get("thought").is_none() {
                    p0.as_object_mut()
                        .map(|obj| obj.insert("thought".to_string(), json!(true)));
                }
            }
        }
    }

    Ok(parts)
}

fn apply_tool_use_signature(
    part: &mut Value,
    id: &str,
    signature: &Option<String>,
    ctx: &BuildContentsContext<'_>,
    state: &BuildContentsState<'_>,
) {
    let final_sig = resolve_tool_use_signature(id, signature, ctx, state);
    if let Some(sig) = final_sig {
        if ctx.is_retry && signature.is_none() {
            tracing::warn!(
                "[Tool-Signature] Skipping signature backfill for tool_use: {} during retry.",
                id
            );
            return;
        }
        if sig.len() < super::thinking::MIN_SIGNATURE_LENGTH {
            tracing::warn!(
                "[Tool-Signature] Signature too short for tool_use: {} (len: {} < {}), skipping.",
                id,
                sig.len(),
                super::thinking::MIN_SIGNATURE_LENGTH
            );
            return;
        }
        if should_use_tool_signature(&sig, id, ctx) {
            part["thoughtSignature"] = json!(sig);
        }
    } else {
        let is_google_cloud = ctx.mapped_model.starts_with("projects/");
        if ctx.is_thinking_enabled && !is_google_cloud {
            tracing::debug!(
                "[Tool-Signature] Adding GEMINI_SKIP_SIGNATURE for tool_use: {}",
                id
            );
            part["thoughtSignature"] = json!("skip_thought_signature_validator");
        }
    }
}

fn resolve_tool_use_signature(
    id: &str,
    signature: &Option<String>,
    ctx: &BuildContentsContext<'_>,
    state: &BuildContentsState<'_>,
) -> Option<String> {
    signature
        .as_ref()
        .or(state.last_thought_signature.as_ref())
        .cloned()
        .or_else(|| {
            crate::proxy::SignatureCache::global()
                .get_session_signature(ctx.session_id)
                .inspect(|s| {
                    tracing::info!(
                        "[Claude-Request] Recovered signature from SESSION cache (session: {}, len: {})",
                        ctx.session_id,
                        s.len()
                    );
                })
        })
        .or_else(|| {
            crate::proxy::SignatureCache::global()
                .get_tool_signature(id)
                .inspect(|_s| {
                    tracing::info!(
                        "[Claude-Request] Recovered signature from TOOL cache for tool_id: {}",
                        id
                    );
                })
        })
        .or_else(|| {
            let global_sig = get_thought_signature();
            if let Some(sig) = &global_sig {
                tracing::warn!(
                    "[Claude-Request] Using deprecated GLOBAL thought_signature fallback (length: {}). This indicates session cache miss.",
                    sig.len()
                );
            }
            global_sig
        })
}

fn should_use_tool_signature(sig: &str, id: &str, ctx: &BuildContentsContext<'_>) -> bool {
    let cached_family = crate::proxy::SignatureCache::global().get_signature_family(sig);

    match cached_family {
        Some(family) => {
            if is_model_compatible(&family, ctx.mapped_model) {
                true
            } else {
                tracing::warn!(
                    "[Tool-Signature] Incompatible signature for tool_use: {} (Family: {}, Target: {})",
                    id,
                    family,
                    ctx.mapped_model
                );
                false
            }
        }
        None => {
            if sig.len() >= super::thinking::MIN_SIGNATURE_LENGTH {
                tracing::debug!(
                    "[Tool-Signature] Unknown signature origin but valid length (len: {}) for tool_use: {}, using as-is for JSON tool calling.",
                    sig.len(),
                    id
                );
                true
            } else if ctx.is_thinking_enabled {
                tracing::warn!(
                    "[Tool-Signature] Unknown signature origin and too short for tool_use: {} (len: {}). Dropping in thinking mode.",
                    id,
                    sig.len()
                );
                false
            } else {
                true
            }
        }
    }
}

fn build_tool_result_content(content: &Value, is_error: bool) -> String {
    let mut compacted_content = content.clone();
    if let Some(blocks) = compacted_content.as_array_mut() {
        tool_result_compressor::sanitize_tool_result_blocks(blocks);
    }
    let mut merged_content = match &compacted_content {
        Value::String(s) => s.clone(),
        Value::Array(arr) => arr
            .iter()
            .filter_map(|block| {
                if let Some(text) = block.get("text").and_then(|v| v.as_str()) {
                    Some(text.to_string())
                } else if block.get("source").is_some() {
                    if block.get("type").and_then(|v| v.as_str()) == Some("image") {
                        Some("[image omitted to save context]".to_string())
                    } else {
                        None
                    }
                } else {
                    None
                }
            })
            .collect::<Vec<_>>()
            .join("\n"),
        _ => content.to_string(),
    };

    if merged_content.len() > MAX_TOOL_RESULT_CHARS {
        tracing::warn!(
            "Truncating tool result from {} chars to {}",
            merged_content.len(),
            MAX_TOOL_RESULT_CHARS
        );
        let mut truncated = merged_content
            .chars()
            .take(MAX_TOOL_RESULT_CHARS)
            .collect::<String>();
        truncated.push_str("\n...[truncated output]");
        merged_content = truncated;
    }
    if merged_content.trim().is_empty() {
        if is_error {
            merged_content = "Tool execution failed with no output.".to_string();
        } else {
            merged_content = "Command executed successfully.".to_string();
        }
    }
    merged_content
}

fn build_google_content(
    msg: &Message,
    ctx: &BuildContentsContext<'_>,
    state: &mut BuildContentsState<'_>,
) -> Result<Value, String> {
    let role = if msg.role == "assistant" {
        "model"
    } else {
        &msg.role
    };
    if role == "model" && !state.pending_tool_use_ids.is_empty() {
        tracing::warn!("[Elastic-Recovery] Detected interrupted tool chain (Assistant -> Assistant). Injecting synthetic User message for IDs: {:?}", state.pending_tool_use_ids);

        let synthetic_parts: Vec<serde_json::Value> = state
            .pending_tool_use_ids
            .iter()
            .filter(|id| !ctx.existing_tool_result_ids.contains(*id))
            .map(|id| {
                let name = state.tool_id_to_name.get(id).cloned().unwrap_or(id.clone());
                json!({
                    "functionResponse": {
                        "name": name,
                        "response": {
                            "result": "Tool execution interrupted. No result provided."
                        },
                        "id": id
                    }
                })
            })
            .collect();

        if !synthetic_parts.is_empty() {
            return Ok(json!({
                "role": "user",
                "parts": synthetic_parts
            }));
        }
        state.pending_tool_use_ids.clear();
    }

    let parts = build_contents(&msg.content, msg.role == "assistant", ctx, state)?;

    if parts.is_empty() {
        return Ok(json!(null));
    }

    Ok(json!({
        "role": role,
        "parts": parts
    }))
}

pub(super) fn build_google_contents(
    messages: &[Message],
    tool_id_to_name: &mut HashMap<String, String>,
    tool_name_to_schema: &HashMap<String, Value>,
    options: GoogleContentsOptions<'_>,
) -> Result<Value, String> {
    let mut contents = Vec::new();
    let mut last_thought_signature: Option<String> = None;
    let mut _accumulated_usage: Option<Value> = None;
    let mut pending_tool_use_ids: Vec<String> = Vec::new();
    let mut last_user_task_text_normalized: Option<String> = None;
    let mut previous_was_tool_result = false;

    let _msg_count = messages.len();
    let mut existing_tool_result_ids = HashSet::new();
    for msg in messages {
        if let MessageContent::Array(blocks) = &msg.content {
            for block in blocks {
                if let ContentBlock::ToolResult { tool_use_id, .. } = block {
                    existing_tool_result_ids.insert(tool_use_id.clone());
                }
            }
        }
    }

    let build_ctx = BuildContentsContext {
        is_thinking_enabled: options.is_thinking_enabled,
        session_id: options.session_id,
        allow_dummy_thought: options.allow_dummy_thought,
        is_retry: options.is_retry,
        tool_name_to_schema,
        mapped_model: options.mapped_model,
        existing_tool_result_ids: &existing_tool_result_ids,
    };
    let mut build_state = BuildContentsState {
        tool_id_to_name,
        last_thought_signature: &mut last_thought_signature,
        pending_tool_use_ids: &mut pending_tool_use_ids,
        last_user_task_text_normalized: &mut last_user_task_text_normalized,
        previous_was_tool_result: &mut previous_was_tool_result,
    };

    for msg in messages {
        let google_content = build_google_content(msg, &build_ctx, &mut build_state)?;

        if !google_content.is_null() {
            contents.push(google_content);
        }
    }
    let mut merged_contents = merge_adjacent_roles(contents);
    if !options.is_thinking_enabled {
        for msg in &mut merged_contents {
            super::generation::clean_thinking_fields_recursive(msg);
        }
    }

    Ok(json!(merged_contents))
}

fn merge_adjacent_roles(mut contents: Vec<Value>) -> Vec<Value> {
    if contents.is_empty() {
        return contents;
    }

    let mut merged = Vec::new();
    let mut current_msg = contents.remove(0);

    for msg in contents {
        let current_role = current_msg["role"].as_str().unwrap_or_default();
        let next_role = msg["role"].as_str().unwrap_or_default();

        if current_role == next_role {
            if let Some(current_parts) = current_msg.get_mut("parts").and_then(|p| p.as_array_mut())
            {
                if let Some(next_parts) = msg.get("parts").and_then(|p| p.as_array()) {
                    current_parts.extend(next_parts.clone());
                    super::preprocess::reorder_gemini_parts(current_parts);
                }
            }
        } else {
            merged.push(current_msg);
            current_msg = msg;
        }
    }
    merged.push(current_msg);
    merged
}

fn is_model_compatible(cached: &str, target: &str) -> bool {
    crate::proxy::common::model_mapping::is_signature_family_compatible(cached, target)
}

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

    #[test]
    fn build_tool_result_content_handles_empty_non_error() {
        let content = json!([]);
        let result = build_tool_result_content(&content, false);
        assert_eq!(result, "Command executed successfully.");
    }

    #[test]
    fn build_tool_result_content_handles_empty_error() {
        let content = json!([]);
        let result = build_tool_result_content(&content, true);
        assert_eq!(result, "Tool execution failed with no output.");
    }

    #[test]
    fn build_tool_result_content_collapses_array_blocks_and_omits_images() {
        let content = json!([
            {"type":"text","text":"line 1"},
            {"type":"image","source":{"type":"base64","media_type":"image/png","data":"AAAA"}},
            {"type":"text","text":"line 2"}
        ]);
        let result = build_tool_result_content(&content, false);
        assert!(result.contains("line 1"));
        assert!(result.contains("line 2"));
        assert!(!result.trim().is_empty());
    }

    #[test]
    fn build_tool_result_content_truncates_large_payload() {
        let long = "x".repeat(MAX_TOOL_RESULT_CHARS + 5000);
        let content = Value::String(long);
        let result = build_tool_result_content(&content, false);
        assert!(result.len() > MAX_TOOL_RESULT_CHARS);
        assert!(result.contains("...[truncated output]"));
    }
}