use serde::{Deserialize, Serialize};

// ── Request types ──────────────────────────────────────────────

#[derive(Serialize, Clone, Debug)]
pub struct ChatMessage {
    pub role: String,
    pub content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCallRequest>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
}

#[derive(Serialize)]
pub(crate) struct ChatCompletionRequest {
    pub(crate) model: String,
    pub(crate) messages: Vec<ChatMessage>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) temperature: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) max_tokens: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) tools: Option<Vec<ToolDefinitionPayload>>,
}

#[derive(Serialize)]
pub(crate) struct ToolDefinitionPayload {
    pub(crate) r#type: String,
    pub(crate) function: ToolFunctionPayload,
}

#[derive(Serialize)]
pub(crate) struct ToolFunctionPayload {
    pub(crate) name: String,
    pub(crate) description: String,
    pub(crate) parameters: serde_json::Value,
}

// ── Response types ─────────────────────────────────────────────

#[derive(Deserialize)]
pub(crate) struct ChatCompletionResponse {
    pub(crate) choices: Vec<ChatChoice>,
}

#[derive(Deserialize)]
pub(crate) struct ChatChoice {
    pub(crate) message: ChatResponseMessage,
}

#[derive(Deserialize)]
pub(crate) struct ChatResponseMessage {
    #[serde(default)]
    pub(crate) content: Option<String>,
    #[serde(default)]
    pub(crate) tool_calls: Option<Vec<ToolCallResponse>>,
}

#[derive(Deserialize)]
pub(crate) struct ToolCallResponse {
    pub(crate) id: String,
    pub(crate) function: ToolCallFunction,
}

#[derive(Deserialize)]
pub(crate) struct ToolCallFunction {
    pub(crate) name: String,
    pub(crate) arguments: String,
}

// ── Public types for tool calling ──────────────────────────────

/// Definition of a tool that the LLM can invoke
#[derive(Debug, Clone, Serialize)]
pub struct ToolDefinition {
    pub name: String,
    pub description: String,
    pub parameters: serde_json::Value,
}

/// A tool call request from the LLM
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmToolCall {
    pub id: String,
    pub name: String,
    pub arguments: serde_json::Value,
}

/// A tool call in the request message format (for sending back tool_calls in assistant messages)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallRequest {
    pub id: String,
    pub r#type: String,
    pub function: ToolCallRequestFunction,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallRequestFunction {
    pub name: String,
    pub arguments: String,
}

/// Response from the LLM — either content or tool calls
#[derive(Debug, Clone)]
pub enum LlmResponse {
    Content(String),
    /// Tool calls with optional reasoning text from the LLM
    ToolCalls {
        calls: Vec<LlmToolCall>,
        reasoning: String,
    },
}

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

    // ── ChatMessage ──────────────────────────────────────────────

    #[test]
    fn chat_message_serializes_minimal() {
        let msg = ChatMessage {
            role: "user".to_string(),
            content: Some("hello".to_string()),
            tool_calls: None,
            tool_call_id: None,
        };
        let v = serde_json::to_value(&msg).unwrap();
        assert_eq!(v["role"], "user");
        assert_eq!(v["content"], "hello");
        // None fields with skip_serializing_if should be absent
        assert!(v.get("tool_calls").is_none());
        assert!(v.get("tool_call_id").is_none());
    }

    #[test]
    fn chat_message_serializes_with_tool_calls() {
        let msg = ChatMessage {
            role: "assistant".to_string(),
            content: None,
            tool_calls: Some(vec![ToolCallRequest {
                id: "call_1".to_string(),
                r#type: "function".to_string(),
                function: ToolCallRequestFunction {
                    name: "get_weather".to_string(),
                    arguments: r#"{"city":"NYC"}"#.to_string(),
                },
            }]),
            tool_call_id: None,
        };
        let v = serde_json::to_value(&msg).unwrap();
        assert!(v["tool_calls"].is_array());
        assert_eq!(v["tool_calls"][0]["function"]["name"], "get_weather");
    }

    #[test]
    fn chat_message_content_null_when_none() {
        let msg = ChatMessage {
            role: "assistant".to_string(),
            content: None,
            tool_calls: None,
            tool_call_id: None,
        };
        let v = serde_json::to_value(&msg).unwrap();
        assert!(v["content"].is_null());
    }

    // ── ToolDefinition ───────────────────────────────────────────

    #[test]
    fn tool_definition_serializes() {
        let td = ToolDefinition {
            name: "search".to_string(),
            description: "Search the web".to_string(),
            parameters: json!({"type": "object", "properties": {"q": {"type": "string"}}}),
        };
        let v = serde_json::to_value(&td).unwrap();
        assert_eq!(v["name"], "search");
        assert_eq!(v["parameters"]["type"], "object");
    }

    #[test]
    fn tool_definition_empty_parameters() {
        let td = ToolDefinition {
            name: "noop".to_string(),
            description: "".to_string(),
            parameters: json!({}),
        };
        let v = serde_json::to_value(&td).unwrap();
        assert_eq!(v["parameters"], json!({}));
    }

    // ── LlmToolCall ──────────────────────────────────────────────

    #[test]
    fn llm_tool_call_roundtrip() {
        let call = LlmToolCall {
            id: "tc_42".to_string(),
            name: "run_scan".to_string(),
            arguments: json!({"path": "/tmp", "verbose": true}),
        };
        let serialized = serde_json::to_string(&call).unwrap();
        let deserialized: LlmToolCall = serde_json::from_str(&serialized).unwrap();
        assert_eq!(deserialized.id, "tc_42");
        assert_eq!(deserialized.name, "run_scan");
        assert_eq!(deserialized.arguments["path"], "/tmp");
        assert_eq!(deserialized.arguments["verbose"], true);
    }

    #[test]
    fn llm_tool_call_empty_arguments() {
        let call = LlmToolCall {
            id: "tc_0".to_string(),
            name: "noop".to_string(),
            arguments: json!({}),
        };
        let rt: LlmToolCall = serde_json::from_str(&serde_json::to_string(&call).unwrap()).unwrap();
        assert!(rt.arguments.as_object().unwrap().is_empty());
    }

    // ── ToolCallRequest / ToolCallRequestFunction ────────────────

    #[test]
    fn tool_call_request_roundtrip() {
        let req = ToolCallRequest {
            id: "call_abc".to_string(),
            r#type: "function".to_string(),
            function: ToolCallRequestFunction {
                name: "my_func".to_string(),
                arguments: r#"{"x":1}"#.to_string(),
            },
        };
        let json_str = serde_json::to_string(&req).unwrap();
        let back: ToolCallRequest = serde_json::from_str(&json_str).unwrap();
        assert_eq!(back.id, "call_abc");
        assert_eq!(back.r#type, "function");
        assert_eq!(back.function.name, "my_func");
        assert_eq!(back.function.arguments, r#"{"x":1}"#);
    }

    #[test]
    fn tool_call_request_type_field_serializes_as_type() {
        let req = ToolCallRequest {
            id: "id".to_string(),
            r#type: "function".to_string(),
            function: ToolCallRequestFunction {
                name: "f".to_string(),
                arguments: "{}".to_string(),
            },
        };
        let v = serde_json::to_value(&req).unwrap();
        // The field should be "type" in JSON, not "r#type"
        assert!(v.get("type").is_some());
        assert!(v.get("r#type").is_none());
    }

    // ── ChatCompletionRequest ────────────────────────────────────

    #[test]
    fn chat_completion_request_skips_none_fields() {
        let req = ChatCompletionRequest {
            model: "gpt-4".to_string(),
            messages: vec![],
            temperature: None,
            max_tokens: None,
            tools: None,
        };
        let v = serde_json::to_value(&req).unwrap();
        assert_eq!(v["model"], "gpt-4");
        assert!(v.get("temperature").is_none());
        assert!(v.get("max_tokens").is_none());
        assert!(v.get("tools").is_none());
    }

    #[test]
    fn chat_completion_request_includes_set_fields() {
        let req = ChatCompletionRequest {
            model: "gpt-4".to_string(),
            messages: vec![],
            temperature: Some(0.7),
            max_tokens: Some(1024),
            tools: Some(vec![]),
        };
        let v = serde_json::to_value(&req).unwrap();
        assert_eq!(v["temperature"], 0.7);
        assert_eq!(v["max_tokens"], 1024);
        assert!(v["tools"].is_array());
    }

    // ── ChatCompletionResponse deserialization ───────────────────

    #[test]
    fn chat_completion_response_deserializes_content() {
        let json_str = r#"{"choices":[{"message":{"content":"Hello!"}}]}"#;
        let resp: ChatCompletionResponse = serde_json::from_str(json_str).unwrap();
        assert_eq!(resp.choices.len(), 1);
        assert_eq!(resp.choices[0].message.content.as_deref(), Some("Hello!"));
        assert!(resp.choices[0].message.tool_calls.is_none());
    }

    #[test]
    fn chat_completion_response_deserializes_tool_calls() {
        let json_str = r#"{
            "choices": [{
                "message": {
                    "tool_calls": [{
                        "id": "call_1",
                        "function": {"name": "search", "arguments": "{\"q\":\"rust\"}"}
                    }]
                }
            }]
        }"#;
        let resp: ChatCompletionResponse = serde_json::from_str(json_str).unwrap();
        let tc = resp.choices[0].message.tool_calls.as_ref().unwrap();
        assert_eq!(tc.len(), 1);
        assert_eq!(tc[0].id, "call_1");
        assert_eq!(tc[0].function.name, "search");
    }

    #[test]
    fn chat_completion_response_defaults_missing_fields() {
        // content and tool_calls are both missing — should default to None
        let json_str = r#"{"choices":[{"message":{}}]}"#;
        let resp: ChatCompletionResponse = serde_json::from_str(json_str).unwrap();
        assert!(resp.choices[0].message.content.is_none());
        assert!(resp.choices[0].message.tool_calls.is_none());
    }

    // ── LlmResponse ─────────────────────────────────────────────

    #[test]
    fn llm_response_content_variant() {
        let resp = LlmResponse::Content("answer".to_string());
        match resp {
            LlmResponse::Content(s) => assert_eq!(s, "answer"),
            _ => panic!("expected Content variant"),
        }
    }

    #[test]
    fn llm_response_tool_calls_variant() {
        let resp = LlmResponse::ToolCalls {
            calls: vec![LlmToolCall {
                id: "1".to_string(),
                name: "f".to_string(),
                arguments: json!({}),
            }],
            reasoning: "because".to_string(),
        };
        match resp {
            LlmResponse::ToolCalls { calls, reasoning } => {
                assert_eq!(calls.len(), 1);
                assert_eq!(reasoning, "because");
            }
            _ => panic!("expected ToolCalls variant"),
        }
    }

    #[test]
    fn llm_response_empty_content() {
        let resp = LlmResponse::Content(String::new());
        match resp {
            LlmResponse::Content(s) => assert!(s.is_empty()),
            _ => panic!("expected Content variant"),
        }
    }
}
