//! Test setup, DTOs, and helper functions for Consumer Health UAT scenarios.

use ed25519_dalek::{Signer, SigningKey};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use std::env;

// ==================== Test Isolation ====================

/// Generate a unique subject prefix for test isolation.
/// Each test run gets unique subjects to avoid pollution from previous runs.
pub fn unique_prefix() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    let nanos = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or(0);
    format!("test_{}", nanos)
}

// ==================== API Client Setup ====================

/// Base URL for StemeDB API - defaults to localhost:18180
pub fn api_url() -> String {
    env::var("STEMEDB_API_URL").unwrap_or_else(|_| "http://localhost:18180".to_string())
}

/// HTTP client for API calls
pub fn client() -> reqwest::blocking::Client {
    reqwest::blocking::Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .build()
        .expect("failed to create HTTP client")
}

/// Check HTTP response and extract body, returning a descriptive error on failure.
pub fn check_response(
    response: reqwest::blocking::Response,
    context: &str,
) -> Result<String, Box<dyn std::error::Error>> {
    if response.status().is_success() {
        Ok(response.text()?)
    } else {
        let status = response.status();
        let body = response.text().unwrap_or_default();
        Err(format!("{} failed with {}: {}", context, status, body).into())
    }
}

// ==================== DTOs ====================
// NOTE: These DTOs intentionally duplicate structures from stemedb-api.
// Integration tests should not depend on internal crate types to maintain
// a clean API boundary. This ensures tests validate the actual wire format.

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateAssertionRequest {
    pub subject: String,
    pub predicate: String,
    pub object: ObjectValue,
    pub confidence: f32,
    pub source_class: String,
    pub source_hash: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signatures: Option<Vec<SignatureDto>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SignatureDto {
    pub agent_id: String,
    pub signature: String,
    pub timestamp: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<u8>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum ObjectValue {
    Boolean(bool),
    Number(f64),
    Text(String),
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct CreateResponse {
    pub hash: String,
    pub status: String,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct SkepticResponse {
    pub subject: String,
    pub predicate: String,
    pub status: String,
    pub conflict_score: f32,
    pub claims: Vec<ClaimSummary>,
    pub candidates_count: usize,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct ClaimSummary {
    pub value: serde_json::Value,
    pub weight_share: f32,
    pub assertion_count: usize,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct LayeredQueryResponse {
    pub subject: String,
    pub predicate: String,
    pub tiers: Vec<TierResolution>,
    pub overall_winner: Option<serde_json::Value>,
    pub overall_conflict_score: f32,
    pub total_candidates: usize,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct TierResolution {
    pub tier: u8,
    pub source_class: String,
    pub winner: Option<serde_json::Value>,
    pub candidates_count: usize,
    pub conflict_score: f32,
    pub resolution_confidence: f32,
}

// ==================== Signing Helpers ====================

/// Test signing key - generated once per test run
pub fn get_signing_key() -> SigningKey {
    SigningKey::generate(&mut OsRng)
}

/// Sign a message using v1 (legacy) format: "{subject}:{predicate}"
pub fn sign_v1(signing_key: &SigningKey, subject: &str, predicate: &str) -> SignatureDto {
    let message = format!("{}:{}", subject, predicate);
    let signature = signing_key.sign(message.as_bytes());
    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);

    SignatureDto {
        agent_id: hex::encode(signing_key.verifying_key().as_bytes()),
        signature: hex::encode(signature.to_bytes()),
        timestamp,
        version: Some(1),
    }
}

// ==================== API Helper Functions ====================

/// POST /v1/assert with automatic signature generation
pub fn create_assertion(
    signing_key: &SigningKey,
    subject: &str,
    predicate: &str,
    object: ObjectValue,
    confidence: f32,
    source_class: &str,
    source_hash: &str,
) -> Result<String, Box<dyn std::error::Error>> {
    let url = format!("{}/v1/assert", api_url());

    let signature = sign_v1(signing_key, subject, predicate);

    let request = CreateAssertionRequest {
        subject: subject.to_string(),
        predicate: predicate.to_string(),
        object,
        confidence,
        source_class: source_class.to_string(),
        source_hash: source_hash.to_string(),
        signatures: Some(vec![signature]),
    };

    let response = client().post(&url).json(&request).send()?;
    let body = check_response(response, "POST /v1/assert")?;
    let create_response: CreateResponse = serde_json::from_str(&body)?;
    Ok(create_response.hash)
}

/// GET /v1/skeptic?subject=...&predicate=...
pub fn query_skeptic(
    subject: &str,
    predicate: &str,
) -> Result<SkepticResponse, Box<dyn std::error::Error>> {
    let url = format!(
        "{}/v1/skeptic?subject={}&predicate={}",
        api_url(),
        urlencoding::encode(subject),
        urlencoding::encode(predicate)
    );

    let response = client().get(&url).send()?;
    let body = check_response(response, "GET /v1/skeptic")?;
    Ok(serde_json::from_str(&body)?)
}

/// GET /v1/layered?subject=...&predicate=...
pub fn query_layered(
    subject: &str,
    predicate: &str,
) -> Result<LayeredQueryResponse, Box<dyn std::error::Error>> {
    let url = format!(
        "{}/v1/layered?subject={}&predicate={}",
        api_url(),
        urlencoding::encode(subject),
        urlencoding::encode(predicate)
    );

    let response = client().get(&url).send()?;
    let body = check_response(response, "GET /v1/layered")?;
    Ok(serde_json::from_str(&body)?)
}
