use greenbone_scanner_framework::models::PreferenceValue;

// SPDX-FileCopyrightText: 2025 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception

use crate::nasl::prelude::*;
use crate::scanner::preferences::preference::PREFERENCES;
use crate::storage::items::kb::KbKey;
use crate::storage::items::kb::Ssl;
use base64::Engine as _;
use std::io::Write;
use tempfile::NamedTempFile;

fn script_get_preference_shared(
    register: &Register,
    config: &ScanCtx,
    name: Option<String>,
    id: Option<usize>,
) -> Option<NaslValue> {
    // A parameter ID is given. Search for the param in the scan config, otherwise try the default value from the NVT metadata
    if let Some(id) = id {
        match register.script_param(id) {
            Some(v) => return Some(v),
            None => {
                if let Some(pref) = config.nvt().clone().and_then(|nvt| {
                    nvt.preferences
                        .into_iter()
                        .find(|p| p.id == Some(id as i32))
                }) {
                    return Some(pref.default().to_string().into());
                }
            }
        }
    }

    // A parameter name is given. Search for the param in NVT metadata to get the ID.
    // Then, search with the ID in the scan config, otherwise return the default value from the NVT metadata.
    if let Some(pref_name) = name
        && let Some(pref) = config
            .nvt()
            .clone()
            .and_then(|nvt| nvt.preferences.into_iter().find(|p| p.name == pref_name))
    {
        return register
            .script_param(pref.id().unwrap() as usize)
            .or_else(|| Some(NaslValue::String(pref.default().to_string())));
    }
    None
}

fn script_get_preference_file_content_shared(
    register: &Register,
    config: &ScanCtx,
    name: Option<String>,
    id: Option<usize>,
) -> Option<Vec<u8>> {
    let content = script_get_preference_shared(register, config, name, id)?;
    let content = content.as_string().unwrap();
    base64::engine::general_purpose::STANDARD
        .decode(content)
        .ok()
}

/// Given a preference name or ID of file type script preference, stores the
/// preference value in a temporary file and returns the path.
// The C implementation of this function, is internally called by nasl_builtin_find_service to
// store the TLS stuff in temporary files, which are later use to create a tls socket.
pub fn get_plugin_preference_fname(
    register: &Register,
    config: &ScanCtx,
    name: Option<String>,
    id: Option<usize>,
) -> Result<String, FnError> {
    let mut tmp = match NamedTempFile::with_prefix("openvas-file-upload.") {
        Ok(f) => f,
        Err(e) => return Err(BuiltinError::Preference(e.to_string()).into()),
    };
    if let Some(file_content) =
        script_get_preference_file_content_shared(register, config, name, id)
        && tmp.write_all(&file_content).is_ok()
    {
        return Ok(tmp.path().to_string_lossy().into_owned());
    }
    Err(BuiltinError::Preference(format!(
        "get_plugin_preference_fname: Could not create temporary file for {:?}",
        tmp.path()
    ))
    .into())
}

pub async fn plug_set_ssl_cert(config: &ScanCtx<'_>, path: String) -> Result<(), FnError> {
    config.set_single_kb_item(KbKey::Ssl(Ssl::Cert), path).await
}

pub async fn plug_set_ssl_key(config: &ScanCtx<'_>, path: String) -> Result<(), FnError> {
    config.set_single_kb_item(KbKey::Ssl(Ssl::Key), path).await
}

pub async fn plug_set_ssl_password(config: &ScanCtx<'_>, path: String) -> Result<(), FnError> {
    config
        .set_single_kb_item(KbKey::Ssl(Ssl::Password), path)
        .await
}

pub async fn plug_set_ssl_ca_file(config: &ScanCtx<'_>, path: String) -> Result<(), FnError> {
    config.set_single_kb_item(KbKey::Ssl(Ssl::Ca), path).await
}

#[nasl_function(named(id))]
fn script_get_preference_file_content(
    register: &Register,
    config: &ScanCtx,
    name: Option<String>,
    id: Option<usize>,
) -> Option<Vec<u8>> {
    script_get_preference_file_content_shared(register, config, name, id)
}

#[nasl_function(named(id))]
fn script_get_preference(
    register: &Register,
    config: &ScanCtx,
    name: Option<String>,
    id: Option<usize>,
) -> Option<NaslValue> {
    script_get_preference_shared(register, config, name, id)
}

#[nasl_function]
fn get_preference(config: &ScanCtx, name: String) -> Option<NaslValue> {
    let val = if let Some(pref) = config.scan_params().find(|p| p.id == name) {
        pref.value.clone()
    } else {
        return None;
    };

    for p in PREFERENCES.to_vec().iter() {
        if p.id == name {
            match p.default {
                PreferenceValue::Bool(_) => match val.as_str() {
                    "false" => return Some(NaslValue::String("no".to_string())),
                    "true" => return Some(NaslValue::String("yes".to_string())),
                    _ => return Some(NaslValue::String(val)),
                },
                PreferenceValue::String(_) => return Some(NaslValue::String(val)),
                PreferenceValue::Int(_) => {
                    return Some(NaslValue::Number(val.parse::<i64>().unwrap()));
                }
            }
        };
    }
    Some(NaslValue::Null)
}

/// The description builtin function
pub struct Preferences;

function_set! {
    Preferences,
    (
        script_get_preference,
        get_preference,
        script_get_preference_file_content,
    )
}
