use artifacthub_client::models::SearchResponse;
use rmcp::handler::server::wrapper::Json;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::tools::ArtifactHubServer;
use artifacthub_client::kind::{self as pkg_kind};

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct SearchParams {
    #[schemars(description = "Search query string")]
    pub q: Option<String>,
    #[schemars(description = pkg_kind::KIND_DESCRIPTION)]
    pub kind: Option<String>,
    #[schemars(description = "Filter by repository name")]
    pub repo: Option<String>,
    #[schemars(description = "Filter by organization name")]
    pub org: Option<String>,
    #[schemars(description = "Number of results (max 60)")]
    pub limit: Option<usize>,
    #[schemars(description = "Offset for pagination")]
    pub offset: Option<usize>,
}

fn resolve_kind(kind: &str) -> Result<String, String> {
    if let Some(id) = pkg_kind::to_id(kind) {
        Ok(id.to_string())
    } else {
        Err(format!(
            "Unknown kind: '{}'. Valid kinds: {}",
            kind,
            pkg_kind::valid_kinds().join(", ")
        ))
    }
}

pub async fn handle_search_packages(
    server: &ArtifactHubServer,
    params: SearchParams,
) -> Result<Json<SearchResponse>, String> {
    if let Some(limit) = params.limit
        && (limit == 0 || limit > 60)
    {
        return Err("limit must be between 1 and 60".to_string());
    }

    let kind = params.kind.as_deref().map(resolve_kind).transpose()?;
    let mut search = server.client.packages().search();

    if let Some(q) = params.q {
        search = search.query(q);
    }
    if let Some(kind) = kind {
        search = search.kind(kind);
    }
    if let Some(repo) = params.repo {
        search = search.repo(repo);
    }
    if let Some(org) = params.org {
        search = search.org(org);
    }
    if let Some(limit) = params.limit {
        search = search.limit(limit);
    }
    if let Some(offset) = params.offset {
        search = search.offset(offset);
    }

    let response = search.send().await?;

    Ok(Json(response))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tools::ALL_TOOL_NAMES;
    use artifacthub_client::client::ArtifactHubClient;
    use std::collections::HashSet;
    use wiremock::matchers::{method, path, query_param};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    fn test_server(base_url: &str) -> ArtifactHubServer {
        ArtifactHubServer {
            client: ArtifactHubClient::with_base_url(base_url),
            enabled_tools: ALL_TOOL_NAMES
                .iter()
                .map(|s| s.to_string())
                .collect::<HashSet<_>>(),
        }
    }

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

        Mock::given(method("GET"))
            .and(path("/packages/search"))
            .and(query_param("ts_query_web", "test"))
            .and(query_param("kind", "0"))
            .and(query_param("limit", "10"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
                "packages": [
                    {
                        "package_id": "abc-123",
                        "name": "test-chart",
                        "normalized_name": "test-chart",
                        "version": "1.0.0",
                        "description": "A test chart",
                        "repository": {
                            "name": "test-repo",
                            "display_name": "Test Repo",
                            "url": "https://example.com"
                        },
                        "deprecated": false,
                        "signed": false,
                        "stars": 42,
                        "ts": 1700000000
                    }
                ]
            })))
            .mount(&mock_server)
            .await;

        let server = test_server(&mock_server.uri());
        let result = handle_search_packages(
            &server,
            SearchParams {
                q: Some("test".to_string()),
                kind: Some("helm".to_string()),
                repo: None,
                org: None,
                limit: Some(10),
                offset: None,
            },
        )
        .await
        .unwrap();

        assert_eq!(result.0.packages.len(), 1);
        assert_eq!(result.0.packages[0].name, "test-chart");
        assert_eq!(result.0.packages[0].stars, 42);
    }

    #[tokio::test]
    async fn test_search_packages_invalid_kind() {
        let server = test_server("http://localhost:12345");
        let result = handle_search_packages(
            &server,
            SearchParams {
                q: None,
                kind: Some("invalid-kind".to_string()),
                repo: None,
                org: None,
                limit: None,
                offset: None,
            },
        )
        .await;

        assert!(result.is_err());
        let Err(err) = result else {
            panic!("expected error")
        };
        assert!(err.contains("Unknown kind"));
        assert!(err.contains("Valid kinds"));
    }

    #[tokio::test]
    async fn test_search_packages_limit_too_high() {
        let server = test_server("http://localhost:12345");
        let result = handle_search_packages(
            &server,
            SearchParams {
                q: None,
                kind: None,
                repo: None,
                org: None,
                limit: Some(61),
                offset: None,
            },
        )
        .await;

        assert!(result.is_err());
        let Err(err) = result else {
            panic!("expected error")
        };
        assert!(err.contains("limit must be between 1 and 60"));
    }

    #[tokio::test]
    async fn test_search_packages_limit_zero() {
        let server = test_server("http://localhost:12345");
        let result = handle_search_packages(
            &server,
            SearchParams {
                q: None,
                kind: None,
                repo: None,
                org: None,
                limit: Some(0),
                offset: None,
            },
        )
        .await;

        assert!(result.is_err());
        let Err(err) = result else {
            panic!("expected error")
        };
        assert!(err.contains("limit must be between 1 and 60"));
    }
}
