// Copyright 2026 Cloudflavor GmbH

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Mock tests for the ollama-api-rs crate
//!
//! These tests use mock HTTP responses to test the client without requiring
//! a running Ollama instance.

#[cfg(test)]
mod tests {
    use oai_sdk::{
        ChatRequest, CopyModelRequest, DeleteModelRequest, EmbeddingsRequest, GenerateRequest,
        Message, ModelClient, ShowModelRequest,
    };
    use serde_json::json;
    use tokio_stream::StreamExt;
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[tokio::test]
    async fn test_get_version() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("GET"))
            .and(path("/api/version"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "version": "0.1.0"
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let version = client.get_version().await.unwrap();
        assert_eq!(version.version, "0.1.0");
    }

    #[tokio::test]
    async fn test_list_models() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("GET"))
            .and(path("/api/tags"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "models": [
                    {
                        "name": "llama3.1:8b",
                        "model": "llama3.1:8b",
                        "modified_at": "2023-08-04T19:22:45.499127Z",
                        "size": 1234567890,
                        "digest": "sha256:...",
                        "details": {
                            "parent_model": "",
                            "format": "gguf",
                            "family": "llama",
                            "families": ["llama"],
                            "parameter_size": "7B",
                            "quantization_level": "Q4_0"
                        }
                    }
                ]
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let models = client.list_models().await.unwrap();
        assert_eq!(models.len(), 1);
        assert_eq!(models[0].name, "llama3.1:8b");
        assert_eq!(models[0].details.parameter_size, "7B");
    }

    #[tokio::test]
    async fn test_chat_completion() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/chat"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "model": "llama3.1:8b",
                "created_at": "2023-08-04T19:22:45.499127Z",
                "message": {
                    "role": "assistant",
                    "content": "Hello! How can I help you today?",
                    "images": null
                },
                "done": true,
                "total_duration": 5191566416u64,
                "load_duration": 2154458u64,
                "prompt_eval_count": 26u32,
                "prompt_eval_duration": 383809000u64,
                "eval_count": 298u32,
                "eval_duration": 4799921000u64
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = ChatRequest {
            model: "llama3.1:8b".to_string(),
            messages: vec![Message::user("Hello!")],
            stream: false,
            format: None,
            options: None,
            keep_alive: None,
            tools: None,
            think: None,
        };

        let response = client.chat(request).await.unwrap();
        assert_eq!(response.message.content, "Hello! How can I help you today?");
        assert!(response.done);
    }

    #[tokio::test]
    async fn test_generate_completion() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "model": "llama3.1:8b",
                "created_at": "2023-08-04T19:22:45.499127Z",
                "response": "The sky is blue because of Rayleigh scattering.",
                "done": true,
                "total_duration": 5191566416u64,
                "load_duration": 2154458u64,
                "prompt_eval_count": 26u32,
                "prompt_eval_duration": 383809000u64,
                "eval_count": 298u32,
                "eval_duration": 4799921000u64
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = GenerateRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "Why is the sky blue?".to_string(),
            stream: false,
            suffix: None,
            images: None,
            format: None,
            options: None,
            system: None,
            template: None,
            raw: None,
            keep_alive: None,
            context: None,
            think: None,
            width: None,
            height: None,
            steps: None,
        };

        let response = client.generate(request).await.unwrap();
        assert_eq!(
            response.response,
            "The sky is blue because of Rayleigh scattering."
        );
        assert!(response.done);
    }

    #[tokio::test]
    async fn test_embeddings() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/embeddings"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "embedding": [0.1, 0.2, 0.3, 0.4, 0.5]
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = oai_sdk::EmbeddingsRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "Hello world".to_string(),
            options: None,
            keep_alive: None,
            truncate: None,
        };

        let response = client.embeddings(request).await.unwrap();
        assert_eq!(response.embedding.len(), 5);
        assert_eq!(response.embedding[0], 0.1);
    }

    #[tokio::test]
    async fn test_show_model() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/show"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "modelfile": "FROM llama3\nPARAMETER temperature 0.7",
                "parameters": "temperature 0.7",
                "template": "[INST] {{ .Prompt }} [/INST]",
                "details": {
                    "parent_model": "",
                    "format": "gguf",
                    "family": "llama",
                    "families": ["llama"],
                    "parameter_size": "7B",
                    "quantization_level": "Q4_0"
                }
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = ShowModelRequest {
            model: "llama3.1:8b".to_string(),
            verbose: None,
        };

        let response = client.show_model(request).await.unwrap();
        assert_eq!(response.modelfile, "FROM llama3\nPARAMETER temperature 0.7");
        assert_eq!(response.details.family, "llama");
    }

    #[tokio::test]
    async fn test_copy_model() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/copy"))
            .respond_with(ResponseTemplate::new(200))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = CopyModelRequest {
            source: "llama3.1:8b".to_string(),
            destination: "llama3-copy".to_string(),
        };

        let result = client.copy_model(request).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_delete_model() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("DELETE"))
            .and(path("/api/delete"))
            .respond_with(ResponseTemplate::new(200))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = DeleteModelRequest {
            model: "llama3.1:8b".to_string(),
        };

        let result = client.delete_model(request).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    #[cfg(feature = "local")]
    async fn test_blob_exists() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("HEAD"))
            .and(path("/api/blobs/sha256:abc123"))
            .respond_with(ResponseTemplate::new(200))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let result = client.blob_exists("sha256:abc123").await;
        assert!(result.unwrap());
    }

    #[tokio::test]
    #[cfg(feature = "local")]
    async fn test_blob_not_exists() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("HEAD"))
            .and(path("/api/blobs/sha256:def456"))
            .respond_with(ResponseTemplate::new(404))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let result = client.blob_exists("sha256:def456").await;
        assert!(!result.unwrap());
    }

    #[tokio::test]
    #[cfg(feature = "local")]
    async fn test_push_blob() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/blobs/sha256:abc123"))
            .respond_with(ResponseTemplate::new(201))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let content = b"test blob content";
        let result = client.push_blob("sha256:abc123", content).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    #[cfg(feature = "local")]
    async fn test_load_model() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "model": "llama3.1:8b",
                "created_at": "2024-01-01T00:00:00Z",
                "response": "",
                "done": true
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let result = client.load_model("llama3.1:8b").await;
        assert!(result.is_ok());
        assert!(result.unwrap().done);
    }

    #[tokio::test]
    #[cfg(feature = "local")]
    async fn test_unload_model() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "model": "llama3.1:8b",
                "created_at": "2024-01-01T00:00:00Z",
                "response": "",
                "done": true
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let result = client.unload_model("llama3.1:8b").await;
        assert!(result.is_ok());
        assert!(result.unwrap().done);
    }

    #[tokio::test]
    async fn test_embeddings_truncate() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/embeddings"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "embedding": [0.1, 0.2, 0.3]
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();

        let request = EmbeddingsRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "test".to_string(),
            truncate: Some(true),
            ..Default::default()
        };

        let result = client.embeddings(request).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_generate_stream() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        let chunks = [
            json!({"model":"llama3.1:8b","created_at":"2024-01-01T00:00:00Z","response":"Hello","done":false}),
            json!({"model":"llama3.1:8b","created_at":"2024-01-01T00:00:01Z","response":" world","done":false}),
            json!({"model":"llama3.1:8b","created_at":"2024-01-01T00:00:02Z","response":"!","done":true}),
        ];
        let body = chunks
            .iter()
            .map(|c| serde_json::to_string(c).unwrap())
            .collect::<Vec<_>>()
            .join("\n");

        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();
        let request = GenerateRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "Say hello".to_string(),
            stream: true,
            ..Default::default()
        };

        let mut stream = client.generate_stream(request).await.unwrap();
        let mut responses = Vec::new();
        while let Some(r) = stream.next().await {
            responses.push(r.unwrap());
        }

        assert_eq!(responses.len(), 3);
        assert!(responses[2].done);
        let full: String = responses.iter().map(|r| r.response.as_str()).collect();
        assert_eq!(full, "Hello world!");
    }

    #[tokio::test]
    async fn test_generate_stream_error_status() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(500).set_body_json(json!({
                "error": "internal server error"
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();
        let request = GenerateRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "test".to_string(),
            stream: true,
            ..Default::default()
        };

        let result = client.generate_stream(request).await;
        assert!(result.is_err());
        if let Err(e) = result {
            let msg = format!("{}", e);
            assert!(msg.contains("500") || msg.contains("internal"));
        }
    }

    #[tokio::test]
    async fn test_chat_stream() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        let chunks = [
            json!({"model":"llama3.1:8b","created_at":"2024-01-01T00:00:00Z","message":{"role":"assistant","content":"Hello"},"done":false}),
            json!({"model":"llama3.1:8b","created_at":"2024-01-01T00:00:01Z","message":{"role":"assistant","content":" world"},"done":false}),
            json!({"model":"llama3.1:8b","created_at":"2024-01-01T00:00:02Z","message":{"role":"assistant","content":""},"done":true}),
        ];
        let body = chunks
            .iter()
            .map(|c| serde_json::to_string(c).unwrap())
            .collect::<Vec<_>>()
            .join("\n");

        Mock::given(method("POST"))
            .and(path("/api/chat"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();
        let request = ChatRequest {
            model: "llama3.1:8b".to_string(),
            messages: vec![Message::user("Say hello")],
            stream: true,
            ..Default::default()
        };

        let mut stream = client.chat_stream(request).await.unwrap();
        let mut responses = Vec::new();
        while let Some(r) = stream.next().await {
            responses.push(r.unwrap());
        }

        assert_eq!(responses.len(), 3);
        assert!(responses[2].done);
    }

    #[tokio::test]
    async fn test_chat_stream_error_status() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/chat"))
            .respond_with(ResponseTemplate::new(503).set_body_json(json!({
                "error": "service unavailable"
            })))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();
        let request = ChatRequest {
            model: "llama3.1:8b".to_string(),
            messages: vec![Message::user("test")],
            stream: true,
            ..Default::default()
        };

        let result = client.chat_stream(request).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_generate_stream_skips_empty_lines() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        let body = "\n\n{\"model\":\"llama3.1:8b\",\"created_at\":\"2024-01-01T00:00:00Z\",\"response\":\"ok\",\"done\":true}\n\n";
        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();
        let request = GenerateRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "test".to_string(),
            stream: true,
            ..Default::default()
        };

        let mut stream = client.generate_stream(request).await.unwrap();
        let mut count = 0;
        while let Some(r) = stream.next().await {
            let chunk = r.unwrap();
            assert!(chunk.done);
            count += 1;
        }
        assert_eq!(count, 1);
    }

    #[tokio::test]
    async fn test_generate_stream_malformed_json() {
        let mock_server = MockServer::start().await;
        let url = mock_server.uri();

        Mock::given(method("POST"))
            .and(path("/api/generate"))
            .respond_with(ResponseTemplate::new(200).set_body_string("not json at all"))
            .mount(&mock_server)
            .await;

        let client = ModelClient::builder().base_url(url).build().unwrap();
        let request = GenerateRequest {
            model: "llama3.1:8b".to_string(),
            prompt: "test".to_string(),
            stream: true,
            ..Default::default()
        };

        let mut stream = client.generate_stream(request).await.unwrap();
        let chunk = stream.next().await.unwrap();
        assert!(chunk.is_err());
        let err = format!("{}", chunk.unwrap_err());
        assert!(err.contains("JSON parsing failed"));
    }
}
