//! IBC SDK Types

use serde::{Deserialize, Serialize};
use std::fmt;

/// IBC Height representation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Height {
    pub revision_number: u64,
    pub revision_height: u64,
}

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

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

    pub fn is_zero(&self) -> bool {
        self.revision_number == 0 && self.revision_height == 0
    }

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

/// Timestamp in nanoseconds since Unix epoch
pub type Timestamp = u64;

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

impl ChainId {
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }
}

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

impl Default for Version {
    fn default() -> Self {
        Self {
            identifier: "1".to_string(),
            features: vec!["ORDER_ORDERED".to_string(), "ORDER_UNORDERED".to_string()],
        }
    }
}

/// Commitment prefix for Merkle paths
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitmentPrefix {
    pub key_prefix: Vec<u8>,
}

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

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

/// Light client types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ClientType {
    Tendermint,
    SoloMachine,
    Localhost,
    Wasm,
}

/// Trust level configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustLevel {
    pub numerator: u64,
    pub denominator: u64,
}

impl Default for TrustLevel {
    fn default() -> Self {
        Self { numerator: 1, denominator: 3 }
    }
}

/// Light client state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientState {
    pub chain_id: String,
    pub trust_level: TrustLevel,
    pub trusting_period: u64,
    pub unbonding_period: u64,
    pub max_clock_drift: u64,
    pub latest_height: Height,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub frozen_height: Option<Height>,
}

/// Consensus state at a specific height
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsensusState {
    pub timestamp: Timestamp,
    #[serde(with = "base64_bytes")]
    pub root: Vec<u8>,
    #[serde(with = "base64_bytes")]
    pub next_validators_hash: Vec<u8>,
}

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

impl ConnectionId {
    pub fn new(sequence: u64) -> Self {
        Self(format!("connection-{}", sequence))
    }
}

/// Connection state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConnectionState {
    Uninitialized,
    Init,
    Tryopen,
    Open,
}

/// Connection counterparty
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionCounterparty {
    pub client_id: ClientId,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub connection_id: Option<ConnectionId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prefix: Option<CommitmentPrefix>,
}

/// Connection end
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionEnd {
    pub client_id: ClientId,
    pub versions: Vec<Version>,
    pub state: ConnectionState,
    pub counterparty: ConnectionCounterparty,
    pub delay_period: u64,
}

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

impl PortId {
    pub fn transfer() -> Self {
        Self("transfer".to_string())
    }
}

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

impl ChannelId {
    pub fn new(sequence: u64) -> Self {
        Self(format!("channel-{}", sequence))
    }
}

/// Channel ordering
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChannelOrder {
    Unordered,
    Ordered,
}

/// Channel state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChannelState {
    Uninitialized,
    Init,
    Tryopen,
    Open,
    Closed,
}

/// Channel counterparty
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelCounterparty {
    pub port_id: PortId,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channel_id: Option<ChannelId>,
}

/// Channel end
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Channel {
    pub state: ChannelState,
    pub ordering: ChannelOrder,
    pub counterparty: ChannelCounterparty,
    pub connection_hops: Vec<ConnectionId>,
    pub version: String,
}

/// IBC Packet
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Packet {
    pub sequence: u64,
    pub source_port: PortId,
    pub source_channel: ChannelId,
    pub dest_port: PortId,
    pub dest_channel: ChannelId,
    #[serde(with = "base64_bytes")]
    pub data: Vec<u8>,
    pub timeout_height: Height,
    pub timeout_timestamp: Timestamp,
}

/// Packet commitment hash
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PacketCommitment(#[serde(with = "base64_bytes")] pub Vec<u8>);

/// Packet acknowledgement
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Acknowledgement {
    Success { success: Vec<u8> },
    Error { error: String },
}

impl Acknowledgement {
    pub fn success(data: Vec<u8>) -> Self {
        Acknowledgement::Success { success: data }
    }

    pub fn error(msg: impl Into<String>) -> Self {
        Acknowledgement::Error { error: msg.into() }
    }

    pub fn is_success(&self) -> bool {
        matches!(self, Acknowledgement::Success { .. })
    }
}

/// Timeout information
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Timeout {
    pub height: Height,
    pub timestamp: Timestamp,
}

impl Timeout {
    pub fn from_height(height: u64) -> Self {
        Self { height: Height::from_height(height), timestamp: 0 }
    }

    pub fn from_timestamp(timestamp: Timestamp) -> Self {
        Self { height: Height::default(), timestamp }
    }
}

/// Transfer packet data (ICS-20)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FungibleTokenPacketData {
    pub denom: String,
    pub amount: String,
    pub sender: String,
    pub receiver: String,
    #[serde(default)]
    pub memo: String,
}

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

/// Swap state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SwapState {
    Pending,
    Locked,
    Completed,
    Refunded,
    Expired,
    Cancelled,
}

/// Swap asset types
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SwapAsset {
    Native { native: NativeAsset },
    Ics20 { ics20: Ics20Asset },
    Ics721 { ics721: Ics721Asset },
}

/// Native blockchain token
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NativeAsset {
    pub amount: u128,
}

/// ICS-20 fungible token
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ics20Asset {
    pub denom: String,
    pub amount: u128,
}

/// ICS-721 NFT
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ics721Asset {
    pub class_id: String,
    pub token_ids: Vec<String>,
}

/// Hashlock - hash of the secret
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Hashlock(#[serde(with = "base64_bytes")] pub Vec<u8>);

impl Hashlock {
    pub fn from_secret(secret: &[u8]) -> Self {
        use sha2::{Sha256, Digest};
        let hash = Sha256::digest(secret);
        Self(hash.to_vec())
    }

    pub fn verify(&self, preimage: &[u8]) -> bool {
        let computed = Self::from_secret(preimage);
        self.0 == computed.0
    }
}

/// Timelock - expiration time
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Timelock {
    pub expiry: Timestamp,
}

impl Timelock {
    pub fn from_duration(current: Timestamp, duration_secs: u64) -> Self {
        Self { expiry: current + duration_secs * 1_000_000_000 }
    }

    pub fn is_expired(&self, current: Timestamp) -> bool {
        current >= self.expiry
    }

    pub fn remaining_secs(&self, current: Timestamp) -> u64 {
        if current >= self.expiry { 0 } else { (self.expiry - current) / 1_000_000_000 }
    }
}

/// Hashed Time-Locked Contract
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Htlc {
    pub swap_id: SwapId,
    pub state: SwapState,
    pub initiator: String,
    pub responder: String,
    pub asset: SwapAsset,
    pub hashlock: Hashlock,
    pub timelock: Timelock,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub secret: Option<Vec<u8>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channel_id: Option<ChannelId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub port_id: Option<PortId>,
    pub created_at: Timestamp,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<Timestamp>,
}

/// Atomic swap between two chains
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AtomicSwap {
    pub swap_id: SwapId,
    pub initiator_htlc: Htlc,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub responder_htlc: Option<Htlc>,
    pub state: SwapState,
}

/// Swap packet actions
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SwapAction {
    Initiate,
    Respond,
    Claim,
    Refund,
}

/// Merkle proof
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MerkleProof {
    pub proofs: Vec<ProofOp>,
}

/// Proof operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProofOp {
    pub r#type: String,
    #[serde(with = "base64_bytes")]
    pub key: Vec<u8>,
    #[serde(with = "base64_bytes")]
    pub data: Vec<u8>,
}

/// Commitment proof with height
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitmentProof {
    pub proof: MerkleProof,
    pub height: Height,
}

/// IBC SDK configuration
#[derive(Debug, Clone)]
pub struct IbcConfig {
    pub api_key: String,
    pub endpoint: String,
    pub ws_endpoint: String,
    pub timeout: std::time::Duration,
    pub retries: u32,
    pub chain_id: String,
    pub debug: bool,
}

impl IbcConfig {
    pub fn new(api_key: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            endpoint: "https://ibc.synor.io/v1".to_string(),
            ws_endpoint: "wss://ibc.synor.io/v1/ws".to_string(),
            timeout: std::time::Duration::from_secs(30),
            retries: 3,
            chain_id: "synor-1".to_string(),
            debug: false,
        }
    }
}

/// IBC error
#[derive(Debug)]
pub struct IbcError {
    pub message: String,
    pub code: Option<String>,
    pub status: Option<u16>,
}

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

impl std::error::Error for IbcError {}

pub type IbcResult<T> = Result<T, IbcError>;

/// Base64 bytes serde helper
mod base64_bytes {
    use base64::{Engine as _, engine::general_purpose::STANDARD};
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer {
        serializer.serialize_str(&STANDARD.encode(bytes))
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
    where D: Deserializer<'de> {
        let s = String::deserialize(deserializer)?;
        STANDARD.decode(&s).map_err(serde::de::Error::custom)
    }
}
