use anyhow::Result;
use camino::Utf8PathBuf;
use chrono::Utc;

use crate::check::validate;
use crate::manifest::RunnerManifest;
use crate::model::{ReceiptMode, ReceiptSummary, RunnerImageReceipt, SchemaVersion, ToolInfo};
use crate::probe::ProbeRequest;
use crate::probe_docker::probe_docker;
use crate::probe_host::probe_host;
use crate::setup_node::{SetupNodeOptions, classify};

pub const TOOL_NAME: &str = "gha-runner-image-proof";
pub const TOOL_VERSION: &str = env!("CARGO_PKG_VERSION");

pub struct CheckOptions {
    pub manifest_path: Utf8PathBuf,
}

pub struct ProbeHostOptions {
    pub manifest_path: Utf8PathBuf,
    pub tools: Vec<String>,
    pub packages: Vec<String>,
    pub capabilities: Vec<String>,
}

pub struct ProbeDockerOptions {
    pub manifest_path: Utf8PathBuf,
    pub image: String,
    pub tools: Vec<String>,
    pub packages: Vec<String>,
    pub capabilities: Vec<String>,
}

pub struct SetupNodeCmdOptions {
    pub manifest_path: Utf8PathBuf,
    pub version_spec: String,
    pub arch: String,
    pub check_latest: bool,
}

fn header(mode: ReceiptMode) -> RunnerImageReceipt {
    RunnerImageReceipt {
        schema_version: SchemaVersion::from(crate::model::SCHEMA_VERSION),
        tool: ToolInfo {
            name: TOOL_NAME.to_string(),
            version: TOOL_VERSION.to_string(),
        },
        checked_at: Utc::now(),
        mode,
        summary: ReceiptSummary::default(),
        check: None,
        probe_host: None,
        probe_docker: None,
        setup_node: None,
        checks: Vec::new(),
    }
}

pub fn run_check(options: &CheckOptions) -> Result<RunnerImageReceipt> {
    let manifest = RunnerManifest::load(&options.manifest_path)?;
    let report = validate(&manifest, options.manifest_path.as_str());
    let mut receipt = header(ReceiptMode::Check);
    receipt.summary = report.summary.clone();
    receipt.check = Some(report);
    Ok(receipt)
}

pub fn run_probe_host(options: &ProbeHostOptions) -> Result<RunnerImageReceipt> {
    let manifest = RunnerManifest::load(&options.manifest_path)?;
    let request = ProbeRequest {
        tools: &options.tools,
        packages: &options.packages,
        capabilities: &options.capabilities,
    };
    let mut report = probe_host(&manifest, &request);
    report.manifest_path = options.manifest_path.to_string();
    let mut receipt = header(ReceiptMode::ProbeHost);
    receipt.summary = report.summary.clone();
    receipt.probe_host = Some(report);
    Ok(receipt)
}

pub fn run_probe_docker(options: &ProbeDockerOptions) -> Result<RunnerImageReceipt> {
    let manifest = RunnerManifest::load(&options.manifest_path)?;
    let request = ProbeRequest {
        tools: &options.tools,
        packages: &options.packages,
        capabilities: &options.capabilities,
    };
    let mut report = probe_docker(&manifest, &options.image, &request);
    report.manifest_path = options.manifest_path.to_string();
    let mut receipt = header(ReceiptMode::ProbeDocker);
    receipt.summary = report.summary.clone();
    receipt.probe_docker = Some(report);
    Ok(receipt)
}

pub fn run_setup_node(options: &SetupNodeCmdOptions) -> Result<RunnerImageReceipt> {
    let manifest = RunnerManifest::load(&options.manifest_path)?;
    let mut report = classify(
        &manifest,
        &SetupNodeOptions {
            version_spec: options.version_spec.clone(),
            arch: options.arch.clone(),
            check_latest: options.check_latest,
        },
    );
    report.manifest_path = options.manifest_path.to_string();
    report.image_name = manifest.image.name.clone();
    let mut receipt = header(ReceiptMode::SetupNode);
    receipt.summary = report.summary.clone();
    receipt.setup_node = Some(report);
    Ok(receipt)
}
