// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Pierre Gronau, ndaal in Cologne

//! CLI for CSAF CRUD operations: import, export, validate.

use std::path::PathBuf;

use anyhow::{Context, bail};
use clap::{Parser, Subcommand};

use csaf_core::config::AppConfig;
use csaf_core::storage::CsafStorage;

/// CSAF CRUD CLI tool.
#[derive(Parser)]
#[command(name = "csaf-cli", version, about = "CSAF CRUD command-line interface")]
struct Cli {
    /// Path to configuration file.
    #[arg(short, long, default_value = "config/default.toml")]
    config: PathBuf,

    /// Subcommand to execute.
    #[command(subcommand)]
    command: Commands,
}

/// Available subcommands.
#[derive(Subcommand)]
enum Commands {
    /// Import CSAF documents from a directory.
    Import {
        /// Directory to import from (overrides config).
        #[arg(short, long)]
        directory: Option<PathBuf>,
    },

    /// Export all CSAF documents to a directory.
    Export {
        /// Directory to export to (overrides config).
        #[arg(short, long)]
        directory: Option<PathBuf>,
    },

    /// Validate a CSAF document file.
    Validate {
        /// Path to the CSAF JSON file.
        file: PathBuf,
    },

    /// Show storage statistics.
    Stats,
}

fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let cli = Cli::parse();

    let config = AppConfig::load(&cli.config)?;

    match cli.command {
        Commands::Import { directory } => cmd_import(&config, directory),
        Commands::Export { directory } => cmd_export(&config, directory),
        Commands::Validate { file } => cmd_validate(&file),
        Commands::Stats => cmd_stats(&config),
    }
}

/// Import CSAF documents from a directory.
fn cmd_import(config: &AppConfig, directory: Option<PathBuf>) -> anyhow::Result<()> {
    let storage = open_storage(config)?;
    let settings = storage.get_settings().unwrap_or_default();

    let import_dir = directory.unwrap_or_else(|| PathBuf::from(&settings.import_directory));

    let result =
        csaf_core::import::import_directory(&import_dir, &storage).context("Import failed")?;

    println!(
        "Import complete: {} imported, {} skipped",
        result.imported, result.skipped
    );
    for err in &result.errors {
        eprintln!("  Error: {err}");
    }

    Ok(())
}

/// Export all CSAF documents.
fn cmd_export(config: &AppConfig, directory: Option<PathBuf>) -> anyhow::Result<()> {
    let storage = open_storage(config)?;
    let mut settings = storage.get_settings().unwrap_or_default();

    if let Some(dir) = directory {
        settings.export_directory = dir.to_string_lossy().to_string();
    }

    let meta_list = storage
        .list_meta(10000, 0)
        .context("Failed to list documents")?;

    let mut exported = 0;
    let mut errors = 0;

    for meta in &meta_list {
        match storage.get_document(&meta.tracking_id) {
            Ok(Some(doc)) => match csaf_core::export::export_document(&doc, &settings) {
                Ok(_) => exported += 1,
                Err(e) => {
                    eprintln!("Export error for {}: {e}", meta.tracking_id);
                    errors += 1;
                },
            },
            Ok(None) => {
                eprintln!("Document {} not found in storage", meta.tracking_id);
                errors += 1;
            },
            Err(e) => {
                eprintln!("Read error for {}: {e}", meta.tracking_id);
                errors += 1;
            },
        }
    }

    // Export provider metadata if available.
    if let Ok(Some(provider_meta)) = storage.get_provider_metadata() {
        let export_dir = std::path::Path::new(&settings.export_directory);
        if let Err(e) = csaf_core::export::export_provider_metadata(&provider_meta, export_dir) {
            eprintln!("Failed to export provider metadata: {e}");
        }
    }

    println!("Export complete: {exported} exported, {errors} errors");
    Ok(())
}

/// Validate a single CSAF file.
fn cmd_validate(file: &std::path::Path) -> anyhow::Result<()> {
    let content = std::fs::read_to_string(file)
        .with_context(|| format!("Failed to read file: {}", file.display()))?;

    let doc: csaf_models::csaf_document::CsafDocument =
        serde_json::from_str(&content).context("Failed to parse CSAF JSON")?;

    let errors = csaf_core::validation::validate(&doc);

    if errors.is_empty() {
        println!("Valid: {} ({})", doc.tracking_id(), doc.category());
    } else {
        println!(
            "Validation findings for {} ({}):",
            doc.tracking_id(),
            doc.category()
        );
        let mut has_errors = false;
        for err in &errors {
            let severity = match err.severity {
                csaf_core::validation::Severity::Error => {
                    has_errors = true;
                    "ERROR"
                },
                csaf_core::validation::Severity::Warning => "WARN",
            };
            println!("  [{severity}] {}: {}", err.path, err.message);
        }

        if has_errors {
            bail!("Validation found hard errors in {}", doc.tracking_id());
        }
    }

    Ok(())
}

/// Show storage statistics.
fn cmd_stats(config: &AppConfig) -> anyhow::Result<()> {
    let storage = open_storage(config)?;

    let count = storage.count_documents().unwrap_or(0);
    let settings = storage.get_settings().unwrap_or_default();

    println!("CSAF Storage Statistics");
    println!("  Documents: {count}");
    println!("  CSAF Mode: {}", settings.csaf_mode);
    println!("  Import Dir: {}", settings.import_directory);
    println!("  Export Dir: {}", settings.export_directory);
    println!(
        "  Sidecar SHA-256: {}",
        if settings.sidecar_sha256 { "yes" } else { "no" }
    );
    println!(
        "  Sidecar SHA3-512: {}",
        if settings.sidecar_sha3_512 {
            "yes"
        } else {
            "no"
        }
    );

    Ok(())
}

/// Open the redb storage from config.
fn open_storage(config: &AppConfig) -> anyhow::Result<CsafStorage> {
    CsafStorage::open(&config.redb_path()).context("Failed to open storage")
}
