//! IBC Handler - Main interface for IBC operations
//!
//! The handler coordinates light clients, connections, channels, and packets.

use crate::channel::{Channel, ChannelId, ChannelManager, ChannelOrder, PortId};
use crate::client::{ClientId, ClientState, ConsensusState, Header, LightClient};
use crate::connection::{ConnectionEnd, ConnectionId, ConnectionManager, Counterparty};
use crate::error::{IbcError, IbcResult};
use crate::packet::{Acknowledgement, FungibleTokenPacketData, Packet, PacketHandler, Timeout};
use crate::types::{Height, Timestamp, Version};
use parking_lot::RwLock;
use std::sync::Arc;

/// IBC handler configuration
#[derive(Debug, Clone)]
pub struct IbcConfig {
    /// Chain ID
    pub chain_id: String,
    /// Default connection delay period (nanoseconds)
    pub default_delay_period: u64,
    /// Enable packet timeout
    pub enable_timeout: bool,
    /// Maximum packet data size
    pub max_packet_size: usize,
}

impl Default for IbcConfig {
    fn default() -> Self {
        Self {
            chain_id: "synor-1".to_string(),
            default_delay_period: 0,
            enable_timeout: true,
            max_packet_size: 1024 * 1024, // 1 MB
        }
    }
}

/// Main IBC handler
pub struct IbcHandler {
    /// Configuration
    config: IbcConfig,
    /// Light client manager
    light_client: Arc<RwLock<LightClient>>,
    /// Connection manager
    connections: Arc<RwLock<ConnectionManager>>,
    /// Channel manager
    channels: Arc<RwLock<ChannelManager>>,
    /// Packet handler
    packets: Arc<RwLock<PacketHandler>>,
    /// Current block height
    current_height: Arc<RwLock<Height>>,
    /// Current block timestamp
    current_timestamp: Arc<RwLock<Timestamp>>,
}

impl IbcHandler {
    /// Create a new IBC handler
    pub fn new(config: IbcConfig) -> Self {
        Self {
            config,
            light_client: Arc::new(RwLock::new(LightClient::new())),
            connections: Arc::new(RwLock::new(ConnectionManager::new())),
            channels: Arc::new(RwLock::new(ChannelManager::new())),
            packets: Arc::new(RwLock::new(PacketHandler::new())),
            current_height: Arc::new(RwLock::new(Height::from_height(1))),
            current_timestamp: Arc::new(RwLock::new(Timestamp::now())),
        }
    }

    /// Update current block height and timestamp
    pub fn update_block(&self, height: Height, timestamp: Timestamp) {
        *self.current_height.write() = height;
        *self.current_timestamp.write() = timestamp;
    }

    /// Get current height
    pub fn current_height(&self) -> Height {
        *self.current_height.read()
    }

    /// Get current timestamp
    pub fn current_timestamp(&self) -> Timestamp {
        *self.current_timestamp.read()
    }

    // ========================================================================
    // Client Operations
    // ========================================================================

    /// Create a new light client
    pub fn create_client(
        &self,
        client_state: ClientState,
        consensus_state: ConsensusState,
    ) -> IbcResult<ClientId> {
        let mut client = self.light_client.write();
        let client_id = client.create_client(client_state, consensus_state)?;

        tracing::info!(
            client_id = %client_id,
            "Created IBC client"
        );

        Ok(client_id)
    }

    /// Update a light client with a new header
    pub fn update_client(&self, client_id: &ClientId, header: Header) -> IbcResult<()> {
        let mut client = self.light_client.write();
        client.update_client(client_id, header)?;

        tracing::info!(
            client_id = %client_id,
            "Updated IBC client"
        );

        Ok(())
    }

    /// Get client state
    pub fn get_client_state(&self, client_id: &ClientId) -> IbcResult<ClientState> {
        let client = self.light_client.read();
        Ok(client.get_client_state(client_id)?.clone())
    }

    // ========================================================================
    // Connection Operations
    // ========================================================================

    /// Initialize a connection (ConnOpenInit)
    pub fn connection_open_init(
        &self,
        client_id: ClientId,
        counterparty_client_id: ClientId,
        version: Option<Version>,
    ) -> IbcResult<ConnectionId> {
        // Verify client exists
        let _ = self.get_client_state(&client_id)?;

        let counterparty = Counterparty::new(
            counterparty_client_id,
            None,
            crate::types::CommitmentPrefix::default(),
        );

        let mut connections = self.connections.write();
        connections.conn_open_init(
            client_id,
            counterparty,
            version,
            self.config.default_delay_period,
        )
    }

    /// Try to open a connection (ConnOpenTry)
    pub fn connection_open_try(
        &self,
        client_id: ClientId,
        counterparty_client_id: ClientId,
        counterparty_conn_id: ConnectionId,
        counterparty_versions: Vec<Version>,
        proof_init: Vec<u8>,
        proof_height: Height,
    ) -> IbcResult<ConnectionId> {
        // Verify client exists
        let _ = self.get_client_state(&client_id)?;

        let counterparty = Counterparty::new(
            counterparty_client_id,
            Some(counterparty_conn_id),
            crate::types::CommitmentPrefix::default(),
        );

        let mut connections = self.connections.write();
        connections.conn_open_try(
            client_id,
            counterparty,
            counterparty_versions,
            self.config.default_delay_period,
            proof_init,
            proof_height,
        )
    }

    /// Acknowledge a connection (ConnOpenAck)
    pub fn connection_open_ack(
        &self,
        conn_id: &ConnectionId,
        counterparty_conn_id: ConnectionId,
        version: Version,
        proof_try: Vec<u8>,
        proof_height: Height,
    ) -> IbcResult<()> {
        let mut connections = self.connections.write();
        connections.conn_open_ack(conn_id, counterparty_conn_id, version, proof_try, proof_height)
    }

    /// Confirm a connection (ConnOpenConfirm)
    pub fn connection_open_confirm(
        &self,
        conn_id: &ConnectionId,
        proof_ack: Vec<u8>,
        proof_height: Height,
    ) -> IbcResult<()> {
        let mut connections = self.connections.write();
        connections.conn_open_confirm(conn_id, proof_ack, proof_height)
    }

    /// Get connection
    pub fn get_connection(&self, conn_id: &ConnectionId) -> IbcResult<ConnectionEnd> {
        let connections = self.connections.read();
        Ok(connections.get_connection(conn_id)?.clone())
    }

    // ========================================================================
    // Channel Operations
    // ========================================================================

    /// Bind a port to a module
    pub fn bind_port(&self, port_id: PortId, module: String) -> IbcResult<()> {
        let mut channels = self.channels.write();
        channels.bind_port(port_id, module)
    }

    /// Initialize a channel (ChanOpenInit)
    pub fn channel_open_init(
        &self,
        port_id: PortId,
        connection_id: ConnectionId,
        counterparty_port: PortId,
        ordering: ChannelOrder,
        version: String,
    ) -> IbcResult<ChannelId> {
        // Verify connection exists and is open
        let connection = self.get_connection(&connection_id)?;
        if !connection.is_open() {
            return Err(IbcError::InvalidConnectionState {
                expected: "OPEN".to_string(),
                actual: connection.state.to_string(),
            });
        }

        let mut channels = self.channels.write();
        channels.chan_open_init(
            port_id,
            ordering,
            vec![connection_id],
            counterparty_port,
            version,
        )
    }

    /// Try to open a channel (ChanOpenTry)
    pub fn channel_open_try(
        &self,
        port_id: PortId,
        connection_id: ConnectionId,
        counterparty_port: PortId,
        counterparty_channel: ChannelId,
        ordering: ChannelOrder,
        version: String,
        counterparty_version: String,
        proof_init: Vec<u8>,
        proof_height: Height,
    ) -> IbcResult<ChannelId> {
        // Verify connection
        let connection = self.get_connection(&connection_id)?;
        if !connection.is_open() {
            return Err(IbcError::InvalidConnectionState {
                expected: "OPEN".to_string(),
                actual: connection.state.to_string(),
            });
        }

        let mut channels = self.channels.write();
        channels.chan_open_try(
            port_id,
            ordering,
            vec![connection_id],
            counterparty_port,
            counterparty_channel,
            version,
            counterparty_version,
            proof_init,
            proof_height,
        )
    }

    /// Acknowledge a channel (ChanOpenAck)
    pub fn channel_open_ack(
        &self,
        port_id: &PortId,
        channel_id: &ChannelId,
        counterparty_channel: ChannelId,
        counterparty_version: String,
        proof_try: Vec<u8>,
        proof_height: Height,
    ) -> IbcResult<()> {
        let mut channels = self.channels.write();
        channels.chan_open_ack(
            port_id,
            channel_id,
            counterparty_channel,
            counterparty_version,
            proof_try,
            proof_height,
        )
    }

    /// Confirm a channel (ChanOpenConfirm)
    pub fn channel_open_confirm(
        &self,
        port_id: &PortId,
        channel_id: &ChannelId,
        proof_ack: Vec<u8>,
        proof_height: Height,
    ) -> IbcResult<()> {
        let mut channels = self.channels.write();
        channels.chan_open_confirm(port_id, channel_id, proof_ack, proof_height)
    }

    /// Close a channel
    pub fn channel_close_init(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<()> {
        let mut channels = self.channels.write();
        channels.chan_close_init(port_id, channel_id)
    }

    /// Get channel
    pub fn get_channel(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<Channel> {
        let channels = self.channels.read();
        Ok(channels.get_channel(port_id, channel_id)?.clone())
    }

    // ========================================================================
    // Packet Operations
    // ========================================================================

    /// Send a packet
    pub fn send_packet(
        &self,
        source_port: PortId,
        source_channel: ChannelId,
        data: Vec<u8>,
        timeout: Timeout,
    ) -> IbcResult<u64> {
        // Validate data size
        if data.len() > self.config.max_packet_size {
            return Err(IbcError::PacketDataTooLarge {
                size: data.len(),
                max: self.config.max_packet_size,
            });
        }

        // Get channel and verify it's open
        let channel = self.get_channel(&source_port, &source_channel)?;
        if !channel.is_open() {
            return Err(IbcError::InvalidChannelState {
                expected: "OPEN".to_string(),
                actual: channel.state.to_string(),
            });
        }

        let counterparty = &channel.counterparty;
        let dest_port = counterparty.port_id.clone();
        let dest_channel = counterparty.channel_id.clone().ok_or_else(|| {
            IbcError::InvalidChannelState {
                expected: "counterparty channel set".to_string(),
                actual: "none".to_string(),
            }
        })?;

        // Get sequence number
        let mut channels = self.channels.write();
        let sequence = channels.increment_send_sequence(&source_port, &source_channel);

        // Create packet
        let packet = Packet::new(
            sequence,
            source_port.clone(),
            source_channel.clone(),
            dest_port,
            dest_channel,
            data,
            timeout.height,
            timeout.timestamp,
        );
        packet.validate()?;

        // Store commitment
        let mut packets = self.packets.write();
        packets.store_packet_commitment(&packet);

        tracing::info!(
            port = %source_port,
            channel = %source_channel,
            sequence = sequence,
            "Packet sent"
        );

        Ok(sequence)
    }

    /// Receive a packet
    pub fn recv_packet(
        &self,
        packet: Packet,
        proof_commitment: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<Acknowledgement> {
        // Validate packet
        packet.validate()?;

        // Check timeout
        let current_height = self.current_height();
        let current_time = self.current_timestamp();
        if packet.is_timed_out(current_height, current_time) {
            return Err(IbcError::PacketTimeout {
                height: current_height.revision_height,
                timestamp: current_time.nanoseconds(),
            });
        }

        // Get channel and verify
        let channel = self.get_channel(&packet.dest_port, &packet.dest_channel)?;
        if !channel.is_open() {
            return Err(IbcError::InvalidChannelState {
                expected: "OPEN".to_string(),
                actual: channel.state.to_string(),
            });
        }

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

        // Handle based on channel ordering
        let mut packets = self.packets.write();
        let mut channels = self.channels.write();

        match channel.ordering {
            ChannelOrder::Unordered => {
                // Verify no receipt exists
                packets.verify_no_receipt(&packet)?;
            }
            ChannelOrder::Ordered => {
                // Verify sequence
                let expected = channels.get_sequences(&packet.dest_port, &packet.dest_channel).next_recv;
                packets.verify_ordered_sequence(&packet, expected)?;
                channels.increment_recv_sequence(&packet.dest_port, &packet.dest_channel);
            }
        }

        // Store receipt
        packets.store_packet_receipt(&packet, current_height, None);

        // Generate acknowledgement (success)
        let ack = Acknowledgement::success(vec![]);

        // Store ack commitment
        packets.store_ack_commitment(
            &packet.dest_port,
            &packet.dest_channel,
            packet.sequence,
            &ack,
        );

        tracing::info!(
            port = %packet.dest_port,
            channel = %packet.dest_channel,
            sequence = packet.sequence,
            "Packet received"
        );

        Ok(ack)
    }

    /// Acknowledge a packet
    pub fn acknowledge_packet(
        &self,
        packet: Packet,
        acknowledgement: Acknowledgement,
        proof_ack: Vec<u8>,
        _proof_height: Height,
    ) -> IbcResult<()> {
        // Get channel
        let channel = self.get_channel(&packet.source_port, &packet.source_channel)?;

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

        let mut packets = self.packets.write();

        // Verify commitment exists
        if packets
            .get_packet_commitment(&packet.source_port, &packet.source_channel, packet.sequence)
            .is_none()
        {
            return Err(IbcError::PacketNotFound(packet.sequence));
        }

        // For ordered channels, verify sequence
        if channel.ordering == ChannelOrder::Ordered {
            let mut channels = self.channels.write();
            let expected = channels.get_sequences(&packet.source_port, &packet.source_channel).next_ack;
            if packet.sequence != expected {
                return Err(IbcError::InvalidPacketSequence {
                    expected,
                    actual: packet.sequence,
                });
            }
            channels.increment_ack_sequence(&packet.source_port, &packet.source_channel);
        }

        // Delete commitment
        packets.delete_packet_commitment(&packet.source_port, &packet.source_channel, packet.sequence);

        tracing::info!(
            port = %packet.source_port,
            channel = %packet.source_channel,
            sequence = packet.sequence,
            success = acknowledgement.is_success(),
            "Packet acknowledged"
        );

        Ok(())
    }

    /// Timeout a packet
    pub fn timeout_packet(
        &self,
        packet: Packet,
        proof_unreceived: Vec<u8>,
        _proof_height: Height,
        _next_sequence_recv: u64,
    ) -> IbcResult<()> {
        // Verify the packet timed out
        let _current_height = self.current_height();
        let _current_time = self.current_timestamp();

        // Note: For timeout, we check if DESTINATION chain's height/time exceeds timeout
        // This is a simplification - real implementation would verify against proof_height

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

        let mut packets = self.packets.write();
        packets.timeout_packet(&packet)?;

        tracing::info!(
            port = %packet.source_port,
            channel = %packet.source_channel,
            sequence = packet.sequence,
            "Packet timed out"
        );

        Ok(())
    }

    // ========================================================================
    // Token Transfer (ICS-20)
    // ========================================================================

    /// Send tokens via IBC transfer
    pub fn transfer(
        &self,
        source_port: PortId,
        source_channel: ChannelId,
        denom: String,
        amount: String,
        sender: String,
        receiver: String,
        timeout: Timeout,
    ) -> IbcResult<u64> {
        let packet_data = FungibleTokenPacketData::new(denom, amount, sender, receiver);
        let data = packet_data.encode();

        self.send_packet(source_port, source_channel, data, timeout)
    }

    // ========================================================================
    // Stats and Info
    // ========================================================================

    /// Get connection count
    pub fn connection_count(&self) -> usize {
        self.connections.read().connection_count()
    }

    /// Get chain ID
    pub fn chain_id(&self) -> &str {
        &self.config.chain_id
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::client::TrustLevel;
    use crate::types::ChainId;

    fn setup_handler() -> IbcHandler {
        let handler = IbcHandler::new(IbcConfig::default());

        // Create a client
        let client_state = ClientState::new_synor(
            ChainId::synor_testnet(),
            Height::from_height(100),
        );
        let consensus_state = ConsensusState::new(
            Timestamp::now(),
            vec![0u8; 32],
            vec![0u8; 32],
        );

        handler.create_client(client_state, consensus_state).unwrap();
        handler.bind_port(PortId::transfer(), "transfer".to_string()).unwrap();

        handler
    }

    #[test]
    fn test_handler_creation() {
        let handler = IbcHandler::default();
        assert_eq!(handler.chain_id(), "synor-1");
        assert_eq!(handler.connection_count(), 0);
    }

    #[test]
    fn test_client_creation() {
        let handler = IbcHandler::default();

        let client_state = ClientState::new_synor(
            ChainId::synor_testnet(),
            Height::from_height(100),
        );
        let consensus_state = ConsensusState::new(
            Timestamp::now(),
            vec![0u8; 32],
            vec![0u8; 32],
        );

        let client_id = handler.create_client(client_state, consensus_state).unwrap();
        assert!(handler.get_client_state(&client_id).is_ok());
    }

    #[test]
    fn test_connection_init() {
        let handler = setup_handler();

        let client_id = ClientId::new("synor", 0);
        let counterparty_client = ClientId::new("07-tendermint", 0);

        let conn_id = handler.connection_open_init(
            client_id,
            counterparty_client,
            None,
        ).unwrap();

        let conn = handler.get_connection(&conn_id).unwrap();
        assert!(!conn.is_open()); // Still in Init state
    }

    #[test]
    fn test_update_block() {
        let handler = IbcHandler::default();

        let new_height = Height::from_height(100);
        let new_time = Timestamp::from_seconds(1000);

        handler.update_block(new_height, new_time);

        assert_eq!(handler.current_height(), new_height);
        assert_eq!(handler.current_timestamp(), new_time);
    }
}
