//! Contract Testing Framework for Synor Smart Contracts.
//!
//! This crate provides a comprehensive testing framework for Synor smart contracts,
//! including test environments, mock storage, test accounts, and assertion utilities.
//!
//! # Quick Start
//!
//! ```rust,ignore
//! use synor_contract_test::prelude::*;
//!
//! #[tokio::test]
//! async fn test_token_contract() {
//!     // Create test environment with accounts
//!     let mut env = TestEnvironment::builder()
//!         .with_account(TestAccount::with_balance(1000))
//!         .with_account(TestAccount::with_balance(500))
//!         .build();
//!
//!     // Deploy contract
//!     let bytecode = include_bytes!("../contracts/token.wasm");
//!     let contract = env.deploy_contract(bytecode, &init_params).unwrap();
//!
//!     // Call contract methods
//!     let result = env.call_contract(contract, "transfer", &args).unwrap();
//!
//!     // Assert results
//!     assert_success!(result);
//!     assert_event_emitted!(result, "Transfer");
//! }
//! ```
//!
//! # Features
//!
//! - **TestEnvironment**: Wraps VmEngine with test utilities
//! - **MockStorage**: Isolated storage for testing
//! - **TestAccount**: Pre-funded accounts with keypairs
//! - **Assertions**: Macros for testing results, storage, and events
//!
//! # Architecture
//!
//! ```text
//! +------------------+
//! | TestEnvironment  |
//! +------------------+
//!         |
//!         v
//! +------------------+     +------------------+
//! |    VmEngine      |<--->|   MockStorage    |
//! +------------------+     +------------------+
//!         |
//!         v
//! +------------------+
//! |   TestAccount[]  |
//! +------------------+
//! ```

#![allow(dead_code)]

pub mod assertions;
mod environment;
mod mock_storage;
mod test_account;

// Re-export core types
pub use environment::{TestEnvironment, TestEnvironmentBuilder};
pub use mock_storage::MockStorage;
pub use test_account::{TestAccount, TestKeypair};

// Re-export assertion utilities
pub use assertions::{
    event_selector, to_return_bytes, to_storage_bytes, to_topic_bytes, AssertionChain,
    AssertionError, ReturnEncodable, StorageEncodable, TopicEncodable,
};

// Re-export VM types for convenience
pub use synor_vm;
pub use synor_vm::{
    CallParams, ContractId, ContractLog, DeployParams, ExecutionResult, StorageChange, StorageKey,
    StorageValue, VmEngine, VmError,
};

/// Errors that can occur during contract testing.
#[derive(Debug, thiserror::Error)]
pub enum TestError {
    /// VM error during execution.
    #[error("VM error: {0}")]
    VmError(#[from] VmError),

    /// Contract deployment failed.
    #[error("Deployment failed: {0}")]
    DeploymentFailed(String),

    /// Contract call failed.
    #[error("Call failed: {0}")]
    CallFailed(String),

    /// Account not found.
    #[error("Account not found: {0}")]
    AccountNotFound(String),

    /// Insufficient balance.
    #[error("Insufficient balance: have {have}, need {need}")]
    InsufficientBalance { have: u64, need: u64 },

    /// Invalid bytecode.
    #[error("Invalid bytecode: {0}")]
    InvalidBytecode(String),

    /// Serialization error.
    #[error("Serialization error: {0}")]
    SerializationError(String),

    /// Invalid state.
    #[error("Invalid state: {0}")]
    InvalidState(String),

    /// Configuration error.
    #[error("Configuration error: {0}")]
    ConfigError(String),
}

/// Result type for contract test operations.
pub type TestResult<T> = Result<T, TestError>;

/// Prelude module for convenient imports.
pub mod prelude {
    pub use crate::assertions::{
        event_selector, to_return_bytes, to_storage_bytes, to_topic_bytes, AssertionChain,
        ReturnEncodable, StorageEncodable, TopicEncodable,
    };
    pub use crate::{
        assert_event_emitted, assert_gas_used, assert_no_events, assert_return_data, assert_revert,
        assert_storage_empty, assert_storage_eq, assert_storage_none, assert_success,
    };
    pub use crate::{
        CallParams, ContractId, DeployParams, ExecutionResult, MockStorage, StorageKey,
        StorageValue, TestAccount, TestEnvironment, TestError, TestKeypair, TestResult, VmError,
    };
    pub use synor_types::{Address, Amount, Hash256, Network};
}

/// Utility to encode method call arguments.
pub fn encode_call_args(method: &str, args: &[u8]) -> Vec<u8> {
    let selector = synor_sdk::method_selector(method);
    let mut data = Vec::with_capacity(4 + args.len());
    data.extend_from_slice(&selector);
    data.extend_from_slice(args);
    data
}

/// Utility to decode return data.
pub fn decode_return_data<T: borsh::BorshDeserialize>(data: &[u8]) -> TestResult<T> {
    borsh::from_slice(data).map_err(|e| TestError::SerializationError(e.to_string()))
}

/// Utility to encode arguments using borsh.
pub fn encode_args<T: borsh::BorshSerialize>(args: &T) -> TestResult<Vec<u8>> {
    borsh::to_vec(args).map_err(|e| TestError::SerializationError(e.to_string()))
}

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

    #[test]
    fn test_encode_call_args() {
        let args = encode_call_args("transfer", &[1, 2, 3]);
        assert!(args.len() >= 4);

        // Method selector should be first 4 bytes
        let selector = synor_sdk::method_selector("transfer");
        assert_eq!(&args[..4], &selector);
        assert_eq!(&args[4..], &[1, 2, 3]);
    }

    #[test]
    fn test_encode_decode_roundtrip() {
        let original: u64 = 12345;
        let encoded = encode_args(&original).unwrap();
        let decoded: u64 = decode_return_data(&encoded).unwrap();
        assert_eq!(original, decoded);
    }
}
