//! Assertion macros and utilities for contract testing.
//!
//! Provides convenient macros for testing contract execution results,
//! storage state, and emitted events.

use synor_vm::ExecutionResult;

/// Result type returned from assertion checks.
pub type AssertionResult = Result<(), AssertionError>;

/// Errors from assertion checks.
#[derive(Debug, thiserror::Error)]
pub enum AssertionError {
    /// Expected success but got error.
    #[error("Expected success but got error: {0}")]
    ExpectedSuccess(String),

    /// Expected revert but got success.
    #[error("Expected revert but got success")]
    ExpectedRevert,

    /// Expected specific revert message.
    #[error("Expected revert message '{expected}' but got '{actual}'")]
    RevertMessageMismatch { expected: String, actual: String },

    /// Storage value mismatch.
    #[error("Storage mismatch for key {key}: expected {expected:?}, got {actual:?}")]
    StorageMismatch {
        key: String,
        expected: String,
        actual: String,
    },

    /// Storage key not found.
    #[error("Storage key not found: {0}")]
    StorageKeyNotFound(String),

    /// Event not found.
    #[error("Expected event '{0}' was not emitted")]
    EventNotFound(String),

    /// Event topic mismatch.
    #[error("Event topic mismatch: expected {expected}, got {actual}")]
    EventTopicMismatch { expected: String, actual: String },

    /// Return data mismatch.
    #[error("Return data mismatch: expected {expected:?}, got {actual:?}")]
    ReturnDataMismatch { expected: Vec<u8>, actual: Vec<u8> },

    /// Gas assertion failed.
    #[error("Gas assertion failed: {0}")]
    GasAssertion(String),
}

/// Asserts that a contract execution succeeded.
///
/// # Examples
///
/// ```rust,ignore
/// let result = env.call_contract(contract, "transfer", &args)?;
/// assert_success!(result);
/// ```
#[macro_export]
macro_rules! assert_success {
    ($result:expr) => {
        match &$result {
            Ok(_) => {}
            Err(e) => {
                panic!("Expected success but got error: {:?}", e);
            }
        }
    };
    ($result:expr, $msg:expr) => {
        match &$result {
            Ok(_) => {}
            Err(e) => {
                panic!("{}: Expected success but got error: {:?}", $msg, e);
            }
        }
    };
}

/// Asserts that a contract execution reverted.
///
/// # Examples
///
/// ```rust,ignore
/// let result = env.call_contract(contract, "transfer", &args);
/// assert_revert!(result);
/// assert_revert!(result, "insufficient balance");
/// ```
#[macro_export]
macro_rules! assert_revert {
    ($result:expr) => {
        match &$result {
            Ok(_) => {
                panic!("Expected revert but got success");
            }
            Err($crate::synor_vm::VmError::Revert(_)) => {}
            Err(e) => {
                // Also accept other errors as "revert" in some sense
                let _ = e;
            }
        }
    };
    ($result:expr, $message:expr) => {
        match &$result {
            Ok(_) => {
                panic!("Expected revert but got success");
            }
            Err($crate::synor_vm::VmError::Revert(msg)) => {
                if !msg.contains($message) {
                    panic!(
                        "Expected revert message containing '{}' but got '{}'",
                        $message, msg
                    );
                }
            }
            Err(e) => {
                panic!(
                    "Expected Revert error with message '{}' but got: {:?}",
                    $message, e
                );
            }
        }
    };
}

/// Asserts that a storage value equals the expected value.
///
/// # Examples
///
/// ```rust,ignore
/// assert_storage_eq!(env, contract, "balance", 1000u64);
/// assert_storage_eq!(env, contract, key_bytes, expected_value);
/// ```
#[macro_export]
macro_rules! assert_storage_eq {
    ($env:expr, $contract:expr, $key:expr, $expected:expr) => {{
        let storage_key = $crate::synor_vm::StorageKey::from_bytes($key.as_ref());
        match $env.get_storage(&$contract, &storage_key) {
            Some(value) => {
                let expected_bytes = $crate::to_storage_bytes(&$expected);
                if value.as_bytes() != expected_bytes.as_slice() {
                    panic!(
                        "Storage mismatch for key {:?}: expected {:?}, got {:?}",
                        $key,
                        expected_bytes,
                        value.as_bytes()
                    );
                }
            }
            None => {
                panic!("Storage key not found: {:?}", $key);
            }
        }
    }};
}

/// Asserts that an event was emitted during contract execution.
///
/// # Examples
///
/// ```rust,ignore
/// let result = env.call_contract(contract, "transfer", &args)?;
/// assert_event_emitted!(result, "Transfer");
/// assert_event_emitted!(result, "Transfer", topic1, topic2);
/// ```
#[macro_export]
macro_rules! assert_event_emitted {
    ($result:expr, $event_type:expr) => {{
        let event_selector = $crate::event_selector($event_type);
        let found = $result.logs.iter().any(|log| {
            log.topics
                .first()
                .map(|t| t.as_bytes()[..4] == event_selector)
                .unwrap_or(false)
        });
        if !found {
            panic!("Expected event '{}' was not emitted", $event_type);
        }
    }};
    ($result:expr, $event_type:expr, $($topic:expr),+) => {{
        let event_selector = $crate::event_selector($event_type);
        let expected_topics: Vec<[u8; 32]> = vec![$($crate::to_topic_bytes(&$topic)),+];

        let found = $result.logs.iter().any(|log| {
            if log.topics.first()
                .map(|t| t.as_bytes()[..4] == event_selector)
                .unwrap_or(false)
            {
                // Check additional topics
                if log.topics.len() < expected_topics.len() + 1 {
                    return false;
                }
                expected_topics.iter().enumerate().all(|(i, expected)| {
                    log.topics.get(i + 1)
                        .map(|t| t.as_bytes() == expected)
                        .unwrap_or(false)
                })
            } else {
                false
            }
        });

        if !found {
            panic!(
                "Expected event '{}' with specified topics was not emitted",
                $event_type
            );
        }
    }};
}

/// Asserts that no events were emitted.
#[macro_export]
macro_rules! assert_no_events {
    ($result:expr) => {
        if !$result.logs.is_empty() {
            panic!(
                "Expected no events but {} events were emitted",
                $result.logs.len()
            );
        }
    };
}

/// Asserts that gas used is within a range.
#[macro_export]
macro_rules! assert_gas_used {
    ($result:expr, $min:expr, $max:expr) => {
        let gas_used = $result.gas_used;
        if gas_used < $min || gas_used > $max {
            panic!(
                "Gas used {} is outside expected range [{}, {}]",
                gas_used, $min, $max
            );
        }
    };
}

/// Asserts that the return data matches expected value.
#[macro_export]
macro_rules! assert_return_data {
    ($result:expr, $expected:expr) => {
        let expected_bytes: Vec<u8> = $crate::to_return_bytes(&$expected);
        if $result.return_data != expected_bytes {
            panic!(
                "Return data mismatch: expected {:?}, got {:?}",
                expected_bytes, $result.return_data
            );
        }
    };
}

/// Asserts that storage is empty for a contract.
#[macro_export]
macro_rules! assert_storage_empty {
    ($env:expr, $contract:expr) => {
        if $env.has_storage(&$contract) {
            panic!("Expected empty storage for contract {:?}", $contract);
        }
    };
}

/// Asserts that a storage key does not exist.
#[macro_export]
macro_rules! assert_storage_none {
    ($env:expr, $contract:expr, $key:expr) => {{
        let storage_key = $crate::synor_vm::StorageKey::from_bytes($key.as_ref());
        if $env.get_storage(&$contract, &storage_key).is_some() {
            panic!("Expected no storage at key {:?}", $key);
        }
    }};
}

// Helper functions for the macros

/// Computes an event selector (first 4 bytes of blake3 hash).
pub fn event_selector(event_name: &str) -> [u8; 4] {
    let hash: [u8; 32] = blake3::hash(event_name.as_bytes()).into();
    [hash[0], hash[1], hash[2], hash[3]]
}

/// Converts a value to storage bytes.
pub fn to_storage_bytes<T: StorageEncodable>(value: &T) -> Vec<u8> {
    value.to_storage_bytes()
}

/// Converts a value to topic bytes (32 bytes).
pub fn to_topic_bytes<T: TopicEncodable>(value: &T) -> [u8; 32] {
    value.to_topic_bytes()
}

/// Converts a value to return bytes.
pub fn to_return_bytes<T: ReturnEncodable>(value: &T) -> Vec<u8> {
    value.to_return_bytes()
}

/// Trait for types that can be encoded as storage values.
pub trait StorageEncodable {
    fn to_storage_bytes(&self) -> Vec<u8>;
}

impl StorageEncodable for u64 {
    fn to_storage_bytes(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }
}

impl StorageEncodable for u128 {
    fn to_storage_bytes(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }
}

impl StorageEncodable for bool {
    fn to_storage_bytes(&self) -> Vec<u8> {
        vec![if *self { 1 } else { 0 }]
    }
}

impl StorageEncodable for [u8; 32] {
    fn to_storage_bytes(&self) -> Vec<u8> {
        self.to_vec()
    }
}

impl StorageEncodable for Vec<u8> {
    fn to_storage_bytes(&self) -> Vec<u8> {
        self.clone()
    }
}

impl StorageEncodable for &[u8] {
    fn to_storage_bytes(&self) -> Vec<u8> {
        self.to_vec()
    }
}

/// Trait for types that can be encoded as event topics.
pub trait TopicEncodable {
    fn to_topic_bytes(&self) -> [u8; 32];
}

impl TopicEncodable for [u8; 32] {
    fn to_topic_bytes(&self) -> [u8; 32] {
        *self
    }
}

impl TopicEncodable for u64 {
    fn to_topic_bytes(&self) -> [u8; 32] {
        let mut bytes = [0u8; 32];
        bytes[24..32].copy_from_slice(&self.to_be_bytes());
        bytes
    }
}

impl TopicEncodable for synor_types::Address {
    fn to_topic_bytes(&self) -> [u8; 32] {
        *self.payload()
    }
}

/// Trait for types that can be encoded as return data.
pub trait ReturnEncodable {
    fn to_return_bytes(&self) -> Vec<u8>;
}

impl ReturnEncodable for () {
    fn to_return_bytes(&self) -> Vec<u8> {
        Vec::new()
    }
}

impl ReturnEncodable for u64 {
    fn to_return_bytes(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }
}

impl ReturnEncodable for u128 {
    fn to_return_bytes(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }
}

impl ReturnEncodable for bool {
    fn to_return_bytes(&self) -> Vec<u8> {
        vec![if *self { 1 } else { 0 }]
    }
}

impl ReturnEncodable for Vec<u8> {
    fn to_return_bytes(&self) -> Vec<u8> {
        self.clone()
    }
}

impl ReturnEncodable for &[u8] {
    fn to_return_bytes(&self) -> Vec<u8> {
        self.to_vec()
    }
}

/// Assertion helper for checking multiple conditions.
pub struct AssertionChain<'a> {
    result: &'a ExecutionResult,
    errors: Vec<String>,
}

impl<'a> AssertionChain<'a> {
    /// Creates a new assertion chain for a result.
    pub fn new(result: &'a ExecutionResult) -> Self {
        AssertionChain {
            result,
            errors: Vec::new(),
        }
    }

    /// Asserts gas used is within range.
    pub fn gas_in_range(mut self, min: u64, max: u64) -> Self {
        if self.result.gas_used < min || self.result.gas_used > max {
            self.errors.push(format!(
                "Gas {} not in range [{}, {}]",
                self.result.gas_used, min, max
            ));
        }
        self
    }

    /// Asserts event count.
    pub fn event_count(mut self, expected: usize) -> Self {
        if self.result.logs.len() != expected {
            self.errors.push(format!(
                "Expected {} events, got {}",
                expected,
                self.result.logs.len()
            ));
        }
        self
    }

    /// Asserts return data length.
    pub fn return_data_len(mut self, expected: usize) -> Self {
        if self.result.return_data.len() != expected {
            self.errors.push(format!(
                "Expected return data length {}, got {}",
                expected,
                self.result.return_data.len()
            ));
        }
        self
    }

    /// Asserts storage change count.
    pub fn storage_changes(mut self, expected: usize) -> Self {
        if self.result.storage_changes.len() != expected {
            self.errors.push(format!(
                "Expected {} storage changes, got {}",
                expected,
                self.result.storage_changes.len()
            ));
        }
        self
    }

    /// Checks if all assertions passed.
    pub fn is_ok(&self) -> bool {
        self.errors.is_empty()
    }

    /// Finishes the chain and returns result.
    pub fn finish(self) -> Result<(), String> {
        if self.errors.is_empty() {
            Ok(())
        } else {
            Err(self.errors.join("\n"))
        }
    }

    /// Panics if any assertion failed.
    pub fn unwrap(self) {
        if !self.errors.is_empty() {
            panic!("Assertion failures:\n{}", self.errors.join("\n"));
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use synor_types::Hash256;
    use synor_vm::ContractLog;

    fn mock_result() -> ExecutionResult {
        ExecutionResult {
            return_data: vec![1, 2, 3, 4],
            gas_used: 1000,
            logs: vec![ContractLog {
                contract: synor_vm::ContractId::from_bytes([0x42; 32]),
                topics: vec![Hash256::from_bytes({
                    let mut bytes = [0u8; 32];
                    let selector = event_selector("Transfer");
                    bytes[..4].copy_from_slice(&selector);
                    bytes
                })],
                data: vec![],
            }],
            storage_changes: vec![],
            internal_calls: vec![],
        }
    }

    #[test]
    fn test_event_selector() {
        let sel1 = event_selector("Transfer");
        let sel2 = event_selector("Transfer");
        assert_eq!(sel1, sel2);

        let sel3 = event_selector("Approval");
        assert_ne!(sel1, sel3);
    }

    #[test]
    fn test_storage_encodable() {
        assert_eq!(to_storage_bytes(&42u64), 42u64.to_le_bytes().to_vec());
        assert_eq!(to_storage_bytes(&true), vec![1]);
        assert_eq!(to_storage_bytes(&false), vec![0]);
    }

    #[test]
    fn test_assertion_chain() {
        let result = mock_result();

        AssertionChain::new(&result)
            .gas_in_range(500, 1500)
            .event_count(1)
            .return_data_len(4)
            .unwrap();
    }

    #[test]
    #[should_panic(expected = "Gas")]
    fn test_assertion_chain_failure() {
        let result = mock_result();

        AssertionChain::new(&result)
            .gas_in_range(2000, 3000) // Will fail
            .unwrap();
    }
}
