use crate::parse;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as base64_engine;
use base64::Engine as _;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use hex::FromHex;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::{InitData, InitDataError};

const TEST_PUBLIC_KEY: &str = "40055058a4ee38156a06562e52eece92a771bcd8346a8c4615cb7376eddf72ec";
const PROD_PUBLIC_KEY: &str = "e7bf03a2fa4602af4580703d88dda5bb59f32ed8b02a56c187fe7d34caed242d";

/// Validates data for third-party use
///
/// If you need to share the data with a third party, they can validate the data without requiring access to your bot's token.
/// Simply provide them with the data from the Telegram.WebApp.initData field and your `bot_id`.
///
/// See: <https://core.telegram.org/bots/webapps#validating-data-for-third-party-use>
///
/// Telegram provides the following Ed25519 public keys for signature verification:
/// * `40055058a4ee38156a06562e52eece92a771bcd8346a8c4615cb7376eddf72ec` for test environment
/// * `e7bf03a2fa4602af4580703d88dda5bb59f32ed8b02a56c187fe7d34caed242d` for production environment
///
/// # Arguments
/// * `init_data` - Raw init data string from Telegram Mini App
/// * `bot_id` - Bot ID
/// * `expires_in` - Optional expiration time in seconds
/// * `is_test` - Whether to use the test public key
///
/// # Returns
/// * `Ok(InitData)` - Parsed and validated init data
/// * `Err(InitDataError)` - Various validation or parsing errors
///
fn validate_third_party_with_signature(
    init_data: &str,
    bot_id: i64,
    expires_in: Option<u64>,
    is_test: bool,
) -> Result<InitData, InitDataError> {
    if init_data.is_empty() || !init_data.contains('=') {
        return Err(InitDataError::UnexpectedFormat(
            "init_data is empty or malformed".to_string(),
        ));
    }

    let pairs: Vec<(String, String)> = url::form_urlencoded::parse(init_data.as_bytes())
        .map(|(k, v)| (k.into_owned(), v.into_owned()))
        .collect();

    let mut signature_b64 = None;
    let mut filtered_pairs: Vec<(String, String)> = Vec::new();
    let mut auth_date: Option<u64> = None;
    for (k, v) in pairs {
        if k == "signature" {
            signature_b64 = Some(v);
        } else if k == "auth_date" {
            auth_date = v.parse().ok();
            filtered_pairs.push((k, v));
        } else if k != "hash" {
            filtered_pairs.push((k, v));
        }
    }
    let signature_b64 = signature_b64.ok_or(InitDataError::SignatureMissing)?;

    if let (Some(expires_in), Some(auth_date)) = (expires_in, auth_date) {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        if auth_date + expires_in < now {
            return Err(InitDataError::Expired);
        }
    }

    filtered_pairs.sort_by(|a, b| a.0.cmp(&b.0));

    let message = format!(
        "{}:WebAppData\n{}",
        bot_id,
        filtered_pairs
            .iter()
            .map(|(k, v)| format!("{k}={v}"))
            .collect::<Vec<_>>()
            .join("\n")
    );

    let signature_bytes = base64_engine
        .decode(signature_b64.as_bytes())
        .map_err(|_| InitDataError::SignatureInvalid("Failed to decode signature from base64".to_string()))?;

    let signature = Signature::from_slice(&signature_bytes)
        .map_err(|_| InitDataError::SignatureInvalid("Failed to parse signature".to_string()))?;

    let public_key_hex = if is_test { TEST_PUBLIC_KEY } else { PROD_PUBLIC_KEY };

    let public_key_bytes = <[u8; 32]>::from_hex(public_key_hex)
        .map_err(|_| InitDataError::SignatureInvalid("Failed to parse public key".to_string()))?;

    let verifying_key = VerifyingKey::from_bytes(&public_key_bytes)
        .map_err(|_| InitDataError::SignatureInvalid("Failed to parse public key".to_string()))?;

    verifying_key
        .verify(message.as_bytes(), &signature)
        .map_err(|_| InitDataError::SignatureInvalid("Failed to verify signature".to_string()))?;

    // 9. If valid, parse into InitData and return Ok
    let data = parse(init_data)?;
    Ok(data)
}

/// Validates init data using both primary and third-party bot tokens.
///
/// Similar to `validate()`, but accepts an additional third-party bot token
/// for validation. The init data is considered valid if it matches either token.
///
/// # Arguments
/// * `init_data` - Raw init data string from Telegram Mini App
/// * `bot_id` - Bot ID
/// * `expires_in` - Optional expiration time in seconds
///
/// # Returns
/// * `Ok(InitData)` - Parsed and validated init data
/// * `Err(InitDataError)` - Various validation or parsing errors
///
/// # Example
/// ```
/// use init_data_rs::validate_third_party;
///
/// let init_data = "query_id=123&auth_date=1662771648&hash=...&signature=...";
/// let result = validate_third_party(init_data, 1234567890, None);
/// ```
///
/// # Errors
///
/// See `init_data_rs::parse` for possible errors
pub fn validate_third_party(init_data: &str, bot_id: i64, expires_in: Option<u64>) -> Result<InitData, InitDataError> {
    validate_third_party_with_signature(init_data, bot_id, expires_in, false)
}

#[cfg(test)]
mod tests {
    use super::*;
    // With signature
    const VALID_INIT_DATA: &str = "user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%20%2B%20-%20%3F%20%5C%2F%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%2C%22photo_url%22%3A%22https%3A%5C%2F%5C%2Ft.me%5C%2Fi%5C%2Fuserpic%5C%2F320%5C%2F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%22%7D&chat_instance=8134722200314281151&chat_type=private&auth_date=1733584787&hash=2174df5b000556d044f3f020384e879c8efcab55ddea2ced4eb752e93e7080d6&signature=zL-ucjNyREiHDE8aihFwpfR9aggP2xiAo3NSpfe-p7IbCisNlDKlo7Kb6G4D0Ao2mBrSgEk4maLSdv6MLIlADQ";
    const BOT_ID: i64 = 7342037359;

    #[test]
    fn test_valid_third_party_signature() {
        let result = validate_third_party(VALID_INIT_DATA, BOT_ID, None);
        assert!(result.is_ok(), "Expected Ok, got {result:?}");
    }

    #[test]
    fn test_invalid_signature() {
        // Tamper with the signature
        let tampered = VALID_INIT_DATA.replace(
            "zL-ucjNyREiHDE8aihFwpfR9aggP2xiAo3NSpfe-p7IbCisNlDKlo7Kb6G4D0Ao2mBrSgEk4maLSdv6MLIlADQ",
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        );
        let result = validate_third_party(&tampered, BOT_ID, None);
        assert!(matches!(result, Err(InitDataError::SignatureInvalid(_))));
    }

    #[test]
    fn test_third_party_invalid_base64_signature() {
        let bad_data = "query_id=test&auth_date=123&signature=!!!notbase64!!!&hash=abc";
        let bot_id = 123456;
        let result = validate_third_party_with_signature(bad_data, bot_id, None, true);
        assert!(matches!(result, Err(InitDataError::SignatureInvalid(_))));
    }

    #[test]
    fn test_third_party_invalid_public_key() {
        let valid_data = "query_id=test&auth_date=123&signature=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&hash=abc";
        let bot_id = 123456;
        // Use an invalid public key by temporarily changing the constant or by passing a custom function if your API allows
        // For this test, you might need to expose a version of your function that takes a public key string
        let result = validate_third_party_with_signature(valid_data, bot_id, None, true); // with a purposely broken key
        assert!(matches!(result, Err(InitDataError::SignatureInvalid(_))));
    }

    #[test]
    fn test_third_party_signature_verification_failure() {
        // Use a valid base64 signature, but one that doesn't match the data
        let bad_sig = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode([0u8; 64]);
        let bad_data = format!("query_id=test&auth_date=123&signature={bad_sig}&hash=abc");
        let bot_id = 123456;
        let result = validate_third_party_with_signature(&bad_data, bot_id, None, true);
        assert!(matches!(result, Err(InitDataError::SignatureInvalid(_))));
    }

    #[test]
    fn test_missing_signature() {
        // Remove the signature field
        let mut parts: Vec<&str> = VALID_INIT_DATA.split('&').collect();
        parts.retain(|s| !s.starts_with("signature="));
        let no_sig = parts.join("&");
        let result = validate_third_party(&no_sig, BOT_ID, None);
        assert!(matches!(result, Err(InitDataError::SignatureMissing)));
    }

    #[test]
    fn test_expired_data() {
        // Use a very old auth_date
        let expired_data = VALID_INIT_DATA.replace("auth_date=1733584787", "auth_date=1000000000");
        let result = validate_third_party(&expired_data, BOT_ID, Some(86400));
        assert!(matches!(result, Err(InitDataError::Expired)));
    }

    #[test]
    fn test_malformed_input() {
        let result = validate_third_party("not_a_query_string", BOT_ID, None);
        assert!(matches!(result, Err(InitDataError::UnexpectedFormat(_))));
    }

    #[test]
    fn test_wrong_bot_id() {
        // Use a wrong bot_id (signature won't match)
        let result = validate_third_party(VALID_INIT_DATA, 1234567890, None);
        assert!(matches!(result, Err(InitDataError::SignatureInvalid(_))));
    }

    #[test]
    fn test_wrong_environment() {
        // Use test environment (signature won't match prod key)
        let result = validate_third_party_with_signature(VALID_INIT_DATA, BOT_ID, None, true);
        assert!(matches!(result, Err(InitDataError::SignatureInvalid(_))));
    }
}
