use mongodb::bson::doc;
use rmcp::{model::*, ErrorData as McpError};
use schemars::JsonSchema;
use serde::Deserialize;

use crate::database::Database;

const MAX_LIMIT: i64 = 200;
const DEFAULT_LIMIT: i64 = 50;

fn cap_limit(limit: Option<i64>) -> i64 {
    limit.unwrap_or(DEFAULT_LIMIT).clamp(1, MAX_LIMIT)
}

#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListSbomPackagesParams {
    /// Filter by repository ID
    pub repo_id: Option<String>,
    /// Only show packages with known vulnerabilities
    pub has_vulns: Option<bool>,
    /// Filter by package manager (e.g. npm, cargo, pip)
    pub package_manager: Option<String>,
    /// Filter by license (e.g. MIT, Apache-2.0)
    pub license: Option<String>,
    /// Maximum number of results (default 50, max 200)
    pub limit: Option<i64>,
}

pub async fn list_sbom_packages(
    db: &Database,
    params: ListSbomPackagesParams,
) -> Result<CallToolResult, McpError> {
    let mut filter = doc! {};
    if let Some(ref repo_id) = params.repo_id {
        filter.insert("repo_id", repo_id);
    }
    if let Some(ref pm) = params.package_manager {
        filter.insert("package_manager", pm);
    }
    if let Some(ref license) = params.license {
        filter.insert("license", license);
    }
    if params.has_vulns == Some(true) {
        filter.insert("known_vulnerabilities.0", doc! { "$exists": true });
    }

    let limit = cap_limit(params.limit);

    let mut cursor = db
        .sbom_entries()
        .find(filter)
        .sort(doc! { "name": 1 })
        .limit(limit)
        .await
        .map_err(|e| McpError::internal_error(format!("DB error: {e}"), None))?;

    let mut results = Vec::new();
    while cursor
        .advance()
        .await
        .map_err(|e| McpError::internal_error(format!("cursor error: {e}"), None))?
    {
        let entry = cursor
            .deserialize_current()
            .map_err(|e| McpError::internal_error(format!("deserialize error: {e}"), None))?;
        results.push(entry);
    }

    let json = serde_json::to_string_pretty(&results)
        .map_err(|e| McpError::internal_error(format!("json error: {e}"), None))?;

    Ok(CallToolResult::success(vec![Content::text(json)]))
}

#[derive(Debug, Deserialize, JsonSchema)]
pub struct SbomVulnReportParams {
    /// Repository ID to generate vulnerability report for
    pub repo_id: String,
}

pub async fn sbom_vuln_report(
    db: &Database,
    params: SbomVulnReportParams,
) -> Result<CallToolResult, McpError> {
    let filter = doc! {
        "repo_id": &params.repo_id,
        "known_vulnerabilities.0": { "$exists": true },
    };

    let mut cursor = db
        .sbom_entries()
        .find(filter)
        .sort(doc! { "name": 1 })
        .await
        .map_err(|e| McpError::internal_error(format!("DB error: {e}"), None))?;

    let mut vulnerable_packages = Vec::new();
    let mut total_vulns = 0u64;

    while cursor
        .advance()
        .await
        .map_err(|e| McpError::internal_error(format!("cursor error: {e}"), None))?
    {
        let entry = cursor
            .deserialize_current()
            .map_err(|e| McpError::internal_error(format!("deserialize error: {e}"), None))?;
        total_vulns += entry.known_vulnerabilities.len() as u64;
        vulnerable_packages.push(serde_json::json!({
            "name": entry.name,
            "version": entry.version,
            "package_manager": entry.package_manager,
            "license": entry.license,
            "vulnerabilities": entry.known_vulnerabilities,
        }));
    }

    let report = serde_json::json!({
        "repo_id": params.repo_id,
        "vulnerable_packages_count": vulnerable_packages.len(),
        "total_vulnerabilities": total_vulns,
        "packages": vulnerable_packages,
    });

    let json = serde_json::to_string_pretty(&report)
        .map_err(|e| McpError::internal_error(format!("json error: {e}"), None))?;

    Ok(CallToolResult::success(vec![Content::text(json)]))
}
