use crate::{MemberManifest, RingspaceError, Standing};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as BASE64URL};
use crypto_box::{SecretKey, aead::OsRng};
use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
use openssl::pkey::PKey;
use reqwest::get;
use serde::Serialize;
use serde_json::{Value, to_string};

///
/// URL-safe sanitization of inputs for SQL database
///
pub fn sanitize(s: &str) -> String {
    s.chars()
        .filter(|&c| match c {
            '\'' | '"' | ';' | '`' | '\\' => false,
            '\0' | '\n' | '\r' | '\t' => false,
            '-' if s.contains("--") => false,
            '#' if s.contains("/*") || s.contains("*/") => false,
            'A'..='Z' | 'a'..='z' | '0'..='9' => true,
            ':' | '/' | '?' | '&' | '=' | '+' | '%' | '@' => true,
            '.' | '-' | '_' | '~' | '!' | '*' | '(' | ')' => true,
            '[' | ']' => true,
            ' ' => true,
            _ => false,
        })
        .collect()
}

#[derive(Serialize, Debug, Clone)]
pub struct Keypair {
    pub private_key: String,
    pub public_key: String,
}

///
/// Generates a keypair comprising two base64-encoded byte sequences of
/// X25519.
///
pub fn generate_encryption_keypair() -> Keypair {
    let private_key = SecretKey::generate(&mut OsRng);
    let public_key = private_key.public_key();

    // let key = PKey::generate_x25519().unwrap();
    Keypair {
        private_key: BASE64URL.encode(private_key.to_bytes()),
        public_key: BASE64URL.encode(public_key.to_bytes()),
    }
}

///
/// Generates a keypair comprising two base64-encoded byte sequences of
/// ED25519.
///
pub fn generate_signing_keypair() -> Keypair {
    let key = PKey::generate_ed25519().unwrap();
    Keypair {
        private_key: BASE64URL.encode(key.raw_private_key().unwrap()),
        public_key: BASE64URL.encode(key.raw_public_key().unwrap()),
    }
}

///
/// Gets input from stdin in the event a parameter
/// was not passed on the command line
///
/// TODO: Safety this up
///
pub fn get_input(prompt: &str) -> String {
    use std::io::Write;
    print!("{prompt} ");
    std::io::stdout().flush().unwrap();
    let mut buf = String::new();
    match std::io::stdin().read_line(&mut buf) {
        Ok(_) => buf.trim().to_string(),
        Err(e) => format!("{e:?}"),
    }
}

///
/// Helper to get a vote from input and check for valid [Standing]
///
pub fn get_vote(url: &str) -> Result<Standing, RingspaceError> {
    loop {
        let vote = get_input(&format!("Vote for {} (good/bad/neutral) :", url));
        if let Ok(standing) = Standing::try_from(vote) {
            return Ok(standing);
        } else {
            println!("Invalid vote! Must be one of: good, bad, neutral");
        }
    }
}

///
/// Retrieve site manifest data
///
pub async fn get_site_manifest(url: &str) -> Result<MemberManifest, RingspaceError> {
    let res = get(url).await?;
    Ok(res.json::<MemberManifest>().await?)
}

///
/// Sign a [serde_json::Value] with a given private key
///
pub fn sign_value(value: &Value, private_signing_key: &str) -> Result<Signature, RingspaceError> {
    let value_string = to_string(&value)?;
    let value_bytes = value_string.as_bytes();
    let key_array: [u8; 32] = BASE64URL.decode(private_signing_key)?.try_into()?;
    let signing_key = SigningKey::from_bytes(&key_array);
    Ok(signing_key.sign(value_bytes))
}

///
/// Verifies the signature on a given [Value] with a signature and a
/// private key
///
pub fn verify_value(
    value: &Value,
    signature: &str,
    public_signing_key: &str,
) -> Result<bool, RingspaceError> {
    let value_string = to_string(&value)?;
    let value_bytes = value_string.as_bytes();
    let key_array: [u8; 32] = BASE64URL.decode(public_signing_key)?.try_into()?;
    let signature_array: [u8; 64] = BASE64URL.decode(signature)?.try_into()?;
    let verifier = VerifyingKey::from_bytes(&key_array)?;
    let signature = Signature::from_bytes(&signature_array);
    Ok(verifier.verify_strict(value_bytes, &signature).is_ok())
}
