//! IBC Channel Management
//!
//! Channels provide the transport layer for IBC packets. Each channel
//! is bound to a specific port and connection.
//!
//! # Channel Types
//!
//! - **Ordered**: Packets must be received in order (sequence numbers)
//! - **Unordered**: Packets can be received in any order
//!
//! # Channel Handshake (4-way)
//!
//! Similar to connection handshake:
//! 1. ChanOpenInit
//! 2. ChanOpenTry
//! 3. ChanOpenAck
//! 4. ChanOpenConfirm

use crate::connection::ConnectionId;
use crate::error::{IbcError, IbcResult};
use crate::types::Height;
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

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

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

    /// Transfer port
    pub fn transfer() -> Self {
        Self("transfer".to_string())
    }

    /// Interchain accounts host port
    pub fn ica_host() -> Self {
        Self("icahost".to_string())
    }

    /// Interchain accounts controller port
    pub fn ica_controller(owner: &str) -> Self {
        Self(format!("icacontroller-{}", owner))
    }

    /// Validate port ID
    pub fn validate(&self) -> IbcResult<()> {
        if self.0.is_empty() {
            return Err(IbcError::InvalidIdentifier("port ID cannot be empty".to_string()));
        }

        if self.0.len() > 128 {
            return Err(IbcError::InvalidIdentifier("port ID too long".to_string()));
        }

        // Alphanumeric and limited special characters
        if !self.0.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.') {
            return Err(IbcError::InvalidIdentifier(
                "port ID contains invalid characters".to_string(),
            ));
        }

        Ok(())
    }
}

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

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

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

    /// Parse sequence from ID
    pub fn sequence(&self) -> Option<u64> {
        self.0.strip_prefix("channel-")?.parse().ok()
    }
}

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

/// Channel ordering
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChannelOrder {
    /// Packets can arrive in any order
    Unordered,
    /// Packets must arrive in sequence order
    Ordered,
}

impl ChannelOrder {
    /// Get order name
    pub fn as_str(&self) -> &'static str {
        match self {
            ChannelOrder::Unordered => "ORDER_UNORDERED",
            ChannelOrder::Ordered => "ORDER_ORDERED",
        }
    }
}

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

/// Channel state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChannelState {
    /// Uninitialized (default)
    Uninitialized,
    /// Init: ChanOpenInit sent
    Init,
    /// TryOpen: ChanOpenTry sent
    TryOpen,
    /// Open: Channel fully established
    Open,
    /// Closed: Channel closed
    Closed,
}

impl ChannelState {
    /// Get state name
    pub fn as_str(&self) -> &'static str {
        match self {
            ChannelState::Uninitialized => "UNINITIALIZED",
            ChannelState::Init => "INIT",
            ChannelState::TryOpen => "TRYOPEN",
            ChannelState::Open => "OPEN",
            ChannelState::Closed => "CLOSED",
        }
    }
}

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

/// Channel counterparty
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelCounterparty {
    /// Counterparty port ID
    pub port_id: PortId,
    /// Counterparty channel ID
    pub channel_id: Option<ChannelId>,
}

impl ChannelCounterparty {
    /// Create a new counterparty
    pub fn new(port_id: PortId, channel_id: Option<ChannelId>) -> Self {
        Self { port_id, channel_id }
    }
}

/// Channel end state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Channel {
    /// Channel state
    pub state: ChannelState,
    /// Channel ordering
    pub ordering: ChannelOrder,
    /// Counterparty information
    pub counterparty: ChannelCounterparty,
    /// Connection hops (usually single connection)
    pub connection_hops: Vec<ConnectionId>,
    /// Channel version (app-specific)
    pub version: String,
}

impl Channel {
    /// Create a new channel in Init state
    pub fn new_init(
        ordering: ChannelOrder,
        counterparty: ChannelCounterparty,
        connection_hops: Vec<ConnectionId>,
        version: String,
    ) -> Self {
        Self {
            state: ChannelState::Init,
            ordering,
            counterparty,
            connection_hops,
            version,
        }
    }

    /// Create a new channel in TryOpen state
    pub fn new_try_open(
        ordering: ChannelOrder,
        counterparty: ChannelCounterparty,
        connection_hops: Vec<ConnectionId>,
        version: String,
    ) -> Self {
        Self {
            state: ChannelState::TryOpen,
            ordering,
            counterparty,
            connection_hops,
            version,
        }
    }

    /// Check if channel is open
    pub fn is_open(&self) -> bool {
        self.state == ChannelState::Open
    }

    /// Check if channel is closed
    pub fn is_closed(&self) -> bool {
        self.state == ChannelState::Closed
    }

    /// Set state to TryOpen
    pub fn set_try_open(&mut self) {
        self.state = ChannelState::TryOpen;
    }

    /// Set state to Open
    pub fn set_open(&mut self) {
        self.state = ChannelState::Open;
    }

    /// Set state to Closed
    pub fn set_closed(&mut self) {
        self.state = ChannelState::Closed;
    }

    /// Set counterparty channel ID
    pub fn set_counterparty_channel_id(&mut self, id: ChannelId) {
        self.counterparty.channel_id = Some(id);
    }

    /// Validate the channel
    pub fn validate(&self) -> IbcResult<()> {
        if self.connection_hops.is_empty() {
            return Err(IbcError::InvalidChannelState {
                expected: "at least one connection hop".to_string(),
                actual: "none".to_string(),
            });
        }

        self.counterparty.port_id.validate()?;

        Ok(())
    }
}

/// Channel key (port, channel)
pub type ChannelKey = (PortId, ChannelId);

/// Sequence counters for a channel
#[derive(Debug, Clone, Default)]
pub struct ChannelSequences {
    /// Next sequence to send
    pub next_send: u64,
    /// Next sequence to receive
    pub next_recv: u64,
    /// Next sequence to acknowledge
    pub next_ack: u64,
}

/// Channel manager
pub struct ChannelManager {
    /// Channels by (port, channel)
    channels: HashMap<ChannelKey, Channel>,
    /// Sequence counters by (port, channel)
    sequences: HashMap<ChannelKey, ChannelSequences>,
    /// Port bindings (port -> module name)
    port_bindings: HashMap<PortId, String>,
    /// Next channel sequence per port
    next_channel_sequence: HashMap<PortId, u64>,
}

impl ChannelManager {
    /// Create a new channel manager
    pub fn new() -> Self {
        Self {
            channels: HashMap::new(),
            sequences: HashMap::new(),
            port_bindings: HashMap::new(),
            next_channel_sequence: HashMap::new(),
        }
    }

    /// Bind a port to a module
    pub fn bind_port(&mut self, port_id: PortId, module: String) -> IbcResult<()> {
        port_id.validate()?;

        if self.port_bindings.contains_key(&port_id) {
            return Err(IbcError::PortAlreadyBound(port_id.to_string()));
        }

        self.port_bindings.insert(port_id, module);
        Ok(())
    }

    /// Release a port binding
    pub fn release_port(&mut self, port_id: &PortId) -> IbcResult<()> {
        if self.port_bindings.remove(port_id).is_none() {
            return Err(IbcError::PortNotBound(port_id.to_string()));
        }
        Ok(())
    }

    /// Check if port is bound
    pub fn is_port_bound(&self, port_id: &PortId) -> bool {
        self.port_bindings.contains_key(port_id)
    }

    /// Generate next channel ID for a port
    fn next_channel_id(&mut self, port_id: &PortId) -> ChannelId {
        let seq = self.next_channel_sequence.entry(port_id.clone()).or_insert(0);
        let id = ChannelId::new(*seq);
        *seq += 1;
        id
    }

    /// Get a channel
    pub fn get_channel(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<&Channel> {
        self.channels
            .get(&(port_id.clone(), channel_id.clone()))
            .ok_or_else(|| IbcError::ChannelNotFound {
                port: port_id.to_string(),
                channel: channel_id.to_string(),
            })
    }

    /// Get mutable channel
    fn get_channel_mut(
        &mut self,
        port_id: &PortId,
        channel_id: &ChannelId,
    ) -> IbcResult<&mut Channel> {
        self.channels
            .get_mut(&(port_id.clone(), channel_id.clone()))
            .ok_or_else(|| IbcError::ChannelNotFound {
                port: port_id.to_string(),
                channel: channel_id.to_string(),
            })
    }

    /// Get sequences for a channel
    pub fn get_sequences(&self, port_id: &PortId, channel_id: &ChannelId) -> ChannelSequences {
        self.sequences
            .get(&(port_id.clone(), channel_id.clone()))
            .cloned()
            .unwrap_or_default()
    }

    /// Increment send sequence and return previous value
    pub fn increment_send_sequence(
        &mut self,
        port_id: &PortId,
        channel_id: &ChannelId,
    ) -> u64 {
        let key = (port_id.clone(), channel_id.clone());
        let seq = self.sequences.entry(key).or_default();
        let current = seq.next_send;
        seq.next_send += 1;
        current
    }

    /// Increment receive sequence
    pub fn increment_recv_sequence(&mut self, port_id: &PortId, channel_id: &ChannelId) {
        let key = (port_id.clone(), channel_id.clone());
        let seq = self.sequences.entry(key).or_default();
        seq.next_recv += 1;
    }

    /// Increment ack sequence
    pub fn increment_ack_sequence(&mut self, port_id: &PortId, channel_id: &ChannelId) {
        let key = (port_id.clone(), channel_id.clone());
        let seq = self.sequences.entry(key).or_default();
        seq.next_ack += 1;
    }

    /// Channel Open Init
    pub fn chan_open_init(
        &mut self,
        port_id: PortId,
        ordering: ChannelOrder,
        connection_hops: Vec<ConnectionId>,
        counterparty_port: PortId,
        version: String,
    ) -> IbcResult<ChannelId> {
        // Validate port
        port_id.validate()?;
        counterparty_port.validate()?;

        // Check port is bound
        if !self.is_port_bound(&port_id) {
            return Err(IbcError::PortNotBound(port_id.to_string()));
        }

        // Generate channel ID
        let channel_id = self.next_channel_id(&port_id);

        // Create channel
        let counterparty = ChannelCounterparty::new(counterparty_port, None);
        let channel = Channel::new_init(ordering, counterparty, connection_hops, version);
        channel.validate()?;

        // Store channel
        let key = (port_id.clone(), channel_id.clone());
        self.channels.insert(key.clone(), channel);
        self.sequences.insert(key, ChannelSequences {
            next_send: 1,
            next_recv: 1,
            next_ack: 1,
        });

        tracing::info!(
            port = %port_id,
            channel = %channel_id,
            "Channel init complete"
        );

        Ok(channel_id)
    }

    /// Channel Open Try
    pub fn chan_open_try(
        &mut self,
        port_id: PortId,
        ordering: ChannelOrder,
        connection_hops: Vec<ConnectionId>,
        counterparty_port: PortId,
        counterparty_channel: ChannelId,
        version: String,
        _counterparty_version: String,
        proof_init: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<ChannelId> {
        // Validate
        port_id.validate()?;

        // Check port is bound
        if !self.is_port_bound(&port_id) {
            return Err(IbcError::PortNotBound(port_id.to_string()));
        }

        // Verify proof (simplified)
        if proof_init.is_empty() {
            return Err(IbcError::MissingProof("proof_init required".to_string()));
        }

        // Generate channel ID
        let channel_id = self.next_channel_id(&port_id);

        // Create channel
        let counterparty = ChannelCounterparty::new(counterparty_port, Some(counterparty_channel));
        let channel = Channel::new_try_open(ordering, counterparty, connection_hops, version);
        channel.validate()?;

        // Store channel
        let key = (port_id.clone(), channel_id.clone());
        self.channels.insert(key.clone(), channel);
        self.sequences.insert(key, ChannelSequences {
            next_send: 1,
            next_recv: 1,
            next_ack: 1,
        });

        tracing::info!(
            port = %port_id,
            channel = %channel_id,
            "Channel try open complete"
        );

        Ok(channel_id)
    }

    /// Channel Open Ack
    pub fn chan_open_ack(
        &mut self,
        port_id: &PortId,
        channel_id: &ChannelId,
        counterparty_channel: ChannelId,
        counterparty_version: String,
        proof_try: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<()> {
        let channel = self.get_channel_mut(port_id, channel_id)?;

        // Verify state
        if channel.state != ChannelState::Init {
            return Err(IbcError::InvalidChannelState {
                expected: ChannelState::Init.to_string(),
                actual: channel.state.to_string(),
            });
        }

        // Verify proof (simplified)
        if proof_try.is_empty() {
            return Err(IbcError::MissingProof("proof_try required".to_string()));
        }

        // Update channel
        channel.set_counterparty_channel_id(counterparty_channel);
        channel.version = counterparty_version;
        channel.set_open();

        tracing::info!(
            port = %port_id,
            channel = %channel_id,
            "Channel ack complete - channel OPEN"
        );

        Ok(())
    }

    /// Channel Open Confirm
    pub fn chan_open_confirm(
        &mut self,
        port_id: &PortId,
        channel_id: &ChannelId,
        proof_ack: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<()> {
        let channel = self.get_channel_mut(port_id, channel_id)?;

        // Verify state
        if channel.state != ChannelState::TryOpen {
            return Err(IbcError::InvalidChannelState {
                expected: ChannelState::TryOpen.to_string(),
                actual: channel.state.to_string(),
            });
        }

        // Verify proof (simplified)
        if proof_ack.is_empty() {
            return Err(IbcError::MissingProof("proof_ack required".to_string()));
        }

        // Update state
        channel.set_open();

        tracing::info!(
            port = %port_id,
            channel = %channel_id,
            "Channel confirm complete - channel OPEN"
        );

        Ok(())
    }

    /// Channel Close Init
    pub fn chan_close_init(&mut self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<()> {
        let channel = self.get_channel_mut(port_id, channel_id)?;

        if !channel.is_open() {
            return Err(IbcError::InvalidChannelState {
                expected: ChannelState::Open.to_string(),
                actual: channel.state.to_string(),
            });
        }

        channel.set_closed();

        tracing::info!(
            port = %port_id,
            channel = %channel_id,
            "Channel close init - channel CLOSED"
        );

        Ok(())
    }

    /// Channel Close Confirm
    pub fn chan_close_confirm(
        &mut self,
        port_id: &PortId,
        channel_id: &ChannelId,
        proof_init: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<()> {
        let channel = self.get_channel_mut(port_id, channel_id)?;

        if channel.is_closed() {
            return Ok(()); // Already closed
        }

        // Verify proof (simplified)
        if proof_init.is_empty() {
            return Err(IbcError::MissingProof("proof_init required".to_string()));
        }

        channel.set_closed();

        tracing::info!(
            port = %port_id,
            channel = %channel_id,
            "Channel close confirm - channel CLOSED"
        );

        Ok(())
    }

    /// Get all channels
    pub fn all_channels(&self) -> impl Iterator<Item = (&ChannelKey, &Channel)> {
        self.channels.iter()
    }

    /// Get channels for a port
    pub fn port_channels(&self, port_id: &PortId) -> Vec<(ChannelId, Channel)> {
        self.channels
            .iter()
            .filter(|((p, _), _)| p == port_id)
            .map(|((_, c), ch)| (c.clone(), ch.clone()))
            .collect()
    }
}

impl Default for ChannelManager {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_port_id() {
        let port = PortId::transfer();
        assert_eq!(port.to_string(), "transfer");
        assert!(port.validate().is_ok());

        let invalid = PortId::new("");
        assert!(invalid.validate().is_err());
    }

    #[test]
    fn test_channel_id() {
        let id = ChannelId::new(5);
        assert_eq!(id.to_string(), "channel-5");
        assert_eq!(id.sequence(), Some(5));
    }

    #[test]
    fn test_port_binding() {
        let mut manager = ChannelManager::new();
        let port = PortId::transfer();

        assert!(manager.bind_port(port.clone(), "transfer".to_string()).is_ok());
        assert!(manager.is_port_bound(&port));

        // Can't bind same port twice
        assert!(manager.bind_port(port.clone(), "other".to_string()).is_err());

        // Release and rebind
        assert!(manager.release_port(&port).is_ok());
        assert!(!manager.is_port_bound(&port));
    }

    #[test]
    fn test_channel_handshake() {
        let mut manager_a = ChannelManager::new();
        let mut manager_b = ChannelManager::new();

        let port = PortId::transfer();
        let conn = ConnectionId::new(0);

        // Bind ports
        manager_a.bind_port(port.clone(), "transfer".to_string()).unwrap();
        manager_b.bind_port(port.clone(), "transfer".to_string()).unwrap();

        // Chain A: Init
        let chan_a = manager_a
            .chan_open_init(
                port.clone(),
                ChannelOrder::Unordered,
                vec![conn.clone()],
                port.clone(),
                "ics20-1".to_string(),
            )
            .unwrap();

        // Chain B: TryOpen
        let chan_b = manager_b
            .chan_open_try(
                port.clone(),
                ChannelOrder::Unordered,
                vec![conn.clone()],
                port.clone(),
                chan_a.clone(),
                "ics20-1".to_string(),
                "ics20-1".to_string(),
                vec![1, 2, 3],
                Height::from_height(100),
            )
            .unwrap();

        // Chain A: Ack
        manager_a
            .chan_open_ack(
                &port,
                &chan_a,
                chan_b.clone(),
                "ics20-1".to_string(),
                vec![1, 2, 3],
                Height::from_height(101),
            )
            .unwrap();

        assert!(manager_a.get_channel(&port, &chan_a).unwrap().is_open());

        // Chain B: Confirm
        manager_b
            .chan_open_confirm(&port, &chan_b, vec![1, 2, 3], Height::from_height(102))
            .unwrap();

        assert!(manager_b.get_channel(&port, &chan_b).unwrap().is_open());
    }

    #[test]
    fn test_sequence_management() {
        let mut manager = ChannelManager::new();
        let port = PortId::transfer();
        let conn = ConnectionId::new(0);

        manager.bind_port(port.clone(), "transfer".to_string()).unwrap();

        let channel = manager
            .chan_open_init(
                port.clone(),
                ChannelOrder::Ordered,
                vec![conn],
                port.clone(),
                "ics20-1".to_string(),
            )
            .unwrap();

        // Initial sequences should be 1
        let seq = manager.get_sequences(&port, &channel);
        assert_eq!(seq.next_send, 1);
        assert_eq!(seq.next_recv, 1);

        // Increment send
        let send_seq = manager.increment_send_sequence(&port, &channel);
        assert_eq!(send_seq, 1);

        let seq = manager.get_sequences(&port, &channel);
        assert_eq!(seq.next_send, 2);
    }
}
