// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Validation for API request types

use std::str::FromStr;

use rialo_s_sdk::pubkey::Pubkey;
use thiserror::Error;
use validator::ValidationErrors;

use crate::constants::*;

/// Validation error types for RPC requests
#[derive(Debug, Error, Clone)]
pub enum ValidationError {
    #[error("Invalid format: {0}")]
    InvalidFormat(String),

    #[error("Value out of range: {0}")]
    OutOfRange(String),

    #[error("Missing required field: {0}")]
    MissingField(String),

    #[error("Invalid signature: {0}")]
    InvalidSignature(String),

    #[error("Invalid encoding: {0}. Supported encodings: base64, base58")]
    InvalidEncoding(String),

    #[error("Invalid public key: {0}")]
    InvalidPublicKey(String),

    #[error("Invalid transaction: {0}")]
    InvalidTransaction(String),

    #[error("Multiple validation errors: {0}")]
    Multiple(String),
}

impl From<ValidationErrors> for ValidationError {
    fn from(errors: ValidationErrors) -> Self {
        let mut error_messages: Vec<String> = Vec::new();

        // Collect field-level errors
        for (field, field_errors) in errors.field_errors() {
            for error in field_errors {
                let message = format!(
                    "{}: {}",
                    field,
                    error
                        .message
                        .as_ref()
                        .unwrap_or(&"validation failed".into())
                );
                error_messages.push(message);
            }
        }

        // Collect struct-level errors (from nested validations)
        for (field, struct_errors) in errors.errors() {
            if let validator::ValidationErrorsKind::Struct(nested_errors) = struct_errors {
                for (nested_field, nested_field_errors) in nested_errors.field_errors() {
                    for error in nested_field_errors {
                        let message = format!(
                            "{}.{}: {}",
                            field,
                            nested_field,
                            error
                                .message
                                .as_ref()
                                .unwrap_or(&"validation failed".into())
                        );
                        error_messages.push(message);
                    }
                }
            }
        }

        if error_messages.is_empty() {
            ValidationError::Multiple("Unknown validation error".to_string())
        } else if error_messages.len() == 1 {
            ValidationError::InvalidFormat(error_messages[0].clone())
        } else {
            ValidationError::Multiple(error_messages.join(", "))
        }
    }
}

/// Result type for validation operations
pub type ValidationResult<T> = Result<T, ValidationError>;

/// Validate Solana public key format
pub fn validate_pubkey(pubkey: &str) -> Result<(), validator::ValidationError> {
    Pubkey::from_str(pubkey).map_err(|_| validator::ValidationError::new("invalid_pubkey"))?;
    Ok(())
}

/// Validate base64 encoded data
pub fn validate_base64(data: &str) -> Result<(), validator::ValidationError> {
    use fastcrypto::encoding::{Base64, Encoding};
    Base64::decode(data).map_err(|_| validator::ValidationError::new("invalid_base64"))?;
    Ok(())
}

/// Validate base58 encoded data
pub fn validate_base58(data: &str) -> Result<(), validator::ValidationError> {
    use fastcrypto::encoding::{Base58, Encoding};
    Base58::decode(data).map_err(|_| validator::ValidationError::new("invalid_base58"))?;
    Ok(())
}

/// Validate transaction signature
pub fn validate_signature(signature: &str) -> Result<(), validator::ValidationError> {
    // Maximum string length for a 64-byte signature in base58 is 88 characters
    // Minimum can be as low as 64 characters if the signature has many leading zeros
    // (each leading zero byte encodes as a single '1' character in base58)
    if signature.len() > MAX_SIGNATURE_LENGTH {
        return Err(validator::ValidationError::new("invalid_signature_length"));
    }
    validate_base58(signature)
}

/// Validate nonce format (should be valid UTF-8 and reasonable length)
pub fn validate_nonce(nonce: &str) -> Result<(), validator::ValidationError> {
    if nonce.is_empty() {
        return Err(validator::ValidationError::new("empty_nonce"));
    }
    if nonce.len() > MAX_NONCE_LENGTH {
        return Err(validator::ValidationError::new("nonce_too_long"));
    }
    Ok(())
}

/// Validate kelvins amount (should be reasonable)
pub fn validate_kelvins(kelvins: u64) -> Result<(), validator::ValidationError> {
    // Validate against maximum possible kelvins (500 million RLO * 1e9 kelvins/RLO)
    if kelvins > MAX_KELVINS {
        return Err(validator::ValidationError::new("kelvins_too_large"));
    }
    Ok(())
}

/// Validate limit parameter for paginated requests
pub fn validate_limit(limit: &u64) -> Result<(), validator::ValidationError> {
    if *limit == 0 {
        return Err(validator::ValidationError::new("limit_zero"));
    }
    if *limit > MAX_PAGINATION_LIMIT {
        return Err(validator::ValidationError::new("limit_too_large"));
    }
    Ok(())
}

/// Custom validator for array of public keys
pub fn validate_pubkey_array(pubkeys: &[String]) -> Result<(), validator::ValidationError> {
    for pubkey in pubkeys {
        validate_pubkey(pubkey)?;
    }
    Ok(())
}

/// Custom validator for array of signatures
pub fn validate_signatures_array(signatures: &[String]) -> Result<(), validator::ValidationError> {
    for signature in signatures {
        validate_signature(signature)?;
    }
    Ok(())
}

/// Custom validator for airdrop amounts
pub fn validate_airdrop_amount(kelvins: u64) -> Result<(), validator::ValidationError> {
    validate_kelvins(kelvins)?;

    // Additional airdrop-specific validation
    if kelvins > MAX_AIRDROP_AMOUNT {
        return Err(validator::ValidationError::new("airdrop_amount_too_large"));
    }

    if kelvins == 0 {
        return Err(validator::ValidationError::new("airdrop_amount_zero"));
    }

    Ok(())
}

/// Custom validator for airdrop amounts (i64 version)
pub fn validate_airdrop_amount_i64(kelvins: i64) -> Result<(), validator::ValidationError> {
    // Check for negative values
    if kelvins < 0 {
        return Err(validator::ValidationError::new("airdrop_amount_negative"));
    }

    // Check for zero
    if kelvins == 0 {
        return Err(validator::ValidationError::new("airdrop_amount_zero"));
    }

    // Convert to u64 for other validations
    let kelvins_u64 = kelvins as u64;
    validate_kelvins(kelvins_u64)?;

    // Additional airdrop-specific validation
    if kelvins_u64 > MAX_AIRDROP_AMOUNT {
        return Err(validator::ValidationError::new("airdrop_amount_too_large"));
    }

    Ok(())
}

/// Validate signature limit (1-1000)
pub fn validate_signature_limit(limit: &u16) -> Result<(), validator::ValidationError> {
    if *limit == 0 {
        return Err(validator::ValidationError::new("limit_must_be_positive"));
    }
    if *limit > MAX_PAGINATION_LIMIT as u16 {
        return Err(validator::ValidationError::new("limit_exceeds_maximum"));
    }
    Ok(())
}

/// Custom validator for transaction data based on encoding
pub fn validate_transaction_data(transaction: &str) -> Result<(), validator::ValidationError> {
    // Empty strings are handled by the length validator, so skip custom validation for them
    if transaction.is_empty() {
        return Ok(());
    }

    // Validate the encoding format - try base64 first (most common)
    if validate_base64(transaction).is_ok() {
        // Now validate that the decoded data can be parsed as a transaction
        return validate_transaction_structure_base64(transaction);
    }

    // If base64 fails, try base58
    if validate_base58(transaction).is_ok() {
        return validate_transaction_structure_base58(transaction);
    }

    // If neither encoding works, return error
    Err(validator::ValidationError::new(
        "invalid_transaction_encoding",
    ))
}

/// Validate that base64 encoded data represents a valid transaction structure
fn validate_transaction_structure_base64(
    transaction: &str,
) -> Result<(), validator::ValidationError> {
    use fastcrypto::encoding::{Base64, Encoding};

    // Decode the base64 data
    let decoded = Base64::decode(transaction)
        .map_err(|_| validator::ValidationError::new("invalid_base64_transaction"))?;

    validate_transaction_bytes(&decoded)
}

/// Validate that base58 encoded data represents a valid transaction structure  
fn validate_transaction_structure_base58(
    transaction: &str,
) -> Result<(), validator::ValidationError> {
    use fastcrypto::encoding::{Base58, Encoding};

    // Decode the base58 data
    let decoded = Base58::decode(transaction)
        .map_err(|_| validator::ValidationError::new("invalid_base58_transaction"))?;

    validate_transaction_bytes(&decoded)
}

/// Validate the raw transaction bytes represent a valid transaction structure
fn validate_transaction_bytes(transaction_bytes: &[u8]) -> Result<(), validator::ValidationError> {
    // Basic size validation - transactions should be at least some minimum size
    if transaction_bytes.len() < MIN_TRANSACTION_SIZE {
        return Err(validator::ValidationError::new("transaction_too_small"));
    }

    // Maximum transaction size check (Solana has a 1232 byte limit)
    if transaction_bytes.len() > MAX_TRANSACTION_SIZE {
        return Err(validator::ValidationError::new("transaction_too_large"));
    }

    // Try to deserialize as a VersionedTransaction to validate structure
    match bincode::deserialize::<rialo_s_sdk::transaction::VersionedTransaction>(transaction_bytes)
    {
        Ok(_) => Ok(()),
        Err(_) => {
            // If VersionedTransaction fails, try legacy Transaction format
            match bincode::deserialize::<rialo_s_sdk::transaction::Transaction>(transaction_bytes) {
                Ok(_) => Ok(()),
                Err(_) => Err(validator::ValidationError::new(
                    "invalid_transaction_structure",
                )),
            }
        }
    }
}

/// Custom validator for limit as string (some endpoints use string format)
pub fn validate_limit_string(limit: &str) -> Result<(), validator::ValidationError> {
    let limit_val: u64 = limit
        .parse()
        .map_err(|_| validator::ValidationError::new("invalid_limit_format"))?;
    validate_limit(&limit_val)?;
    Ok(())
}

/// Validate blockhash format (should be valid base58)
pub fn validate_blockhash(blockhash: &str) -> Result<(), validator::ValidationError> {
    // Solana blockhashes are base58 encoded and should be 32 bytes (44 characters in base58)
    if blockhash.len() < MIN_BLOCKHASH_LENGTH || blockhash.len() > MAX_BLOCKHASH_LENGTH {
        return Err(validator::ValidationError::new("invalid_blockhash_length"));
    }
    validate_base58(blockhash)
}

/// Validate array of addresses (public keys)
pub fn validate_addresses(addresses: &[String]) -> Result<(), validator::ValidationError> {
    for address in addresses {
        validate_pubkey(address)?;
    }
    Ok(())
}

/// Validate array of signatures
pub fn validate_signatures(signatures: &[String]) -> Result<(), validator::ValidationError> {
    for signature in signatures {
        validate_signature(signature)?;
    }
    Ok(())
}

/// Validate encoding format
pub fn validate_encoding(encoding: &str) -> Result<(), validator::ValidationError> {
    match encoding {
        "json" | "jsonParsed" | "base58" | "base64" => Ok(()),
        _ => Err(validator::ValidationError::new("invalid_encoding_format")),
    }
}

/// Validate max transaction version
pub fn validate_max_transaction_version(version: &u8) -> Result<(), validator::ValidationError> {
    if *version <= 1 {
        Ok(())
    } else {
        Err(validator::ValidationError::new(
            "invalid_max_transaction_version",
        ))
    }
}

/// Validation middleware that validates a request
pub fn validate_request<T>(request: T) -> ValidationResult<T>
where
    T: validator::Validate,
{
    request.validate().map_err(ValidationError::from)?;
    Ok(request)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validate_limit() {
        assert!(validate_limit(&1).is_ok());
        assert!(validate_limit(&MAX_PAGINATION_LIMIT).is_ok());
        assert!(validate_limit(&0).is_err());
        assert!(validate_limit(&(MAX_PAGINATION_LIMIT + 1)).is_err());
    }

    #[test]
    fn test_validate_nonce() {
        assert!(validate_nonce("valid_nonce").is_ok());
        assert!(validate_nonce("").is_err());
        let long_nonce = "x".repeat(65);
        assert!(validate_nonce(&long_nonce).is_err());
    }

    #[test]
    fn test_validate_encoding() {
        assert!(validate_encoding("json").is_ok());
        assert!(validate_encoding("jsonParsed").is_ok());
        assert!(validate_encoding("base58").is_ok());
        assert!(validate_encoding("base64").is_ok());
        assert!(validate_encoding("invalid").is_err());
    }

    #[test]
    fn test_validate_signature() {
        use fastcrypto::encoding::{Base58, Encoding};

        // Valid signature (87-88 chars, decodes to 64 bytes)
        let valid_sig =
            "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW";
        assert!(validate_signature(valid_sig).is_ok());

        // Signature with leading zeros (shorter string, still 64 bytes when decoded)
        // Create a signature with leading zero bytes
        let mut sig_with_zeros = [0u8; 64];
        sig_with_zeros[63] = 1; // Only last byte is non-zero
        let short_sig = Base58::encode(sig_with_zeros);
        // This will be much shorter than 87 chars due to leading zeros
        assert!(
            short_sig.len() < 87,
            "Expected short signature due to leading zeros, got len {}",
            short_sig.len()
        );
        assert!(
            validate_signature(&short_sig).is_ok(),
            "Signature with leading zeros should be valid"
        );

        // All zeros signature (64 '1' characters in base58)
        let all_zeros = [0u8; 64];
        let all_zeros_sig = Base58::encode(all_zeros);
        assert_eq!(all_zeros_sig.len(), 64);
        assert!(
            validate_signature(&all_zeros_sig).is_ok(),
            "All-zeros signature should be valid"
        );

        // Invalid: not base58
        assert!(validate_signature("invalid!signature").is_err());

        // Invalid: too long string
        let too_long = "1".repeat(100);
        assert!(validate_signature(&too_long).is_err());
    }

    #[test]
    fn test_validate_max_transaction_version() {
        assert!(validate_max_transaction_version(&0).is_ok());
        assert!(validate_max_transaction_version(&1).is_ok());
        assert!(validate_max_transaction_version(&2).is_err());
    }

    #[test]
    fn test_validation_error_from_field_errors() {
        use validator::Validate;

        // Create a struct with field-level validation errors
        #[derive(Validate)]
        struct TestStruct {
            #[validate(length(min = 1, message = "Field cannot be empty"))]
            field1: String,
            #[validate(range(min = 0, max = 100, message = "Must be between 0 and 100"))]
            field2: usize,
        }

        let test = TestStruct {
            field1: "".to_string(), // Invalid: empty
            field2: 150,            // Invalid: out of range
        };

        let validation_result = test.validate();
        assert!(validation_result.is_err());

        let validation_errors = validation_result.unwrap_err();
        let error: ValidationError = validation_errors.into();

        // Should be Multiple since there are 2 errors
        match error {
            ValidationError::Multiple(msg) => {
                assert!(msg.contains("field1"));
                assert!(msg.contains("Field cannot be empty"));
                assert!(msg.contains("field2"));
                assert!(msg.contains("Must be between 0 and 100"));
            }
            other => panic!("Expected ValidationError::Multiple, got {:?}", other),
        }
    }

    #[test]
    fn test_validation_error_from_nested_struct_errors() {
        use validator::Validate;

        #[derive(Validate)]
        struct NestedConfig {
            #[validate(range(
                min = 0,
                max = 100,
                message = "Max retries must be between 0 and 100"
            ))]
            max_retries: usize,
            #[validate(range(min = 0, message = "Min slot must be non-negative"))]
            min_slot: u64,
        }

        #[derive(Validate)]
        struct ParentStruct {
            #[validate(length(min = 1, message = "Name cannot be empty"))]
            name: String,
            #[validate(nested)]
            config: NestedConfig,
        }

        let test = ParentStruct {
            name: "valid".to_string(),
            config: NestedConfig {
                max_retries: 150, // Invalid: exceeds 100
                min_slot: 0,
            },
        };

        let validation_result = test.validate();
        assert!(validation_result.is_err());

        let validation_errors = validation_result.unwrap_err();
        let error: ValidationError = validation_errors.into();

        // Should contain the nested field path
        let error_msg = error.to_string();
        assert!(
            error_msg.contains("config.max_retries"),
            "Expected 'config.max_retries' in error message, got: {}",
            error_msg
        );
        assert!(
            error_msg.contains("Max retries must be between 0 and 100"),
            "Expected validation message in error, got: {}",
            error_msg
        );
    }

    #[test]
    fn test_validation_error_from_mixed_errors() {
        use validator::Validate;

        #[derive(Validate)]
        struct NestedConfig {
            #[validate(range(min = 0, max = 100, message = "Nested field must be 0-100"))]
            nested_field: usize,
        }

        #[derive(Validate)]
        struct ParentStruct {
            #[validate(length(min = 1, message = "Parent field cannot be empty"))]
            parent_field: String,
            #[validate(nested)]
            config: NestedConfig,
        }

        let test = ParentStruct {
            parent_field: "".to_string(), // Invalid: empty
            config: NestedConfig {
                nested_field: 150, // Invalid: out of range
            },
        };

        let validation_result = test.validate();
        assert!(validation_result.is_err());

        let validation_errors = validation_result.unwrap_err();
        let error: ValidationError = validation_errors.into();

        let error_msg = error.to_string();
        // Should contain both parent and nested errors
        assert!(
            error_msg.contains("parent_field")
                && error_msg.contains("Parent field cannot be empty"),
            "Expected parent field error in message, got: {}",
            error_msg
        );
        assert!(
            error_msg.contains("config.nested_field")
                && error_msg.contains("Nested field must be 0-100"),
            "Expected nested field error in message, got: {}",
            error_msg
        );
    }
}
