//! Tests for the FDA label extractor.

use stemedb_core::types::{ObjectValue, SourceClass};

use super::{FdaLabelExtractor, MedicalClaim, MedicalExtractor, RetryConfig, SourceInput};

#[test]
fn test_normalize_drug_name() {
    let extractor = FdaLabelExtractor::new();

    assert_eq!(extractor.normalize_drug_name("ozempic"), "Semaglutide");
    assert_eq!(extractor.normalize_drug_name("WEGOVY"), "Semaglutide");
    assert_eq!(extractor.normalize_drug_name("Mounjaro"), "Tirzepatide");
    assert_eq!(extractor.normalize_drug_name("semaglutide"), "Semaglutide");
    assert_eq!(extractor.normalize_drug_name("metformin"), "Metformin");
}

#[test]
fn test_build_url() {
    let extractor = FdaLabelExtractor::new();
    let url = extractor.build_url("semaglutide");

    assert!(url.contains("api.fda.gov"));
    assert!(url.contains("semaglutide"));
}

#[test]
fn test_build_url_with_api_key() {
    let extractor = FdaLabelExtractor::new().with_api_key("test_key");
    let url = extractor.build_url("semaglutide");

    assert!(url.contains("api_key=test_key"));
}

#[test]
fn test_extract_adverse_reaction_rates() {
    let extractor = FdaLabelExtractor::new();
    let text =
        "Nausea occurred in 44% of patients. Vomiting was reported in 24% of patients. Diarrhea: 30%";

    let claims = extractor.extract_adverse_reaction_rates("Semaglutide", text);

    assert!(claims.len() >= 2);

    // Find nausea claim
    let nausea_claim = claims.iter().find(|c| c.predicate == "nausea_rate");
    assert!(nausea_claim.is_some());
    if let Some(claim) = nausea_claim {
        if let ObjectValue::Number(rate) = claim.value {
            assert!((rate - 0.44).abs() < 0.01);
        } else {
            panic!("Expected Number value");
        }
    }
}

#[test]
fn test_extract_indications() {
    let extractor = FdaLabelExtractor::new();
    let text = "OZEMPIC is indicated as an adjunct to diet and exercise to improve glycemic control in adults with type 2 diabetes mellitus and to reduce the risk of major adverse cardiovascular events.";

    let claims = extractor.extract_indications("Semaglutide", text);

    assert!(claims.len() >= 2);

    let indication_values: Vec<_> = claims
        .iter()
        .filter_map(|c| match &c.value {
            ObjectValue::Text(t) => Some(t.as_str()),
            _ => None,
        })
        .collect();

    assert!(indication_values.contains(&"Type2Diabetes"));
    assert!(indication_values.contains(&"MACE_Reduction"));
}

#[test]
fn test_can_handle() {
    let extractor = FdaLabelExtractor::new();

    assert!(extractor.can_handle(&SourceInput::DrugName("semaglutide".to_string())));
    assert!(extractor.can_handle(&SourceInput::ApplicationNumber("NDA123456".to_string())));
    assert!(!extractor.can_handle(&SourceInput::Url("https://example.com".to_string())));
}

#[test]
fn test_retry_config_builder() {
    let config = RetryConfig {
        max_retries: 5,
        initial_delay_ms: 500,
        max_delay_ms: 10000,
        timeout_ms: 15000,
    };

    let extractor = FdaLabelExtractor::new().with_retry_config(config);

    assert_eq!(extractor.retry_config().max_retries, 5);
    assert_eq!(extractor.retry_config().initial_delay_ms, 500);
}

#[test]
fn test_to_assertion_signing() {
    use ed25519_dalek::SigningKey;
    use rand::rngs::OsRng;

    let claim = MedicalClaim::new("Semaglutide", "has_boxed_warning", ObjectValue::Boolean(true))
        .with_confidence(0.95)
        .with_source_url("https://api.fda.gov/drug/label.json?search=semaglutide")
        .with_source_section("Boxed Warning")
        .with_quote("WARNING: RISK OF THYROID C-CELL TUMORS")
        .with_source_class(SourceClass::Regulatory);

    let signing_key = SigningKey::generate(&mut OsRng);
    let agent_id = signing_key.verifying_key().to_bytes();
    let hlc = uhlc::HLCBuilder::new().build();

    let assertion = claim.to_assertion(&signing_key, agent_id, &hlc);

    // Verify assertion fields
    assert_eq!(assertion.subject, "Semaglutide");
    assert_eq!(assertion.predicate, "has_boxed_warning");
    assert_eq!(assertion.object, ObjectValue::Boolean(true));
    assert_eq!(assertion.confidence, 0.95);
    assert_eq!(assertion.source_class, SourceClass::Regulatory);
    // Assertions from regulatory sources start as Approved (production-ready)
    assert_eq!(assertion.lifecycle, stemedb_core::types::LifecycleStage::Approved);

    // Verify signature
    assert_eq!(assertion.signatures.len(), 1);
    assert_eq!(assertion.signatures[0].agent_id, agent_id);
    assert_eq!(assertion.signatures[0].version, 2); // Enterprise signing

    // Verify source hash is computed
    assert_ne!(assertion.source_hash, [0u8; 32]);

    // Verify HLC timestamp is set
    assert!(assertion.hlc_timestamp.time_ntp64 > 0);
}

#[test]
fn test_to_assertion_signature_verifiable() {
    use ed25519_dalek::{Signature, SigningKey, Verifier};
    use rand::rngs::OsRng;
    use stemedb_core::signing::compute_content_hash_v2;

    let claim = MedicalClaim::new("Semaglutide", "has_boxed_warning", ObjectValue::Boolean(true))
        .with_confidence(0.95)
        .with_source_url("https://api.fda.gov/drug/label.json?search=semaglutide")
        .with_source_section("Boxed Warning")
        .with_quote("WARNING: RISK OF THYROID C-CELL TUMORS")
        .with_source_class(SourceClass::Regulatory);

    let signing_key = SigningKey::generate(&mut OsRng);
    let verifying_key = signing_key.verifying_key();
    let agent_id = verifying_key.to_bytes();
    let hlc = uhlc::HLCBuilder::new().build();

    let assertion = claim.to_assertion(&signing_key, agent_id, &hlc);

    // Recompute content hash using the shared utility
    let content_hash = compute_content_hash_v2(&assertion);

    // Extract and verify the signature
    let signature_bytes = assertion.signatures[0].signature;
    let signature = Signature::from_bytes(&signature_bytes);

    // Signature should verify successfully
    assert!(
        verifying_key.verify(&content_hash, &signature).is_ok(),
        "Signature verification failed - content hash mismatch"
    );
}

#[test]
fn test_non_regulatory_source_starts_proposed() {
    use ed25519_dalek::SigningKey;
    use rand::rngs::OsRng;

    // Create a claim from a non-regulatory source (e.g., Clinical)
    let claim = MedicalClaim::new("Semaglutide", "weight_loss_percent", ObjectValue::Number(15.0))
        .with_confidence(0.85)
        .with_source_class(SourceClass::Clinical);

    let signing_key = SigningKey::generate(&mut OsRng);
    let agent_id = signing_key.verifying_key().to_bytes();
    let hlc = uhlc::HLCBuilder::new().build();

    let assertion = claim.to_assertion(&signing_key, agent_id, &hlc);

    // Non-regulatory sources should start as Proposed, not Approved
    assert_eq!(assertion.lifecycle, stemedb_core::types::LifecycleStage::Proposed);
}

#[test]
fn test_to_assertion_with_explicit_lifecycle() {
    use ed25519_dalek::SigningKey;
    use rand::rngs::OsRng;
    use stemedb_core::types::LifecycleStage;

    let claim = MedicalClaim::new("Semaglutide", "has_boxed_warning", ObjectValue::Boolean(true))
        .with_source_class(SourceClass::Regulatory);

    let signing_key = SigningKey::generate(&mut OsRng);
    let agent_id = signing_key.verifying_key().to_bytes();
    let hlc = uhlc::HLCBuilder::new().build();

    // Use explicit lifecycle override
    let assertion = claim.to_assertion_with_lifecycle(
        &signing_key,
        agent_id,
        &hlc,
        LifecycleStage::UnderReview,
    );

    assert_eq!(assertion.lifecycle, LifecycleStage::UnderReview);
}

#[test]
fn test_to_assertion_with_metadata() {
    use ed25519_dalek::SigningKey;
    use rand::rngs::OsRng;

    let metadata = serde_json::json!({
        "label_version": "2024.1",
        "effective_date": "2024-01-15"
    });

    let claim = MedicalClaim::new("Tirzepatide", "max_approved_dose_mg", ObjectValue::Number(15.0))
        .with_confidence(0.98)
        .with_source_class(SourceClass::Regulatory)
        .with_metadata(metadata);

    let signing_key = SigningKey::generate(&mut OsRng);
    let agent_id = signing_key.verifying_key().to_bytes();
    let hlc = uhlc::HLCBuilder::new().build();

    let assertion = claim.to_assertion(&signing_key, agent_id, &hlc);

    // Verify metadata is serialized
    assert!(assertion.source_metadata.is_some());
    let meta_bytes = assertion.source_metadata.as_ref().expect("metadata should exist");
    let parsed: serde_json::Value = serde_json::from_slice(meta_bytes).expect("should parse");
    assert_eq!(parsed["label_version"], "2024.1");
}

// ============================================================================
// Integration Tests (hit live FDA API)
// Run with: cargo test -p stemedb-ontology -- --ignored
// ============================================================================

/// Integration test - fetches real semaglutide label from FDA API.
#[tokio::test]
#[ignore]
async fn test_fetch_real_semaglutide_label() {
    let extractor = FdaLabelExtractor::new();
    let claims = extractor.extract(&SourceInput::DrugName("semaglutide".to_string())).await;

    assert!(claims.is_ok(), "Failed to fetch: {:?}", claims.err());
    let claims = claims.expect("claims should exist");
    assert!(!claims.is_empty(), "No claims extracted");

    // Verify expected claims exist
    let has_boxed_warning = claims.iter().any(|c| c.predicate == "has_boxed_warning");
    assert!(has_boxed_warning, "Missing has_boxed_warning claim");

    // Semaglutide should have a boxed warning for thyroid tumors
    let boxed_warning_claim = claims
        .iter()
        .find(|c| c.predicate == "has_boxed_warning")
        .expect("should have boxed warning claim");
    assert_eq!(boxed_warning_claim.value, ObjectValue::Boolean(true));

    // Should have indication claims
    let has_indications = claims.iter().any(|c| c.predicate == "approved_indications");
    assert!(has_indications, "Missing approved_indications claim");

    // All claims should be for Semaglutide
    for claim in &claims {
        assert_eq!(claim.subject, "Semaglutide", "Subject mismatch: {}", claim.subject);
        assert_eq!(claim.source_class, SourceClass::Regulatory);
    }
}

/// Integration test - fetches multiple GLP-1 drugs.
#[tokio::test]
#[ignore]
async fn test_fetch_multiple_glp1_drugs() {
    let extractor = FdaLabelExtractor::new();

    for drug in ["semaglutide", "tirzepatide", "liraglutide"] {
        let result = extractor.extract(&SourceInput::DrugName(drug.to_string())).await;
        assert!(result.is_ok(), "Failed to fetch {}: {:?}", drug, result.err());

        let claims = result.expect("claims should exist");
        assert!(!claims.is_empty(), "No claims for {}", drug);

        // Each drug should have at least boxed warning and indication claims
        let predicates: Vec<_> = claims.iter().map(|c| c.predicate.as_str()).collect();
        assert!(predicates.contains(&"has_boxed_warning"), "{} missing boxed warning", drug);
    }
}

/// Integration test - converts extracted claims to assertions.
#[tokio::test]
#[ignore]
async fn test_extract_and_convert_to_assertions() {
    use ed25519_dalek::SigningKey;
    use rand::rngs::OsRng;

    let extractor = FdaLabelExtractor::new();
    let claims = extractor
        .extract(&SourceInput::DrugName("semaglutide".to_string()))
        .await
        .expect("should fetch claims");

    let signing_key = SigningKey::generate(&mut OsRng);
    let agent_id = signing_key.verifying_key().to_bytes();
    let hlc = uhlc::HLCBuilder::new().build();

    // Convert all claims to assertions
    let assertions: Vec<_> =
        claims.iter().map(|claim| claim.to_assertion(&signing_key, agent_id, &hlc)).collect();

    assert!(!assertions.is_empty());

    // Verify all assertions have valid signatures
    for assertion in &assertions {
        assert_eq!(assertion.signatures.len(), 1);
        assert_eq!(assertion.signatures[0].agent_id, agent_id);
        assert_eq!(assertion.signatures[0].version, 2);
        assert_ne!(assertion.signatures[0].signature, [0u8; 64]);
    }

    // Verify HLC timestamps are monotonically non-decreasing
    for window in assertions.windows(2) {
        assert!(
            window[0].hlc_timestamp <= window[1].hlc_timestamp,
            "HLC timestamps should be monotonic"
        );
    }
}

/// Integration test - verifies rate limit handling with retry.
#[tokio::test]
#[ignore]
async fn test_retry_behavior() {
    // Use aggressive retry config to test quickly
    let config =
        RetryConfig { max_retries: 2, initial_delay_ms: 100, max_delay_ms: 500, timeout_ms: 10000 };

    let extractor = FdaLabelExtractor::new().with_retry_config(config);

    // This should succeed even if initial request is slow
    let result = extractor.extract(&SourceInput::DrugName("metformin".to_string())).await;

    assert!(result.is_ok(), "Failed with retry: {:?}", result.err());
}
