use crate::common::TestServer;
use serde_json::json;

/// Insert a DAST target directly into MongoDB linked to a repo.
async fn insert_dast_target(server: &TestServer, repo_id: &str, name: &str) -> String {
    let mongodb_uri = std::env::var("TEST_MONGODB_URI")
        .unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
    let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
    let db = client.database(&server.db_name());

    let result = db
        .collection::<mongodb::bson::Document>("dast_targets")
        .insert_one(mongodb::bson::doc! {
            "name": name,
            "base_url": format!("https://{name}.example.com"),
            "target_type": "webapp",
            "repo_id": repo_id,
            "rate_limit": 10,
            "allow_destructive": false,
            "created_at": mongodb::bson::DateTime::now(),
        })
        .await
        .unwrap();

    result.inserted_id.as_object_id().unwrap().to_hex()
}

/// Insert a pentest session linked to a target.
async fn insert_pentest_session(server: &TestServer, target_id: &str, repo_id: &str) -> String {
    let mongodb_uri = std::env::var("TEST_MONGODB_URI")
        .unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
    let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
    let db = client.database(&server.db_name());

    let result = db
        .collection::<mongodb::bson::Document>("pentest_sessions")
        .insert_one(mongodb::bson::doc! {
            "target_id": target_id,
            "repo_id": repo_id,
            "strategy": "comprehensive",
            "status": "completed",
            "findings_count": 1_i32,
            "exploitable_count": 0_i32,
            "created_at": mongodb::bson::DateTime::now(),
        })
        .await
        .unwrap();

    result.inserted_id.as_object_id().unwrap().to_hex()
}

/// Insert an attack chain node linked to a session.
async fn insert_attack_node(server: &TestServer, session_id: &str) {
    let mongodb_uri = std::env::var("TEST_MONGODB_URI")
        .unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
    let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
    let db = client.database(&server.db_name());

    db.collection::<mongodb::bson::Document>("attack_chain_nodes")
        .insert_one(mongodb::bson::doc! {
            "session_id": session_id,
            "node_id": "node-1",
            "tool_name": "recon",
            "status": "completed",
            "created_at": mongodb::bson::DateTime::now(),
        })
        .await
        .unwrap();
}

/// Insert a DAST finding linked to a target.
async fn insert_dast_finding(server: &TestServer, target_id: &str, session_id: &str) {
    let mongodb_uri = std::env::var("TEST_MONGODB_URI")
        .unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
    let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
    let db = client.database(&server.db_name());

    db.collection::<mongodb::bson::Document>("dast_findings")
        .insert_one(mongodb::bson::doc! {
            "scan_run_id": "run-1",
            "target_id": target_id,
            "vuln_type": "xss",
            "title": "Reflected XSS",
            "description": "XSS in search param",
            "severity": "high",
            "endpoint": "https://example.com/search",
            "method": "GET",
            "exploitable": true,
            "evidence": [],
            "session_id": session_id,
            "created_at": mongodb::bson::DateTime::now(),
        })
        .await
        .unwrap();
}

/// Helper to count documents in a collection
async fn count_docs(server: &TestServer, collection: &str) -> u64 {
    let mongodb_uri = std::env::var("TEST_MONGODB_URI")
        .unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
    let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
    let db = client.database(&server.db_name());
    db.collection::<mongodb::bson::Document>(collection)
        .count_documents(mongodb::bson::doc! {})
        .await
        .unwrap()
}

#[tokio::test]
async fn delete_repo_cascades_to_dast_and_pentest_data() {
    let server = TestServer::start().await;

    // Create a repo
    let resp = server
        .post(
            "/api/v1/targets",
            &json!({
                "name": "cascade-test",
                "target_type": "web_app",
                "artifacts": [{ "kind": "git_repo", "source_ref": "https://github.com/example/cascade-test.git", "branch": "main" }],
            }),
        )
        .await;
    let body: serde_json::Value = resp.json().await.unwrap();
    let repo_id = body["data"]["_id"]["$oid"].as_str().unwrap().to_string();

    // Insert DAST target linked to repo
    let target_id = insert_dast_target(&server, &repo_id, "cascade-target").await;

    // Insert pentest session linked to target
    let session_id = insert_pentest_session(&server, &target_id, &repo_id).await;

    // Insert downstream data
    insert_attack_node(&server, &session_id).await;
    insert_dast_finding(&server, &target_id, &session_id).await;

    // Verify data exists
    assert_eq!(count_docs(&server, "dast_targets").await, 1);
    assert_eq!(count_docs(&server, "pentest_sessions").await, 1);
    assert_eq!(count_docs(&server, "attack_chain_nodes").await, 1);
    assert_eq!(count_docs(&server, "dast_findings").await, 1);

    // Delete the repo
    let resp = server.delete(&format!("/api/v1/targets/{repo_id}")).await;
    assert_eq!(resp.status(), 200);

    // All downstream data should be gone
    assert_eq!(count_docs(&server, "dast_targets").await, 0);
    assert_eq!(count_docs(&server, "pentest_sessions").await, 0);
    assert_eq!(count_docs(&server, "attack_chain_nodes").await, 0);
    assert_eq!(count_docs(&server, "dast_findings").await, 0);

    server.cleanup().await;
}

#[tokio::test]
async fn delete_repo_cascades_sast_findings_and_sbom() {
    let server = TestServer::start().await;

    // Create a repo
    let resp = server
        .post(
            "/api/v1/targets",
            &json!({
                "name": "sast-cascade",
                "target_type": "web_app",
                "artifacts": [{ "kind": "git_repo", "source_ref": "https://github.com/example/sast-cascade.git", "branch": "main" }],
            }),
        )
        .await;
    let body: serde_json::Value = resp.json().await.unwrap();
    let repo_id = body["data"]["_id"]["$oid"].as_str().unwrap().to_string();

    // Insert SAST finding and SBOM entry
    let mongodb_uri = std::env::var("TEST_MONGODB_URI")
        .unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
    let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
    let db = client.database(&server.db_name());
    let now = mongodb::bson::DateTime::now();

    db.collection::<mongodb::bson::Document>("findings")
        .insert_one(mongodb::bson::doc! {
            "repo_id": &repo_id,
            "fingerprint": "fp-test-1",
            "scanner": "semgrep",
            "scan_type": "sast",
            "title": "SQL Injection",
            "description": "desc",
            "severity": "critical",
            "status": "open",
            "created_at": now,
            "updated_at": now,
        })
        .await
        .unwrap();

    db.collection::<mongodb::bson::Document>("sbom_entries")
        .insert_one(mongodb::bson::doc! {
            "repo_id": &repo_id,
            "name": "lodash",
            "version": "4.17.20",
            "package_manager": "npm",
            "known_vulnerabilities": [],
        })
        .await
        .unwrap();

    assert_eq!(count_docs(&server, "findings").await, 1);
    assert_eq!(count_docs(&server, "sbom_entries").await, 1);

    // Delete repo
    server.delete(&format!("/api/v1/targets/{repo_id}")).await;

    // Both should be gone
    assert_eq!(count_docs(&server, "findings").await, 0);
    assert_eq!(count_docs(&server, "sbom_entries").await, 0);

    server.cleanup().await;
}
