//! Signature verification helpers.
//!
//! Note: random.org does **not** publish their RSA public key, so there is no
//! offline verification path. All signature checking goes through the
//! [`Client::verify_signature`] RPC, which relays to their server.
//! The other helpers here (`create_url`, `create_html`) build the
//! self-contained verification artefacts described at
//! <https://api.random.org/signatures/form>.

use base64::{Engine, engine::general_purpose::STANDARD as B64};
use serde_json::{Value, json};

use crate::client::Client;
use crate::error::Error;
use crate::types::VerifyResult;

impl Client {
    /// Server-side signature verification — relays the (`random`, `signature`)
    /// pair to random.org's `verifySignature` method.
    pub async fn verify_signature(
        &self,
        random: &Value,
        signature: &str,
    ) -> Result<VerifyResult, Error> {
        let params = json!({ "random": random, "signature": signature });
        self.call("verifySignature", params).await
    }

    /// Re-fetch a previously signed result by serial number.
    pub async fn get_result(&self, serial_number: u64) -> Result<Value, Error> {
        self.call_keyed("getResult", json!({ "serialNumber": serial_number }))
            .await
    }

    /// Build the URL for the signature-verification page of a signed response.
    /// Pure client-side string construction (mirrors the Python lib helper).
    /// Errors if the resulting URL exceeds 2046 chars.
    pub fn create_url(&self, random: &Value, signature: &str) -> Result<String, Error> {
        let r = url_formatting_value(random)?;
        let s = url_formatting_str(signature);
        let url =
            format!("https://api.random.org/signatures/form?format=json&random={r}&signature={s}");
        if url.len() > 2046 {
            return Err(Error::Invalid("URL exceeds 2046 chars".into()));
        }
        Ok(url)
    }

    /// Build an HTML form posting a signed response to the verification page.
    pub fn create_html(&self, random: &Value, signature: &str) -> String {
        let random_str = serde_json::to_string(random).unwrap_or_default();
        let mut s =
            String::from("<form action='https://api.random.org/signatures/form' method='post'>\n");
        s.push_str("  <input type='hidden' name='format' value='json' />\n");
        s.push_str(&format!(
            "  <input type='hidden' name='random' value='{}' />\n",
            html_escape(&random_str)
        ));
        s.push_str(&format!(
            "  <input type='hidden' name='signature' value='{}' />\n",
            html_escape(signature)
        ));
        s.push_str("  <input type='submit' value='Validate' />\n</form>");
        s
    }
}

fn url_formatting_value(v: &Value) -> Result<String, Error> {
    let s = serde_json::to_string(v)?;
    Ok(percent_encode_for_form(&base64_if_needed(&s)))
}

fn url_formatting_str(s: &str) -> String {
    percent_encode_for_form(&base64_if_needed(s))
}

fn base64_if_needed(s: &str) -> String {
    if is_base64(s) {
        s.to_string()
    } else {
        B64.encode(s.as_bytes())
    }
}

fn is_base64(s: &str) -> bool {
    if s.is_empty() {
        return false;
    }
    let bytes = s.as_bytes();
    #[allow(clippy::manual_is_multiple_of)]
    if bytes.len() % 4 != 0 {
        return false;
    }
    let mut content_end = bytes.len();
    while content_end > 0 && bytes[content_end - 1] == b'=' {
        content_end -= 1;
    }
    if bytes.len() - content_end > 2 {
        return false;
    }
    bytes[..content_end]
        .iter()
        .all(|&b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/')
}

fn percent_encode_for_form(s: &str) -> String {
    s.replace('=', "%3D")
        .replace('+', "%2B")
        .replace('/', "%2F")
}

fn html_escape(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('\'', "&#39;")
}
