mod catalog;
mod cli;
mod errors;
mod output;
mod vcf;

use std::io::Read;

use clap::Parser;

use cli::{Cli, Commands};
use errors::LabGeneticsError;

fn main() {
    let cli = Cli::parse();

    if let Some(cmd) = &cli.command {
        match cmd {
            Commands::AgentInfo => {
                print_agent_info();
                return;
            }
            Commands::Variants { gene, category } => {
                print_variants(gene.as_deref(), category.as_deref(), cli.json);
                return;
            }
        }
    }

    let use_json = cli.json || !output::is_tty();

    match run(&cli) {
        Ok(result) => {
            if use_json {
                println!("{}", output::json::render(&result));
            } else {
                output::table::render(&result);
            }
        }
        Err(e) => {
            if use_json {
                eprintln!("{}", output::json::render_error(&e.to_string()));
            } else {
                eprintln!("error: {}", e);
            }
            e.exit();
        }
    }
}

fn run(cli: &Cli) -> Result<AnalysisResult, LabGeneticsError> {
    // Read input: VCF file, JSON gene list, or stdin
    let input = if cli.stdin {
        let mut buf = String::new();
        std::io::stdin()
            .read_to_string(&mut buf)
            .map_err(|e| LabGeneticsError::IoError(e.to_string()))?;
        buf
    } else if let Some(path) = &cli.input {
        std::fs::read_to_string(path)
            .map_err(|e| LabGeneticsError::IoError(format!("{}: {}", path.display(), e)))?
    } else if let Some(genes) = &cli.genes {
        genes.clone()
    } else {
        return Err(LabGeneticsError::NoInput);
    };

    // Detect input format and parse
    let variants = if input.starts_with("##fileformat=VCF") || input.starts_with("#CHROM") {
        vcf::parse_vcf(&input)?
    } else if input.trim_start().starts_with('{') || input.trim_start().starts_with('[') {
        // JSON input: list of variants or gene names
        parse_json_input(&input)?
    } else {
        // Free-form text: gene names, rs IDs, or variant descriptions
        parse_text_input(&input)?
    };

    let catalog = catalog::load_catalog()?;
    let results = catalog::analyze_variants(&catalog, &variants)?;

    Ok(AnalysisResult {
        variants: results,
        total_input: variants.len(),
    })
}

fn parse_json_input(input: &str) -> Result<Vec<vcf::Variant>, LabGeneticsError> {
    // Try parsing as array of variant objects
    if let Ok(variants) = serde_json::from_str::<Vec<vcf::Variant>>(input) {
        return Ok(variants);
    }
    // Try parsing as array of gene names / rsIDs
    if let Ok(ids) = serde_json::from_str::<Vec<String>>(input) {
        return Ok(ids
            .into_iter()
            .map(|id| vcf::Variant::from_id(&id))
            .collect());
    }
    Err(LabGeneticsError::ParseError(
        "Cannot parse JSON input as variants or gene list".into(),
    ))
}

fn parse_text_input(input: &str) -> Result<Vec<vcf::Variant>, LabGeneticsError> {
    let variants: Vec<vcf::Variant> = input
        .lines()
        .filter(|l| !l.trim().is_empty() && !l.starts_with('#'))
        .filter_map(|line| {
            let trimmed = line.trim();
            // Accept: rs12345, MTHFR C677T, BRCA1:c.5266dupC, etc.
            Some(vcf::Variant::from_id(trimmed))
        })
        .collect();

    if variants.is_empty() {
        return Err(LabGeneticsError::ParseError("No variants found in input".into()));
    }
    Ok(variants)
}

fn print_agent_info() {
    let info = serde_json::json!({
        "name": "labgenetics",
        "version": env!("CARGO_PKG_VERSION"),
        "description": "Genetic variant analysis — pathogenicity, pharmacogenomics, and polygenic risk",
        "capabilities": [
            "Classify genetic variants by pathogenicity (ClinVar-based)",
            "Pharmacogenomics (PGx) star-allele interpretation",
            "Polygenic risk score estimation from known variants",
            "Gene-gene interaction detection",
            "Actionable variant flagging for clinical protocols"
        ],
        "input_formats": ["VCF", "JSON", "text (gene names / rsIDs)"],
        "output_formats": ["JSON (stdout)", "human-readable table (TTY)"],
        "categories": [
            "cardiovascular", "metabolic", "neurological", "cancer",
            "pharmacogenomics", "longevity", "immune", "musculoskeletal"
        ],
        "usage": {
            "vcf": "labgenetics patient.vcf --json",
            "genes": "labgenetics --genes 'MTHFR C677T, APOE e4/e4, BRCA1' --json",
            "stdin": "cat variants.json | labgenetics --stdin --json",
            "pipe": "labgenetics patient.vcf | labassess --stdin"
        },
        "exit_codes": {
            "0": "success",
            "1": "error",
            "2": "config error",
            "3": "no variants found"
        }
    });
    println!("{}", serde_json::to_string_pretty(&info).unwrap());
}

fn print_variants(gene: Option<&str>, category: Option<&str>, json: bool) {
    let catalog = match catalog::load_catalog() {
        Ok(c) => c,
        Err(e) => {
            eprintln!("error: {}", e);
            std::process::exit(2);
        }
    };

    let entries: Vec<&catalog::VariantEntry> = catalog
        .variants
        .iter()
        .filter(|v| {
            gene.map_or(true, |g| {
                v.gene.eq_ignore_ascii_case(g) || v.id.eq_ignore_ascii_case(g)
            })
        })
        .filter(|v| category.map_or(true, |c| v.category.eq_ignore_ascii_case(c)))
        .collect();

    if json {
        println!("{}", serde_json::to_string_pretty(&entries).unwrap());
    } else {
        use comfy_table::{presets::UTF8_FULL, Table};
        let mut table = Table::new();
        table.load_preset(UTF8_FULL);
        table.set_header(vec!["Gene", "Variant", "Category", "Pathogenicity", "Actionable"]);
        for v in &entries {
            table.add_row(vec![
                v.gene.clone(),
                v.variant_name.clone(),
                v.category.clone(),
                v.pathogenicity.clone(),
                if v.actionable { "Yes".to_string() } else { "No".to_string() },
            ]);
        }
        println!("{table}");
        println!("\n{} variants in catalog", entries.len());
    }
}

/// Top-level analysis result
pub struct AnalysisResult {
    pub variants: Vec<catalog::VariantResult>,
    pub total_input: usize,
}
