//! IBC Connection Management
//!
//! Connections are the foundational layer of IBC, establishing a verified
//! link between two chains through their light clients.
//!
//! # Connection Handshake (4-way)
//!
//! ```text
//! Chain A                                      Chain B
//!    │                                            │
//!    │──── ConnOpenInit ────────────────────────>│
//!    │     (Propose connection)                   │
//!    │                                            │
//!    │<─────────────────── ConnOpenTry ──────────│
//!    │                     (Accept & propose)     │
//!    │                                            │
//!    │──── ConnOpenAck ─────────────────────────>│
//!    │     (Acknowledge)                          │
//!    │                                            │
//!    │<─────────────────── ConnOpenConfirm ──────│
//!    │                     (Confirm)              │
//!    │                                            │
//!    │           CONNECTION OPEN                  │
//! ```

use crate::client::ClientId;
use crate::error::{IbcError, IbcResult};
use crate::types::{CommitmentPrefix, Height, Version};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

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

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

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

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

/// Connection state in the handshake lifecycle
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionState {
    /// Uninitialized (default)
    Uninitialized,
    /// Init: Chain A has sent ConnOpenInit
    Init,
    /// TryOpen: Chain B has sent ConnOpenTry
    TryOpen,
    /// Open: Connection is fully established
    Open,
}

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

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

/// Counterparty connection information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Counterparty {
    /// Counterparty client ID
    pub client_id: ClientId,
    /// Counterparty connection ID (may be empty during init)
    pub connection_id: Option<ConnectionId>,
    /// Commitment prefix for the counterparty's key-value store
    pub prefix: CommitmentPrefix,
}

impl Counterparty {
    /// Create a new counterparty
    pub fn new(
        client_id: ClientId,
        connection_id: Option<ConnectionId>,
        prefix: CommitmentPrefix,
    ) -> Self {
        Self {
            client_id,
            connection_id,
            prefix,
        }
    }

    /// Validate the counterparty
    pub fn validate(&self) -> IbcResult<()> {
        if self.client_id.0.is_empty() {
            return Err(IbcError::InvalidCounterparty(
                "client ID cannot be empty".to_string(),
            ));
        }
        Ok(())
    }
}

/// Connection end state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionEnd {
    /// Connection state
    pub state: ConnectionState,
    /// Client ID associated with this connection
    pub client_id: ClientId,
    /// Counterparty chain information
    pub counterparty: Counterparty,
    /// Supported versions
    pub versions: Vec<Version>,
    /// Delay period for packet verification (in nanoseconds)
    pub delay_period: u64,
}

impl ConnectionEnd {
    /// Create a new connection end in Init state
    pub fn new_init(
        client_id: ClientId,
        counterparty: Counterparty,
        versions: Vec<Version>,
        delay_period: u64,
    ) -> Self {
        Self {
            state: ConnectionState::Init,
            client_id,
            counterparty,
            versions,
            delay_period,
        }
    }

    /// Create a new connection end in TryOpen state
    pub fn new_try_open(
        client_id: ClientId,
        counterparty: Counterparty,
        versions: Vec<Version>,
        delay_period: u64,
    ) -> Self {
        Self {
            state: ConnectionState::TryOpen,
            client_id,
            counterparty,
            versions,
            delay_period,
        }
    }

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

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

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

    /// Set counterparty connection ID
    pub fn set_counterparty_connection_id(&mut self, id: ConnectionId) {
        self.counterparty.connection_id = Some(id);
    }

    /// Validate the connection end
    pub fn validate(&self) -> IbcResult<()> {
        if self.client_id.0.is_empty() {
            return Err(IbcError::InvalidConnectionState {
                expected: "non-empty client ID".to_string(),
                actual: "empty".to_string(),
            });
        }

        self.counterparty.validate()?;

        if self.versions.is_empty() {
            return Err(IbcError::ConnectionVersionMismatch(
                "at least one version required".to_string(),
            ));
        }

        Ok(())
    }
}

/// Connection manager
pub struct ConnectionManager {
    /// Connections by ID
    connections: HashMap<ConnectionId, ConnectionEnd>,
    /// Client to connection mapping
    client_connections: HashMap<ClientId, Vec<ConnectionId>>,
    /// Next connection sequence
    next_connection_sequence: u64,
}

impl ConnectionManager {
    /// Create a new connection manager
    pub fn new() -> Self {
        Self {
            connections: HashMap::new(),
            client_connections: HashMap::new(),
            next_connection_sequence: 0,
        }
    }

    /// Generate next connection ID
    fn next_connection_id(&mut self) -> ConnectionId {
        let id = ConnectionId::new(self.next_connection_sequence);
        self.next_connection_sequence += 1;
        id
    }

    /// Get a connection by ID
    pub fn get_connection(&self, conn_id: &ConnectionId) -> IbcResult<&ConnectionEnd> {
        self.connections
            .get(conn_id)
            .ok_or_else(|| IbcError::ConnectionNotFound(conn_id.to_string()))
    }

    /// Get mutable connection
    fn get_connection_mut(&mut self, conn_id: &ConnectionId) -> IbcResult<&mut ConnectionEnd> {
        self.connections
            .get_mut(conn_id)
            .ok_or_else(|| IbcError::ConnectionNotFound(conn_id.to_string()))
    }

    /// Get all connections for a client
    pub fn get_client_connections(&self, client_id: &ClientId) -> Vec<ConnectionId> {
        self.client_connections
            .get(client_id)
            .cloned()
            .unwrap_or_default()
    }

    /// Connection Open Init - Start the handshake
    ///
    /// Chain A calls this to propose a connection to Chain B
    pub fn conn_open_init(
        &mut self,
        client_id: ClientId,
        counterparty: Counterparty,
        version: Option<Version>,
        delay_period: u64,
    ) -> IbcResult<ConnectionId> {
        // Validate counterparty
        counterparty.validate()?;

        // Generate connection ID
        let conn_id = self.next_connection_id();

        // Default version if not specified
        let versions = match version {
            Some(v) => vec![v],
            None => vec![Version::default_connection()],
        };

        // Create connection end
        let connection = ConnectionEnd::new_init(client_id.clone(), counterparty, versions, delay_period);
        connection.validate()?;

        // Store connection
        self.connections.insert(conn_id.clone(), connection);
        self.client_connections
            .entry(client_id)
            .or_default()
            .push(conn_id.clone());

        tracing::info!(
            connection_id = %conn_id,
            "Connection init complete"
        );

        Ok(conn_id)
    }

    /// Connection Open Try - Respond to Init
    ///
    /// Chain B calls this after receiving proof of Chain A's ConnOpenInit
    pub fn conn_open_try(
        &mut self,
        client_id: ClientId,
        counterparty: Counterparty,
        counterparty_versions: Vec<Version>,
        delay_period: u64,
        proof_init: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<ConnectionId> {
        // Validate counterparty
        counterparty.validate()?;

        // Verify we have the counterparty's connection ID (clone to avoid borrow issues)
        let counterparty_conn_id = counterparty
            .connection_id
            .clone()
            .ok_or_else(|| {
                IbcError::InvalidCounterparty("counterparty connection ID required".to_string())
            })?;

        // Verify proof of init (simplified - real implementation verifies Merkle proof)
        if proof_init.is_empty() {
            return Err(IbcError::MissingProof("proof_init required".to_string()));
        }

        // Select compatible version
        let versions = self.pick_version(&counterparty_versions)?;

        // Generate our connection ID
        let conn_id = self.next_connection_id();

        // Create connection end in TryOpen state
        let connection =
            ConnectionEnd::new_try_open(client_id.clone(), counterparty, versions, delay_period);
        connection.validate()?;

        // Store connection
        self.connections.insert(conn_id.clone(), connection);
        self.client_connections
            .entry(client_id)
            .or_default()
            .push(conn_id.clone());

        tracing::info!(
            connection_id = %conn_id,
            counterparty = %counterparty_conn_id,
            "Connection try open complete"
        );

        Ok(conn_id)
    }

    /// Connection Open Ack - Acknowledge Try
    ///
    /// Chain A calls this after receiving proof of Chain B's ConnOpenTry
    pub fn conn_open_ack(
        &mut self,
        conn_id: &ConnectionId,
        counterparty_conn_id: ConnectionId,
        version: Version,
        proof_try: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<()> {
        // Get connection
        let connection = self.get_connection_mut(conn_id)?;

        // Verify state
        if connection.state != ConnectionState::Init {
            return Err(IbcError::InvalidConnectionState {
                expected: ConnectionState::Init.to_string(),
                actual: connection.state.to_string(),
            });
        }

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

        // Verify version is compatible
        if !connection.versions.iter().any(|v| v.identifier == version.identifier) {
            return Err(IbcError::ConnectionVersionMismatch(format!(
                "version {} not supported",
                version.identifier
            )));
        }

        // Update connection
        connection.set_counterparty_connection_id(counterparty_conn_id.clone());
        connection.versions = vec![version];
        connection.set_open();

        tracing::info!(
            connection_id = %conn_id,
            counterparty = %counterparty_conn_id,
            "Connection ack complete - connection OPEN"
        );

        Ok(())
    }

    /// Connection Open Confirm - Final confirmation
    ///
    /// Chain B calls this after receiving proof of Chain A's ConnOpenAck
    pub fn conn_open_confirm(
        &mut self,
        conn_id: &ConnectionId,
        proof_ack: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<()> {
        // Get connection
        let connection = self.get_connection_mut(conn_id)?;

        // Verify state
        if connection.state != ConnectionState::TryOpen {
            return Err(IbcError::InvalidConnectionState {
                expected: ConnectionState::TryOpen.to_string(),
                actual: connection.state.to_string(),
            });
        }

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

        // Update state to Open
        connection.set_open();

        tracing::info!(
            connection_id = %conn_id,
            "Connection confirm complete - connection OPEN"
        );

        Ok(())
    }

    /// Pick a compatible version from counterparty's versions
    fn pick_version(&self, counterparty_versions: &[Version]) -> IbcResult<Vec<Version>> {
        let our_version = Version::default_connection();

        // Find matching version
        for version in counterparty_versions {
            if version.identifier == our_version.identifier {
                // Check features overlap
                let common_features: Vec<String> = version
                    .features
                    .iter()
                    .filter(|f| our_version.features.contains(f))
                    .cloned()
                    .collect();

                if !common_features.is_empty() {
                    return Ok(vec![Version::new(&version.identifier, common_features)]);
                }
            }
        }

        Err(IbcError::ConnectionVersionMismatch(
            "no compatible version found".to_string(),
        ))
    }

    /// Get all connections
    pub fn all_connections(&self) -> impl Iterator<Item = (&ConnectionId, &ConnectionEnd)> {
        self.connections.iter()
    }

    /// Count connections
    pub fn connection_count(&self) -> usize {
        self.connections.len()
    }
}

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

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

    fn test_counterparty() -> Counterparty {
        Counterparty::new(
            ClientId::new("07-tendermint", 0),
            None,
            CommitmentPrefix::default(),
        )
    }

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

    #[test]
    fn test_connection_init() {
        let mut manager = ConnectionManager::new();
        let client_id = ClientId::new("synor", 0);

        let conn_id = manager
            .conn_open_init(client_id.clone(), test_counterparty(), None, 0)
            .unwrap();

        let conn = manager.get_connection(&conn_id).unwrap();
        assert_eq!(conn.state, ConnectionState::Init);
        assert_eq!(conn.client_id, client_id);
    }

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

        let client_a = ClientId::new("synor", 0);
        let client_b = ClientId::new("synor", 1);

        // Chain A: Init
        let conn_a = manager_a
            .conn_open_init(
                client_a.clone(),
                Counterparty::new(client_b.clone(), None, CommitmentPrefix::default()),
                None,
                0,
            )
            .unwrap();

        // Chain B: TryOpen
        let counterparty_b = Counterparty::new(
            client_a.clone(),
            Some(conn_a.clone()),
            CommitmentPrefix::default(),
        );
        let conn_b = manager_b
            .conn_open_try(
                client_b.clone(),
                counterparty_b,
                vec![Version::default_connection()],
                0,
                vec![1, 2, 3], // Mock proof
                Height::from_height(100),
            )
            .unwrap();

        // Chain A: Ack
        manager_a
            .conn_open_ack(
                &conn_a,
                conn_b.clone(),
                Version::default_connection(),
                vec![1, 2, 3], // Mock proof
                Height::from_height(101),
            )
            .unwrap();

        // Verify Chain A is open
        let conn = manager_a.get_connection(&conn_a).unwrap();
        assert_eq!(conn.state, ConnectionState::Open);

        // Chain B: Confirm
        manager_b
            .conn_open_confirm(
                &conn_b,
                vec![1, 2, 3], // Mock proof
                Height::from_height(102),
            )
            .unwrap();

        // Verify Chain B is open
        let conn = manager_b.get_connection(&conn_b).unwrap();
        assert_eq!(conn.state, ConnectionState::Open);
    }

    #[test]
    fn test_invalid_state_transition() {
        let mut manager = ConnectionManager::new();
        let client_id = ClientId::new("synor", 0);

        let conn_id = manager
            .conn_open_init(client_id, test_counterparty(), None, 0)
            .unwrap();

        // Try to confirm without going through TryOpen
        let result = manager.conn_open_confirm(
            &conn_id,
            vec![1, 2, 3],
            Height::from_height(100),
        );

        assert!(result.is_err());
    }
}
