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

use compliance_core::models::TrackedRepository;

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

#[server]
pub async fn fetch_repositories(page: u64) -> Result<RepositoryListResponse, ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!(
        "{}/api/v1/repositories?page={page}&limit=20",
        state.agent_api_url
    );

    let resp = reqwest::get(&url)
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: RepositoryListResponse = resp
        .json()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    Ok(body)
}

#[server]
pub async fn add_repository(
    name: String,
    git_url: String,
    default_branch: String,
    auth_token: Option<String>,
    auth_username: Option<String>,
    tracker_type: Option<String>,
    tracker_owner: Option<String>,
    tracker_repo: Option<String>,
    tracker_token: Option<String>,
) -> Result<(), ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!("{}/api/v1/repositories", state.agent_api_url);

    let mut body = serde_json::json!({
        "name": name,
        "git_url": git_url,
        "default_branch": default_branch,
    });
    if let Some(token) = auth_token.filter(|t| !t.is_empty()) {
        body["auth_token"] = serde_json::Value::String(token);
    }
    if let Some(username) = auth_username.filter(|u| !u.is_empty()) {
        body["auth_username"] = serde_json::Value::String(username);
    }
    if let Some(tt) = tracker_type.filter(|t| !t.is_empty()) {
        body["tracker_type"] = serde_json::Value::String(tt);
    }
    if let Some(to) = tracker_owner.filter(|t| !t.is_empty()) {
        body["tracker_owner"] = serde_json::Value::String(to);
    }
    if let Some(tr) = tracker_repo.filter(|t| !t.is_empty()) {
        body["tracker_repo"] = serde_json::Value::String(tr);
    }
    if let Some(tk) = tracker_token.filter(|t| !t.is_empty()) {
        body["tracker_token"] = serde_json::Value::String(tk);
    }

    let client = reqwest::Client::new();
    let resp = client
        .post(&url)
        .json(&body)
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    if !resp.status().is_success() {
        let body = resp.text().await.unwrap_or_default();
        return Err(ServerFnError::new(format!(
            "Failed to add repository: {body}"
        )));
    }

    Ok(())
}

#[server]
pub async fn update_repository(
    repo_id: String,
    name: Option<String>,
    default_branch: Option<String>,
    auth_token: Option<String>,
    auth_username: Option<String>,
    tracker_type: Option<String>,
    tracker_owner: Option<String>,
    tracker_repo: Option<String>,
    tracker_token: Option<String>,
    scan_schedule: Option<String>,
) -> Result<(), ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!("{}/api/v1/repositories/{repo_id}", state.agent_api_url);

    let mut body = serde_json::Map::new();
    if let Some(v) = name.filter(|s| !s.is_empty()) {
        body.insert("name".into(), serde_json::Value::String(v));
    }
    if let Some(v) = default_branch.filter(|s| !s.is_empty()) {
        body.insert("default_branch".into(), serde_json::Value::String(v));
    }
    if let Some(v) = auth_token {
        body.insert("auth_token".into(), serde_json::Value::String(v));
    }
    if let Some(v) = auth_username {
        body.insert("auth_username".into(), serde_json::Value::String(v));
    }
    if let Some(v) = tracker_type {
        body.insert("tracker_type".into(), serde_json::Value::String(v));
    }
    if let Some(v) = tracker_owner {
        body.insert("tracker_owner".into(), serde_json::Value::String(v));
    }
    if let Some(v) = tracker_repo {
        body.insert("tracker_repo".into(), serde_json::Value::String(v));
    }
    if let Some(v) = tracker_token {
        body.insert("tracker_token".into(), serde_json::Value::String(v));
    }
    if let Some(v) = scan_schedule {
        body.insert("scan_schedule".into(), serde_json::Value::String(v));
    }

    let client = reqwest::Client::new();
    let resp = client
        .patch(&url)
        .json(&body)
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    if !resp.status().is_success() {
        let text = resp.text().await.unwrap_or_default();
        return Err(ServerFnError::new(format!(
            "Failed to update repository: {text}"
        )));
    }

    Ok(())
}

#[server]
pub async fn fetch_ssh_public_key() -> Result<String, ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!("{}/api/v1/settings/ssh-public-key", state.agent_api_url);

    let resp = reqwest::get(&url)
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    if !resp.status().is_success() {
        return Err(ServerFnError::new("SSH key not available".to_string()));
    }

    let body: serde_json::Value = resp
        .json()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    Ok(body
        .get("public_key")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string())
}

#[server]
pub async fn delete_repository(repo_id: String) -> Result<(), ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!("{}/api/v1/repositories/{repo_id}", state.agent_api_url);

    let client = reqwest::Client::new();
    let resp = client
        .delete(&url)
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    if !resp.status().is_success() {
        let body = resp.text().await.unwrap_or_default();
        return Err(ServerFnError::new(format!(
            "Failed to delete repository: {body}"
        )));
    }

    Ok(())
}

#[server]
pub async fn trigger_repo_scan(repo_id: String) -> Result<(), ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!("{}/api/v1/repositories/{repo_id}/scan", state.agent_api_url);

    let client = reqwest::Client::new();
    client
        .post(&url)
        .send()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    Ok(())
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WebhookConfigResponse {
    pub webhook_secret: Option<String>,
    pub tracker_type: String,
}

#[server]
pub async fn fetch_webhook_config(repo_id: String) -> Result<WebhookConfigResponse, ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!(
        "{}/api/v1/repositories/{repo_id}/webhook-config",
        state.agent_api_url
    );

    let resp = reqwest::get(&url)
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: WebhookConfigResponse = resp
        .json()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    Ok(body)
}

/// Check if a repository has any running scans
#[server]
pub async fn check_repo_scanning(repo_id: String) -> Result<bool, ServerFnError> {
    let state: super::server_state::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;
    let url = format!("{}/api/v1/scan-runs?page=1&limit=1", state.agent_api_url);

    let resp = reqwest::get(&url)
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let body: serde_json::Value = resp
        .json()
        .await
        .map_err(|e| ServerFnError::new(e.to_string()))?;

    // Check if the most recent scan for this repo is still running
    if let Some(scans) = body.get("data").and_then(|d| d.as_array()) {
        for scan in scans {
            let scan_repo = scan.get("repo_id").and_then(|v| v.as_str()).unwrap_or("");
            let status = scan.get("status").and_then(|v| v.as_str()).unwrap_or("");
            if scan_repo == repo_id && status == "running" {
                return Ok(true);
            }
        }
    }

    Ok(false)
}
