//! IBC error types

use thiserror::Error;

/// Result type for IBC operations
pub type IbcResult<T> = Result<T, IbcError>;

/// IBC error types
#[derive(Debug, Error)]
pub enum IbcError {
    // Client errors
    #[error("Client not found: {0}")]
    ClientNotFound(String),

    #[error("Client already exists: {0}")]
    ClientAlreadyExists(String),

    #[error("Invalid client state: {0}")]
    InvalidClientState(String),

    #[error("Client frozen: {0}")]
    ClientFrozen(String),

    #[error("Client expired: {0}")]
    ClientExpired(String),

    // Connection errors
    #[error("Connection not found: {0}")]
    ConnectionNotFound(String),

    #[error("Connection already exists: {0}")]
    ConnectionAlreadyExists(String),

    #[error("Invalid connection state: expected {expected}, got {actual}")]
    InvalidConnectionState { expected: String, actual: String },

    #[error("Connection version mismatch: {0}")]
    ConnectionVersionMismatch(String),

    // Channel errors
    #[error("Channel not found: port={port}, channel={channel}")]
    ChannelNotFound { port: String, channel: String },

    #[error("Channel already exists: port={port}, channel={channel}")]
    ChannelAlreadyExists { port: String, channel: String },

    #[error("Invalid channel state: expected {expected}, got {actual}")]
    InvalidChannelState { expected: String, actual: String },

    #[error("Port not bound: {0}")]
    PortNotBound(String),

    #[error("Port already bound: {0}")]
    PortAlreadyBound(String),

    // Packet errors
    #[error("Packet timeout: height={height}, timestamp={timestamp}")]
    PacketTimeout { height: u64, timestamp: u64 },

    #[error("Packet already received: sequence={0}")]
    PacketAlreadyReceived(u64),

    #[error("Packet not found: sequence={0}")]
    PacketNotFound(u64),

    #[error("Invalid packet sequence: expected {expected}, got {actual}")]
    InvalidPacketSequence { expected: u64, actual: u64 },

    #[error("Packet data too large: {size} bytes (max {max})")]
    PacketDataTooLarge { size: usize, max: usize },

    #[error("Invalid packet data: {0}")]
    InvalidPacketData(String),

    #[error("Invalid acknowledgement: {0}")]
    InvalidAcknowledgement(String),

    // Swap errors
    #[error("Swap not found: {0}")]
    SwapNotFound(String),

    #[error("Invalid swap state: expected {expected}, got {actual}")]
    InvalidSwapState { expected: String, actual: String },

    #[error("Swap timeout expired: {0}")]
    SwapTimeoutExpired(String),

    #[error("Invalid secret: {0}")]
    InvalidSecret(String),

    // Proof errors
    #[error("Proof verification failed: {0}")]
    ProofVerificationFailed(String),

    #[error("Invalid proof: {0}")]
    InvalidProof(String),

    #[error("Invalid proof height: expected {expected}, got {actual}")]
    InvalidProofHeight { expected: u64, actual: u64 },

    #[error("Missing proof: {0}")]
    MissingProof(String),

    // Commitment errors
    #[error("Commitment not found: {0}")]
    CommitmentNotFound(String),

    #[error("Invalid commitment: {0}")]
    InvalidCommitment(String),

    // Handshake errors
    #[error("Handshake error: {0}")]
    HandshakeError(String),

    #[error("Invalid counterparty: {0}")]
    InvalidCounterparty(String),

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

    #[error("Deserialization error: {0}")]
    DeserializationError(String),

    // General errors
    #[error("Invalid identifier: {0}")]
    InvalidIdentifier(String),

    #[error("Unauthorized: {0}")]
    Unauthorized(String),

    #[error("Internal error: {0}")]
    Internal(String),
}

impl From<serde_json::Error> for IbcError {
    fn from(err: serde_json::Error) -> Self {
        IbcError::SerializationError(err.to_string())
    }
}

impl From<std::io::Error> for IbcError {
    fn from(err: std::io::Error) -> Self {
        IbcError::Internal(err.to_string())
    }
}

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

    #[test]
    fn test_error_display() {
        let err = IbcError::ClientNotFound("client-1".to_string());
        assert!(err.to_string().contains("client-1"));

        let err = IbcError::PacketTimeout {
            height: 100,
            timestamp: 1000,
        };
        assert!(err.to_string().contains("100"));
    }
}
