//! Command implementations for steme-pharma CLI.

use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
use stemedb_ontology::client::StemeClient;
use stemedb_ontology::pharma::extractors::{FdaLabelExtractor, MedicalExtractor, SourceInput};
use tracing::{info, warn};

use crate::cli::{
    CompareArgs, ExploreArgs, IngestArgs, OutputFormat, QueryArgs, QueryMode, ValidateArgs,
};
use crate::helpers::{format_value, ingest_mock_conflicts};
use crate::output;

/// Execute the ingest command.
pub async fn run_ingest(
    client: &StemeClient,
    args: IngestArgs,
) -> Result<(), Box<dyn std::error::Error>> {
    // Check server health
    if !client.health_check().await {
        eprintln!("Error: StemeDB server not reachable at {}", client.base_url());
        eprintln!("Start the server with: cargo run --bin stemedb-api");
        return Ok(());
    }

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

    println!("=== Pharma Ingest ===");
    println!("Agent ID: {}...", &hex::encode(agent_id)[..16]);
    println!("Server: {}", client.base_url());
    println!();

    let mut total_ingested = 0usize;
    let mut total_errors = 0usize;

    // Extract and ingest FDA claims
    if !args.mock_only {
        let extractor = FdaLabelExtractor::new();
        let drugs: Vec<&str> = args.drugs.split(',').map(str::trim).collect();
        let total_drugs = drugs.len();

        for (drug_idx, drug) in drugs.iter().enumerate() {
            println!("--- [{}/{}] Ingesting {} (FDA) ---", drug_idx + 1, total_drugs, drug);

            match extractor.extract(&SourceInput::DrugName((*drug).to_string())).await {
                Ok(claims) => {
                    let claims_count = claims.len();
                    info!(drug = drug, claims_count = claims_count, "Extracted FDA claims");

                    for (claim_idx, claim) in claims.iter().enumerate() {
                        let assertion = claim.to_assertion(&signing_key, agent_id, &hlc);
                        match client.assert(&assertion).await {
                            Ok(hash) => {
                                total_ingested += 1;
                                println!(
                                    "  [{}/{}] [Regulatory] {} = {} -> {}...",
                                    claim_idx + 1,
                                    claims_count,
                                    claim.predicate,
                                    format_value(&claim.value),
                                    &hash[..16]
                                );
                            }
                            Err(e) => {
                                total_errors += 1;
                                warn!(error = %e, predicate = %claim.predicate, "Failed to ingest");
                                println!(
                                    "  [{}/{}] [ERROR] {} -> {}",
                                    claim_idx + 1,
                                    claims_count,
                                    claim.predicate,
                                    e
                                );
                            }
                        }
                    }
                }
                Err(e) => {
                    warn!(drug = drug, error = %e, "Failed to extract FDA claims");
                    println!("  [ERROR] Failed to extract: {}", e);
                }
            }
            println!();
        }
    }

    // Add mock conflicts if requested
    if args.with_conflicts {
        println!("--- Adding Mock Conflicts ---");
        let (ingested, errors) = ingest_mock_conflicts(client, &signing_key, agent_id, &hlc).await;
        total_ingested += ingested;
        total_errors += errors;
        println!();
    }

    // Summary
    println!("=== Summary ===");
    println!("Total ingested: {}", total_ingested);
    println!("Total errors: {}", total_errors);

    Ok(())
}

/// Execute the query command.
pub async fn run_query(
    client: &StemeClient,
    args: QueryArgs,
    format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
    match args.mode {
        QueryMode::Skeptic => {
            let response = client.skeptic(&args.subject, &args.predicate).await?;
            match format {
                OutputFormat::Table => {
                    println!("{}", output::format_skeptic_table(&response));
                }
                OutputFormat::Json => {
                    println!("{}", serde_json::to_string_pretty(&response)?);
                }
            }
        }
        QueryMode::Layered => {
            let response = client.layered(&args.subject, &args.predicate).await?;
            match format {
                OutputFormat::Table => {
                    println!("{}", output::format_layered_table(&response));
                }
                OutputFormat::Json => {
                    println!("{}", serde_json::to_string_pretty(&response)?);
                }
            }
        }
        QueryMode::Lens(lens) => {
            let response =
                client.query(Some(&args.subject), Some(&args.predicate), Some(lens)).await?;
            match format {
                OutputFormat::Table => {
                    println!(
                        "{}",
                        output::format_query_table(&response, &args.subject, &args.predicate)
                    );
                }
                OutputFormat::Json => {
                    println!("{}", serde_json::to_string_pretty(&response)?);
                }
            }
        }
    }

    Ok(())
}

/// Execute the compare command.
pub async fn run_compare(
    client: &StemeClient,
    args: CompareArgs,
    format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
    // Query both subjects
    let resp1 = client.skeptic(&args.subject1, &args.predicate).await?;
    let resp2 = client.skeptic(&args.subject2, &args.predicate).await?;

    match format {
        OutputFormat::Table => {
            println!(
                "{}",
                output::format_compare_table(&args.subject1, &args.subject2, &resp1, &resp2)
            );
        }
        OutputFormat::Json => {
            let comparison = serde_json::json!({
                "subject1": args.subject1,
                "subject2": args.subject2,
                "predicate": args.predicate,
                "response1": resp1,
                "response2": resp2
            });
            println!("{}", serde_json::to_string_pretty(&comparison)?);
        }
    }

    Ok(())
}

/// Execute the explore command.
pub async fn run_explore(
    client: &StemeClient,
    args: ExploreArgs,
    format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
    let predicates = client.list_predicates(&args.subject).await?;

    match format {
        OutputFormat::Table => {
            println!("{}", output::format_explore_table(&args.subject, &predicates));
        }
        OutputFormat::Json => {
            let result = serde_json::json!({
                "subject": args.subject,
                "predicates": predicates,
                "count": predicates.len()
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
    }

    Ok(())
}

/// Execute the validate command.
pub async fn run_validate(
    args: ValidateArgs,
    format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
    // Known pharma predicates
    let valid_predicates = [
        "hba1c_change_percent",
        "nausea_rate",
        "weight_loss_percent",
        "vomiting_rate",
        "diarrhea_rate",
        "constipation_rate",
        "hypoglycemia_rate",
        "pancreatitis_rate",
        "indication",
        "mechanism_of_action",
        "dosage_form",
        "route_of_administration",
    ];

    // Known subject patterns
    let valid_subject_suffixes = [":Type2Diabetes", ":ChronicWeightManagement", ":Obesity", ""];

    let mut issues: Vec<String> = Vec::new();

    // Check predicate
    if !valid_predicates.contains(&args.predicate.as_str()) {
        issues.push(format!(
            "Unknown predicate '{}'. Known predicates: {}",
            args.predicate,
            valid_predicates.join(", ")
        ));
    }

    // Check subject format
    let has_valid_suffix = valid_subject_suffixes
        .iter()
        .any(|s| args.subject.ends_with(s) || !args.subject.contains(':'));

    if !has_valid_suffix && args.subject.contains(':') {
        let suffix = args.subject.split(':').next_back().unwrap_or("");
        issues.push(format!(
            "Unknown indication suffix ':{}'. Known: {}",
            suffix,
            valid_subject_suffixes.join(", ")
        ));
    }

    let is_valid = issues.is_empty();

    match format {
        OutputFormat::Table => {
            if is_valid {
                println!("Validation PASSED");
                println!("  Subject: {}", args.subject);
                println!("  Predicate: {}", args.predicate);
            } else {
                println!("Validation FAILED");
                for issue in &issues {
                    println!("  - {}", issue);
                }
            }
        }
        OutputFormat::Json => {
            let result = serde_json::json!({
                "subject": args.subject,
                "predicate": args.predicate,
                "valid": is_valid,
                "issues": issues
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
    }

    Ok(())
}
