//! Common IBC types and identifiers

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use std::fmt;

/// Height representation for IBC
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Height {
    /// Revision number (for hard forks)
    pub revision_number: u64,
    /// Block height
    pub revision_height: u64,
}

impl Height {
    /// Create a new height
    pub fn new(revision_number: u64, revision_height: u64) -> Self {
        Self {
            revision_number,
            revision_height,
        }
    }

    /// Create height with revision 0
    pub fn from_height(height: u64) -> Self {
        Self {
            revision_number: 0,
            revision_height: height,
        }
    }

    /// Check if this height is zero
    pub fn is_zero(&self) -> bool {
        self.revision_number == 0 && self.revision_height == 0
    }

    /// Increment the height
    pub fn increment(&self) -> Self {
        Self {
            revision_number: self.revision_number,
            revision_height: self.revision_height + 1,
        }
    }

    /// Decrement the height
    pub fn decrement(&self) -> Option<Self> {
        if self.revision_height == 0 {
            None
        } else {
            Some(Self {
                revision_number: self.revision_number,
                revision_height: self.revision_height - 1,
            })
        }
    }
}

impl Default for Height {
    fn default() -> Self {
        Self {
            revision_number: 0,
            revision_height: 1,
        }
    }
}

impl fmt::Display for Height {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}-{}", self.revision_number, self.revision_height)
    }
}

/// Timestamp in nanoseconds since Unix epoch
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
    Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
#[derive(Default)]
pub struct Timestamp(pub u64);

impl Timestamp {
    /// Create from nanoseconds
    pub fn from_nanoseconds(nanos: u64) -> Self {
        Self(nanos)
    }

    /// Create from seconds
    pub fn from_seconds(secs: u64) -> Self {
        Self(secs * 1_000_000_000)
    }

    /// Get nanoseconds
    pub fn nanoseconds(&self) -> u64 {
        self.0
    }

    /// Get seconds
    pub fn seconds(&self) -> u64 {
        self.0 / 1_000_000_000
    }

    /// Check if zero (disabled timeout)
    pub fn is_zero(&self) -> bool {
        self.0 == 0
    }

    /// Get current timestamp
    pub fn now() -> Self {
        use std::time::{SystemTime, UNIX_EPOCH};
        let duration = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards");
        Self(duration.as_nanos() as u64)
    }
}


impl fmt::Display for Timestamp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// IBC version with features
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Version {
    /// Version identifier
    pub identifier: String,
    /// Supported features
    pub features: Vec<String>,
}

impl Version {
    /// Create a new version
    pub fn new(identifier: impl Into<String>, features: Vec<String>) -> Self {
        Self {
            identifier: identifier.into(),
            features,
        }
    }

    /// Default IBC version for connections
    pub fn default_connection() -> Self {
        Self {
            identifier: "1".to_string(),
            features: vec!["ORDER_ORDERED".to_string(), "ORDER_UNORDERED".to_string()],
        }
    }

    /// Check if a feature is supported
    pub fn has_feature(&self, feature: &str) -> bool {
        self.features.iter().any(|f| f == feature)
    }
}

impl Default for Version {
    fn default() -> Self {
        Self::default_connection()
    }
}

/// Prefix for commitment paths
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommitmentPrefix {
    /// Key prefix bytes
    pub key_prefix: Vec<u8>,
}

impl CommitmentPrefix {
    /// Create a new prefix
    pub fn new(prefix: impl Into<Vec<u8>>) -> Self {
        Self {
            key_prefix: prefix.into(),
        }
    }

    /// Get as string (if valid UTF-8)
    pub fn as_str(&self) -> Option<&str> {
        std::str::from_utf8(&self.key_prefix).ok()
    }
}

impl Default for CommitmentPrefix {
    fn default() -> Self {
        Self {
            key_prefix: b"ibc".to_vec(),
        }
    }
}

/// Signer address
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Signer(pub String);

impl Signer {
    /// Create a new signer
    pub fn new(address: impl Into<String>) -> Self {
        Self(address.into())
    }

    /// Get address string
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for Signer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Chain identifier
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ChainId(pub String);

impl ChainId {
    /// Create a new chain ID
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }

    /// Synor mainnet chain ID
    pub fn synor_mainnet() -> Self {
        Self("synor-1".to_string())
    }

    /// Synor testnet chain ID
    pub fn synor_testnet() -> Self {
        Self("synor-testnet-1".to_string())
    }

    /// Get revision number from chain ID
    pub fn revision_number(&self) -> u64 {
        // Parse revision from format "chain-name-N"
        self.0
            .rsplit('-')
            .next()
            .and_then(|s| s.parse().ok())
            .unwrap_or(0)
    }
}

impl fmt::Display for ChainId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

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

    #[test]
    fn test_height() {
        let h = Height::new(1, 100);
        assert_eq!(h.revision_number, 1);
        assert_eq!(h.revision_height, 100);

        let h2 = h.increment();
        assert_eq!(h2.revision_height, 101);

        let h3 = h2.decrement().unwrap();
        assert_eq!(h3.revision_height, 100);
    }

    #[test]
    fn test_timestamp() {
        let ts = Timestamp::from_seconds(1000);
        assert_eq!(ts.nanoseconds(), 1_000_000_000_000);
        assert_eq!(ts.seconds(), 1000);
    }

    #[test]
    fn test_version() {
        let v = Version::default_connection();
        assert!(v.has_feature("ORDER_ORDERED"));
        assert!(v.has_feature("ORDER_UNORDERED"));
        assert!(!v.has_feature("UNKNOWN"));
    }

    #[test]
    fn test_chain_id_revision() {
        let chain = ChainId::new("cosmos-hub-4");
        assert_eq!(chain.revision_number(), 4);

        let chain = ChainId::new("synor-testnet-1");
        assert_eq!(chain.revision_number(), 1);
    }
}
