use dioxus::prelude::*;
use serde::{Deserialize, Serialize};

// ── Local types (no bson dependency, WASM-safe) ──

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VulnRefData {
    pub id: String,
    pub source: String,
    pub severity: Option<String>,
    pub url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomEntryData {
    #[serde(rename = "_id", default)]
    pub id: Option<serde_json::Value>,
    pub repo_id: String,
    pub name: String,
    pub version: String,
    pub package_manager: String,
    pub license: Option<String>,
    pub purl: Option<String>,
    #[serde(default)]
    pub known_vulnerabilities: Vec<VulnRefData>,
    #[serde(default)]
    pub created_at: Option<serde_json::Value>,
    #[serde(default)]
    pub updated_at: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomListResponse {
    pub data: Vec<SbomEntryData>,
    pub total: Option<u64>,
    pub page: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LicenseSummaryData {
    pub license: String,
    pub count: u64,
    pub is_copyleft: bool,
    pub packages: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LicenseSummaryResponse {
    pub data: Vec<LicenseSummaryData>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomDiffEntryData {
    pub name: String,
    pub version: String,
    pub package_manager: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomVersionDiffData {
    pub name: String,
    pub package_manager: String,
    pub version_a: String,
    pub version_b: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomDiffResultData {
    pub only_in_a: Vec<SbomDiffEntryData>,
    pub only_in_b: Vec<SbomDiffEntryData>,
    pub version_changed: Vec<SbomVersionDiffData>,
    pub common_count: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomDiffResponse {
    pub data: SbomDiffResultData,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SbomFiltersResponse {
    pub package_managers: Vec<String>,
    pub licenses: Vec<String>,
}

// ── Server functions ──

#[server]
pub async fn fetch_sbom_filters() -> Result<SbomFiltersResponse, ServerFnError> {
    let resp = super::agent_client::agent_get("/api/v1/sbom/filters")
        .await?
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let text = resp
        .text()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: SbomFiltersResponse = serde_json::from_str(&text)
        .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?;
    Ok(body)
}

#[server]
pub async fn fetch_sbom_filtered(
    repo_id: Option<String>,
    package_manager: Option<String>,
    q: Option<String>,
    has_vulns: Option<bool>,
    license: Option<String>,
    page: u64,
) -> Result<SbomListResponse, ServerFnError> {
    let mut params = vec![format!("page={page}"), "limit=50".to_string()];
    if let Some(r) = &repo_id {
        if !r.is_empty() {
            params.push(format!("repo_id={r}"));
        }
    }
    if let Some(pm) = &package_manager {
        if !pm.is_empty() {
            params.push(format!("package_manager={pm}"));
        }
    }
    if let Some(q) = &q {
        if !q.is_empty() {
            params.push(format!("q={}", q.replace(' ', "%20")));
        }
    }
    if let Some(hv) = has_vulns {
        params.push(format!("has_vulns={hv}"));
    }
    if let Some(l) = &license {
        if !l.is_empty() {
            params.push(format!("license={}", l.replace(' ', "%20")));
        }
    }

    let path = format!("/api/v1/sbom?{}", params.join("&"));
    let resp = super::agent_client::agent_get(&path)
        .await?
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let text = resp
        .text()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: SbomListResponse = serde_json::from_str(&text)
        .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?;
    Ok(body)
}

#[server]
pub async fn fetch_sbom_export(repo_id: String, format: String) -> Result<String, ServerFnError> {
    let path = format!("/api/v1/sbom/export?repo_id={repo_id}&format={format}");
    let resp = super::agent_client::agent_get(&path)
        .await?
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let text = resp
        .text()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    Ok(text)
}

#[server]
pub async fn fetch_license_summary(
    repo_id: Option<String>,
) -> Result<LicenseSummaryResponse, ServerFnError> {
    let mut path = "/api/v1/sbom/licenses".to_string();
    if let Some(r) = &repo_id {
        if !r.is_empty() {
            path = format!("{path}?repo_id={r}");
        }
    }

    let resp = super::agent_client::agent_get(&path)
        .await?
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let text = resp
        .text()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: LicenseSummaryResponse = serde_json::from_str(&text)
        .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?;
    Ok(body)
}

#[server]
pub async fn fetch_sbom_diff(
    repo_a: String,
    repo_b: String,
) -> Result<SbomDiffResponse, ServerFnError> {
    let path = format!("/api/v1/sbom/diff?repo_a={repo_a}&repo_b={repo_b}");
    let resp = super::agent_client::agent_get(&path)
        .await?
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let text = resp
        .text()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: SbomDiffResponse = serde_json::from_str(&text)
        .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?;
    Ok(body)
}
