use super::*;

pub(super) fn completion_request(mut value: Value) -> Result<ChatRequest, (u16, String)> {
    let object = value
        .as_object_mut()
        .ok_or_else(|| (400, "invalid JSON request".to_owned()))?;
    let prompt = object
        .remove("prompt")
        .ok_or_else(|| (400, "missing prompt".to_owned()))?;
    let prompt = match prompt {
        Value::String(text) => text,
        Value::Array(values) => values
            .into_iter()
            .next()
            .and_then(|value| value.as_str().map(str::to_owned))
            .unwrap_or_default(),
        _ => String::new(),
    };
    object.insert(
        "messages".into(),
        json!([
            {"role": "system", "content": "You are a helpful assistant"},
            {"role": "user", "content": prompt}
        ]),
    );
    decode_chat_request(value)
}

pub(super) fn anthropic_request(value: Value) -> Result<ChatRequest, (u16, String)> {
    let object = value
        .as_object()
        .ok_or_else(|| (400, "invalid JSON request".to_owned()))?;
    let source = object
        .get("messages")
        .and_then(Value::as_array)
        .ok_or_else(|| (400, "missing messages".to_owned()))?;
    let mut messages = Vec::new();
    let system = object.get("system").map(content_text).unwrap_or_default();
    if !system.is_empty() {
        messages.push(json!({"role": "system", "content": system}));
    }
    for message in source {
        let role = message
            .get("role")
            .and_then(Value::as_str)
            .unwrap_or("user");
        let Some(blocks) = message.get("content").and_then(Value::as_array) else {
            messages.push(json!({"role": role, "content": message.get("content").cloned().unwrap_or(Value::Null)}));
            continue;
        };
        let mut text = String::new();
        let mut reasoning = String::new();
        let mut calls = Vec::new();
        for block in blocks {
            match block.get("type").and_then(Value::as_str).unwrap_or("text") {
                "text" => text.push_str(block.get("text").and_then(Value::as_str).unwrap_or("")),
                "thinking" | "redacted_thinking" => reasoning.push_str(
                    block
                        .get("thinking")
                        .or_else(|| block.get("data"))
                        .and_then(Value::as_str)
                        .unwrap_or(""),
                ),
                "tool_use" => calls.push(json!({
                    "id": block.get("id").and_then(Value::as_str).unwrap_or(""),
                    "type": "function",
                    "function": {
                        "name": block.get("name").and_then(Value::as_str).unwrap_or(""),
                        "arguments": block.get("input").cloned().unwrap_or_else(|| json!({})).to_string()
                    }
                })),
                "tool_result" => {
                    if !text.is_empty() {
                        messages.push(json!({"role": role, "content": std::mem::take(&mut text)}));
                    }
                    messages.push(json!({
                        "role": "tool",
                        "content": block.get("content").map(content_text).unwrap_or_default(),
                        "tool_call_id": block.get("tool_use_id").and_then(Value::as_str).unwrap_or("")
                    }));
                }
                _ => {}
            }
        }
        if !text.is_empty() || !reasoning.is_empty() || !calls.is_empty() {
            messages.push(json!({
                "role": role,
                "content": text,
                "reasoning_content": reasoning,
                "tool_calls": calls
            }));
        }
    }
    let mut request = Map::new();
    copy_request_fields(object, &mut request);
    request.insert("messages".into(), Value::Array(messages));
    if let Some(stops) = object.get("stop_sequences") {
        request.insert("stop".into(), stops.clone());
    }
    if let Some(effort) = object
        .get("output_config")
        .and_then(|value| value.get("effort"))
    {
        request.insert("reasoning_effort".into(), effort.clone());
    }
    request.insert(
        "tools".into(),
        normalize_anthropic_tools(object.get("tools")),
    );
    decode_chat_request(Value::Object(request))
}

pub(super) fn responses_request(value: Value) -> Result<ChatRequest, (u16, String)> {
    let object = value
        .as_object()
        .ok_or_else(|| (400, "invalid JSON request".to_owned()))?;
    for key in ["previous_response_id", "conversation"] {
        if object.get(key).is_some_and(|value| !value.is_null()) {
            return Err((
                400,
                format!("{key} is not supported; replay full input instead"),
            ));
        }
    }
    if let Some(choice) = object.get("tool_choice") {
        match choice {
            Value::String(choice) if choice == "none" || choice == "auto" => {}
            Value::String(choice) => {
                return Err((400, format!("tool_choice={choice} not supported")));
            }
            Value::Object(_) => return Err((400, "forced tool_choice not supported".into())),
            _ => {}
        }
    }
    let input = object
        .get("input")
        .ok_or_else(|| (400, "missing input".to_owned()))?;
    let mut messages = Vec::new();
    match input {
        Value::String(text) => messages.push(json!({"role": "user", "content": text})),
        Value::Array(items) => {
            let mut pending_reasoning = String::new();
            for item in items {
                let item = item
                    .as_object()
                    .ok_or_else(|| (400, "invalid JSON request".to_owned()))?;
                if item
                    .get("status")
                    .and_then(Value::as_str)
                    .is_some_and(|status| status != "completed")
                {
                    return Err((400, "invalid JSON request".into()));
                }
                let kind = item
                    .get("type")
                    .and_then(Value::as_str)
                    .unwrap_or("message");
                let role = item.get("role").and_then(Value::as_str).unwrap_or("user");
                let consumes_reasoning = (kind == "message" && role == "assistant")
                    || matches!(
                        kind,
                        "function_call"
                            | "custom_tool_call"
                            | "local_shell_call"
                            | "web_search_call"
                            | "tool_search_call"
                            | "image_generation_call"
                    );
                let bookkeeping = matches!(kind, "compaction" | "context_compaction");
                if !consumes_reasoning && !bookkeeping && !pending_reasoning.is_empty() {
                    messages.push(reasoning_message(std::mem::take(&mut pending_reasoning)));
                }
                match kind {
                    "message" => {
                        let mut message = json!({
                            "role": role,
                            "content": responses_content_text(item.get("content").unwrap_or(&Value::Null))?
                        });
                        if role == "assistant" && !pending_reasoning.is_empty() {
                            message["reasoning_content"] =
                                Value::String(std::mem::take(&mut pending_reasoning));
                        }
                        messages.push(message);
                    }
                    "function_call" | "custom_tool_call" => {
                        let name = if kind == "function_call" {
                            format!(
                                "{}{}",
                                item.get("namespace").and_then(Value::as_str).unwrap_or(""),
                                item.get("name").and_then(Value::as_str).unwrap_or("")
                            )
                        } else {
                            item.get("name")
                                .and_then(Value::as_str)
                                .unwrap_or("")
                                .to_owned()
                        };
                        push_responses_call(
                            &mut messages,
                            &mut pending_reasoning,
                            item,
                            &name,
                            item.get("arguments").or_else(|| item.get("input")),
                        );
                    }
                    "function_call_output" | "custom_tool_call_output" => messages.push(json!({
                        "role": "tool",
                        "content": responses_output_text(item.get("output"))?,
                        "tool_call_id": item.get("call_id").or_else(|| item.get("id")).and_then(Value::as_str).unwrap_or("")
                    })),
                    "reasoning" => {
                        for value in [item.get("summary"), item.get("content")]
                            .into_iter()
                            .flatten()
                        {
                            let text = responses_content_text(value)?;
                            if !pending_reasoning.is_empty() && !text.is_empty() {
                                pending_reasoning.push('\n');
                            }
                            pending_reasoning.push_str(&text);
                        }
                    }
                    "local_shell_call"
                    | "web_search_call"
                    | "tool_search_call"
                    | "image_generation_call" => {
                        let name = match kind {
                            "local_shell_call" => "local_shell",
                            "tool_search_call" => "tool_search",
                            other => other,
                        };
                        push_responses_call(
                            &mut messages,
                            &mut pending_reasoning,
                            item,
                            name,
                            item.get("action")
                                .or_else(|| item.get("arguments"))
                                .or_else(|| item.get("input")),
                        );
                    }
                    "local_shell_call_output"
                    | "web_search_call_output"
                    | "tool_search_output"
                    | "tool_search_call_output"
                    | "image_generation_call_output" => {
                        let content = if let Some(value) =
                            item.get("output").or_else(|| item.get("result"))
                        {
                            responses_output_text(Some(value))?
                        } else {
                            item.get("tools").map(Value::to_string).unwrap_or_default()
                        };
                        messages.push(json!({
                            "role": "tool",
                            "content": content,
                            "tool_call_id": item.get("call_id").or_else(|| item.get("id")).and_then(Value::as_str).unwrap_or("")
                        }));
                    }
                    "compaction" | "context_compaction" => {}
                    _ => return Err((400, "invalid JSON request".into())),
                }
            }
            if !pending_reasoning.is_empty() {
                messages.push(reasoning_message(pending_reasoning));
            }
        }
        _ => return Err((400, "invalid JSON request".into())),
    }
    if let Some(instructions) = object.get("instructions") {
        let instructions = match instructions {
            Value::Null => String::new(),
            Value::String(text) => text.clone(),
            _ => return Err((400, "invalid JSON request".into())),
        };
        if !instructions.is_empty() {
            messages.insert(0, json!({"role": "system", "content": instructions}));
        }
    }
    let mut request = Map::new();
    copy_request_fields(object, &mut request);
    request.insert("messages".into(), Value::Array(messages));
    if let Some(tokens) = object.get("max_output_tokens") {
        request.insert("max_tokens".into(), tokens.clone());
    }
    if let Some(effort) = object
        .get("reasoning")
        .and_then(|value| value.get("effort"))
    {
        request.insert("reasoning_effort".into(), effort.clone());
    }
    request.insert(
        "tools".into(),
        normalize_responses_tools(object.get("tools")),
    );
    decode_chat_request(Value::Object(request))
}

fn reasoning_message(reasoning: String) -> Value {
    json!({"role": "assistant", "content": "", "reasoning_content": reasoning})
}

fn push_responses_call(
    messages: &mut Vec<Value>,
    pending_reasoning: &mut String,
    item: &Map<String, Value>,
    name: &str,
    arguments: Option<&Value>,
) {
    let call = json!({
        "id": item.get("call_id").or_else(|| item.get("id")).and_then(Value::as_str).unwrap_or(""),
        "type": "function",
        "function": {"name": name, "arguments": raw_json_text(arguments)}
    });
    if let Some(last) = messages.last_mut()
        && last.get("role").and_then(Value::as_str) == Some("assistant")
    {
        if !pending_reasoning.is_empty()
            && last
                .get("reasoning_content")
                .and_then(Value::as_str)
                .is_none_or(str::is_empty)
        {
            last["reasoning_content"] = Value::String(std::mem::take(pending_reasoning));
        }
        if !last.get("tool_calls").is_some_and(Value::is_array) {
            last["tool_calls"] = json!([]);
        }
        last["tool_calls"].as_array_mut().unwrap().push(call);
    } else {
        messages.push(json!({
            "role": "assistant",
            "content": "",
            "reasoning_content": std::mem::take(pending_reasoning),
            "tool_calls": [call]
        }));
    }
}

fn raw_json_text(value: Option<&Value>) -> String {
    match value {
        Some(Value::String(text)) => text.clone(),
        Some(value) => value.to_string(),
        None => "{}".into(),
    }
}

fn responses_output_text(value: Option<&Value>) -> Result<String, (u16, String)> {
    match value {
        Some(Value::Array(_)) => responses_content_text(value.unwrap()),
        Some(Value::String(text)) => Ok(text.clone()),
        Some(value) => Ok(value.to_string()),
        None => Ok(String::new()),
    }
}

fn responses_content_text(value: &Value) -> Result<String, (u16, String)> {
    match value {
        Value::String(text) => Ok(text.clone()),
        Value::Null => Ok(String::new()),
        Value::Array(parts) => {
            let mut text = String::new();
            for part in parts {
                match part {
                    Value::String(part) => text.push_str(part),
                    Value::Object(part)
                        if matches!(
                            part.get("type").and_then(Value::as_str),
                            Some(
                                "input_text"
                                    | "output_text"
                                    | "text"
                                    | "summary_text"
                                    | "reasoning_text"
                            )
                        ) && matches!(
                            part.get("text"),
                            Some(Value::String(_) | Value::Null)
                        ) =>
                    {
                        if let Some(value) = part.get("text").and_then(Value::as_str) {
                            text.push_str(value);
                        }
                    }
                    _ => return Err((400, "invalid JSON request".into())),
                }
            }
            Ok(text)
        }
        _ => Err((400, "invalid JSON request".into())),
    }
}

fn copy_request_fields(source: &Map<String, Value>, target: &mut Map<String, Value>) {
    for key in [
        "model",
        "max_tokens",
        "max_completion_tokens",
        "temperature",
        "top_p",
        "min_p",
        "top_k",
        "seed",
        "stream",
        "stream_options",
        "thinking",
        "think",
        "reasoning_effort",
        "tool_choice",
        "stop",
    ] {
        if let Some(value) = source.get(key) {
            target.insert(key.into(), value.clone());
        }
    }
}

fn normalize_anthropic_tools(tools: Option<&Value>) -> Value {
    Value::Array(
        tools
            .and_then(Value::as_array)
            .into_iter()
            .flatten()
            .map(|tool| {
                json!({"type": "function", "function": {
                    "name": tool.get("name").and_then(Value::as_str).unwrap_or(""),
                    "description": tool.get("description").and_then(Value::as_str).unwrap_or(""),
                    "parameters": tool.get("input_schema").cloned().unwrap_or_else(|| json!({"type": "object"}))
                }})
            })
            .collect(),
    )
}

fn normalize_responses_tools(tools: Option<&Value>) -> Value {
    Value::Array(
        tools
            .and_then(Value::as_array)
            .into_iter()
            .flatten()
            .filter(|tool| tool.get("type").and_then(Value::as_str) == Some("function"))
            .map(|tool| {
                json!({"type": "function", "function": {
                    "name": tool.get("name").and_then(Value::as_str).unwrap_or(""),
                    "description": tool.get("description").and_then(Value::as_str).unwrap_or(""),
                    "parameters": tool.get("parameters").cloned().unwrap_or_else(|| json!({"type": "object"}))
                }})
            })
            .collect(),
    )
}

pub(super) fn decode_chat_request(value: Value) -> Result<ChatRequest, (u16, String)> {
    serde_json::from_value(value).map_err(|_| (400, "invalid JSON request".to_owned()))
}

pub(super) fn raw_tool_schemas(
    body: &[u8],
    protocol: Protocol,
) -> Result<Vec<String>, (u16, String)> {
    let request: RawToolsRequest<'_> =
        serde_json::from_slice(body).map_err(|_| (400, "invalid JSON request".to_owned()))?;
    request
        .tools
        .into_iter()
        .map(|tool| {
            if protocol == Protocol::Chat {
                let wrapper: RawOpenAiTool<'_> = serde_json::from_str(tool.get())
                    .map_err(|_| (400, "invalid JSON request".to_owned()))?;
                Ok(wrapper.function.unwrap_or(tool).get().to_owned())
            } else {
                Ok(tool.get().to_owned())
            }
        })
        .collect()
}

pub(super) fn parse_chat_request(
    state: &State,
    request: ChatRequest,
    protocol: Protocol,
) -> Result<ParsedRequest, (u16, String)> {
    if request.messages.is_empty() {
        return Err((400, "missing messages".into()));
    }
    let requested_id = request.model.as_deref().unwrap_or_default();
    let config = state
        .config
        .read()
        .map_err(|_| (500, "preferences are unavailable".to_owned()))?
        .clone();
    let model = if requested_id.is_empty() {
        Some(config.model)
    } else {
        model_alias(requested_id)
    }
    .ok_or_else(|| (400, format!("unknown model: {requested_id}")))?;
    if !installed_endpoint_models(&state.models_path).contains(&model) {
        return Err((400, format!("model is not installed and verified: {model}")));
    }

    let reasoning_mode = request_reasoning(&request, requested_id)?;
    let mut generation = config.generation_for(model, reasoning_mode);
    generation.max_generated_tokens = request
        .max_completion_tokens
        .or(request.max_tokens)
        .unwrap_or(generation.max_generated_tokens);
    if generation.max_generated_tokens <= 0 {
        return Err((400, "max_tokens must be positive".into()));
    }
    generation.temperature = request.temperature.or(Some(1.0));
    generation.top_p = request.top_p.or(Some(1.0));
    generation.min_p = request.min_p.or(Some(0.05));
    generation.seed = request.seed.filter(|seed| *seed > 0);

    let tools_enabled = (!request.tools.is_empty() || !request.tool_schemas.is_empty())
        && request.tool_choice.as_ref().and_then(Value::as_str) != Some("none");
    let (system, messages) = render_messages(
        state,
        &request.messages,
        &request.tools,
        &request.tool_schemas,
        tools_enabled,
        protocol,
    )?;
    generation.system_prompt = system;
    let runtime = config.runtime_for(model);
    let mut effective = effective_settings(model, &generation, &runtime, &state.models_path)
        .map_err(|error| (400, error))?;
    effective.turn.top_k = request.top_k.unwrap_or(0);
    if effective.turn.top_k < 0 {
        return Err((400, "top_k must not be negative".into()));
    }
    effective.turn.stops = match request.stop {
        Some(OneOrMany::One(stop)) => vec![stop],
        Some(OneOrMany::Many(stops)) => stops,
        None => Vec::new(),
    };
    effective.turn.stops.retain(|stop| !stop.is_empty());

    Ok(ParsedRequest {
        protocol,
        model_id: if requested_id.is_empty() {
            model.id().to_owned()
        } else {
            requested_id.to_owned()
        },
        messages,
        turn: effective.turn,
        engine: effective.engine,
        idle_timeout: Duration::from_secs(config.idle_timeout_minutes.max(1) as u64 * 60),
        stream: request.stream,
        include_usage: request
            .stream_options
            .is_some_and(|options| options.include_usage),
        has_tools: tools_enabled,
    })
}

fn request_reasoning(
    request: &ChatRequest,
    model_id: &str,
) -> Result<ReasoningMode, (u16, String)> {
    let explicit_thinking = request
        .think
        .or_else(|| request.thinking.as_ref().and_then(thinking_enabled));
    let mut reasoning = match request.reasoning_effort.as_deref() {
        Some("max") => ReasoningMode::Max,
        Some("none") => ReasoningMode::Direct,
        Some("xhigh" | "high" | "medium" | "low" | "minimal") | None => ReasoningMode::High,
        Some(value) => return Err((400, format!("unsupported reasoning_effort: {value}"))),
    };
    if explicit_thinking == Some(false)
        || (explicit_thinking.is_none()
            && matches!(
                model_id,
                "deepseek-chat" | "glm-5.2-chat" | "glm-5.2-no-think" | "glm-5.2-nothink"
            ))
    {
        reasoning = ReasoningMode::Direct;
    }
    Ok(reasoning)
}

fn thinking_enabled(value: &Value) -> Option<bool> {
    value.as_bool().or_else(|| {
        value.as_str().map(|value| value != "disabled").or_else(|| {
            value
                .get("type")
                .and_then(Value::as_str)
                .map(|value| value != "disabled")
        })
    })
}
