// SPDX-License-Identifier: Apache-2.0

use malwaredb_client::MdbClient;

use std::path::PathBuf;
use std::process::ExitCode;

use anyhow::{anyhow, Result};
use clap::Parser;
use uuid::Uuid;

#[derive(Debug, Clone, Eq, PartialEq, Parser)]
pub struct SearchRequest {
    /// Partial hash to search for
    #[clap(long)]
    pub hash: Option<String>,

    /// Hash algorith to search for, defaults to SHA-256
    #[clap(long, default_value = "sha256")]
    pub hash_type: String,

    /// Partial file name to search for
    #[clap(long)]
    pub file_name: Option<String>,

    /// Search for samples with any of the specified labels
    #[clap(long)]
    pub labels: Option<Vec<String>>,

    /// Search by file type
    #[clap(long = "type")]
    pub file_type: Option<String>,

    /// Search based on `libmagic` output, also known as the file command.
    #[clap(long = "magic")]
    pub magic: Option<String>,

    /// Get the response type in the form of a specific hash
    #[clap(long, default_value = "sha256")]
    pub response_type: String,

    /// Maximum amount of search results to show
    #[clap(long, default_value_t = 100)]
    pub limit: u32,
}

impl SearchRequest {
    pub async fn exec(&self, config: &MdbClient) -> Result<ExitCode> {
        let hash_search = if let Some(hash) = &self.hash {
            let hash_type = self
                .hash_type
                .as_str()
                .try_into()
                .map_err(|e: String| anyhow!(e))?;
            Some((hash_type, hash.clone()))
        } else {
            None
        };

        let response = self
            .response_type
            .as_str()
            .try_into()
            .map_err(|e: String| anyhow!(e))?;
        let response = config
            .partial_search_labels_type(
                hash_search,
                self.file_name.clone(),
                response,
                self.labels.clone(),
                self.file_type.clone(),
                self.magic.clone(),
                self.limit,
            )
            .await?;

        if response.hashes.is_empty() {
            println!("No results!");
        } else {
            for result in response.hashes {
                println!("{result}");
            }
        }

        Ok(ExitCode::SUCCESS)
    }
}

/// Search for samples using Yara
#[derive(Debug, Clone, Eq, PartialEq, Parser)]
pub struct YaraSearch {
    /// Path to the Yara rule file for searching
    pub yara_rule: PathBuf,
}

impl YaraSearch {
    pub async fn exec(&self, config: &MdbClient) -> Result<ExitCode> {
        let contents = std::fs::read_to_string(&self.yara_rule)?;
        let response = config.yara_search(&contents).await?;
        println!("Yara rule submitted as job: {}", response.uuid);
        Ok(ExitCode::SUCCESS)
    }
}

/// Get the Yara search result identified by UUID
#[derive(Debug, Clone, Eq, PartialEq, Parser)]
pub struct YaraResult {
    pub uuid: Uuid,
}

impl YaraResult {
    pub async fn exec(&self, config: &MdbClient) -> Result<ExitCode> {
        let result = config.yara_result(self.uuid).await?;
        for (rule, hashes) in result.results {
            println!("{rule}");
            for hash in hashes {
                println!("\t{hash}");
            }
        }
        Ok(ExitCode::SUCCESS)
    }
}
