use std::sync::{Arc, Mutex};

use sha2::Digest;
use tokio::sync::mpsc;

use crate::mls_engine::{MlsEngine, ProcessedMessage};
use crate::transport::slim_proto::{self, ApplicationPayload, Content, SessionMessageType};
use crate::transport::{IncomingMessage, Transport};
use crate::SdkError;

/// An encrypted channel backed by an MLS group and gRPC transport.
///
/// Channels support concurrent send and receive. Call `send()` from any
/// thread while iterating `messages()` on another — the MLS engine and
/// transport are shared safely via `Arc<Mutex<>>`.
///
/// # Thread Safety
///
/// `Channel` is **not safe for concurrent `send()` from multiple threads**.
/// The MLS encryption state is protected by a `Mutex`, but the lock is released
/// between `encrypt()` and `publish()`. If another thread processes an incoming
/// MLS Commit between two `send()` calls, the epoch could advance causing the
/// second send to fail. In practice, the Python GIL serializes all calls from
/// Python, so this is not an issue for the primary SDK use case.
///
/// # Example
///
/// ```no_run
/// use skytale_sdk::client::SkytaleClient;
/// let mut client = SkytaleClient::new(
///     "https://relay.skytale.sh:5000",
///     "/tmp/sdk-data",
///     b"agent-1",
/// ).unwrap();
/// let mut channel = client.create_channel("org/team/general").unwrap();
/// channel.send(b"hello").unwrap();
/// ```
pub struct Channel {
    channel_id: [u8; 32],
    channel_name: (String, String, String),
    mls_engine: Arc<Mutex<MlsEngine>>,
    transport: Arc<dyn Transport>,
    runtime: Arc<tokio::runtime::Runtime>,
    receiver: Arc<Mutex<mpsc::Receiver<IncomingMessage>>>,
}

impl Channel {
    /// Create a new channel handle. Called internally by `SkytaleClient`.
    pub(crate) fn new(
        channel_id: [u8; 32],
        channel_name: (String, String, String),
        mls_engine: Arc<Mutex<MlsEngine>>,
        transport: Arc<dyn Transport>,
        runtime: Arc<tokio::runtime::Runtime>,
        receiver: mpsc::Receiver<IncomingMessage>,
    ) -> Self {
        Self {
            channel_id,
            channel_name,
            mls_engine,
            transport,
            runtime,
            receiver: Arc::new(Mutex::new(receiver)),
        }
    }

    /// Add a member to this channel. Returns Welcome bytes for the new member.
    ///
    /// The caller must send the welcome bytes to the new member out-of-band
    /// so they can call `SkytaleClient::join_channel`.
    #[tracing::instrument(skip(self, key_package_bytes), fields(channel.name = %format!("{}/{}/{}", self.channel_name.0, self.channel_name.1, self.channel_name.2)))]
    pub fn add_member(&mut self, key_package_bytes: &[u8]) -> Result<Vec<u8>, SdkError> {
        if key_package_bytes.len() > Self::MAX_KEY_PACKAGE_SIZE {
            return Err(SdkError::KeyPackageTooLarge(
                key_package_bytes.len(),
                Self::MAX_KEY_PACKAGE_SIZE,
            ));
        }

        let (commit_bytes, welcome_bytes) = {
            let mut engine = self.mls_engine.lock().map_err(|_| SdkError::LockPoisoned)?;
            engine.add_member(&self.channel_id, key_package_bytes)?
        };

        // Publish the commit via transport as a GroupAdd message.
        let name = self.name_tuple();
        let content = Content {
            content_type: Some(slim_proto::content::ContentType::CommandPayload(
                slim_proto::CommandPayload {
                    command_payload_type: Some(
                        slim_proto::command_payload::CommandPayloadType::GroupAdd(
                            slim_proto::GroupAddPayload {
                                new_participant: None,
                                participants: vec![],
                                mls: Some(slim_proto::MlsPayload {
                                    commit_id: 0,
                                    mls_content: commit_bytes,
                                }),
                            },
                        ),
                    ),
                },
            )),
        };

        self.runtime.block_on(self.transport.publish(
            name,
            SessionMessageType::GroupAdd,
            content,
        ))?;

        Ok(welcome_bytes)
    }

    /// Maximum payload size (256 KB), matching the relay's max_message_size.
    const MAX_MESSAGE_SIZE: usize = 256 * 1024;

    /// Maximum key package size (64 KB).
    const MAX_KEY_PACKAGE_SIZE: usize = 65536;

    /// Send an encrypted message on this channel.
    #[tracing::instrument(skip(self, payload), fields(channel.name = %format!("{}/{}/{}", self.channel_name.0, self.channel_name.1, self.channel_name.2), message.size = payload.len()))]
    pub fn send(&mut self, payload: &[u8]) -> Result<(), SdkError> {
        if payload.len() > Self::MAX_MESSAGE_SIZE {
            return Err(SdkError::MessageTooLarge(
                payload.len(),
                Self::MAX_MESSAGE_SIZE,
            ));
        }

        let ciphertext = {
            let mut engine = self.mls_engine.lock().map_err(|_| SdkError::LockPoisoned)?;
            engine.encrypt(&self.channel_id, payload)?
        };

        let name = self.name_tuple();
        let content = Content {
            content_type: Some(slim_proto::content::ContentType::AppPayload(
                ApplicationPayload {
                    payload_type: String::new(),
                    blob: ciphertext,
                },
            )),
        };

        self.runtime.block_on(
            self.transport
                .publish(name, SessionMessageType::Msg, content),
        )?;

        Ok(())
    }

    /// Return a blocking iterator over incoming messages on this channel.
    ///
    /// Each received message is MLS-decrypted before being yielded. Commit
    /// messages are processed silently (they advance the MLS epoch) and are
    /// not returned to the caller.
    ///
    /// The channel remains fully usable after calling this — `send()` and
    /// `add_member()` work from any thread while the iterator blocks on
    /// another.
    pub fn messages(&self) -> MessageIterator {
        MessageIterator {
            channel_id: self.channel_id,
            mls_engine: self.mls_engine.clone(),
            runtime: self.runtime.clone(),
            receiver: self.receiver.clone(),
        }
    }

    fn name_tuple(&self) -> (&str, &str, &str) {
        (
            &self.channel_name.0,
            &self.channel_name.1,
            &self.channel_name.2,
        )
    }
}

/// A blocking iterator over decrypted messages from a channel.
pub struct MessageIterator {
    channel_id: [u8; 32],
    mls_engine: Arc<Mutex<MlsEngine>>,
    runtime: Arc<tokio::runtime::Runtime>,
    receiver: Arc<Mutex<mpsc::Receiver<IncomingMessage>>>,
}

/// A decrypted message received from a channel.
pub struct ReceivedMessage {
    /// The decrypted payload bytes.
    pub payload: Vec<u8>,
}

impl MessageIterator {
    /// Try to receive and decrypt the next message, with a timeout.
    ///
    /// Returns `None` if the timeout expires or the channel is closed. This
    /// uses `try_recv` polling instead of `block_on` so it works from any
    /// thread (no tokio runtime context required on the calling thread).
    pub fn next_timeout(&mut self, timeout: std::time::Duration) -> Option<ReceivedMessage> {
        let deadline = std::time::Instant::now() + timeout;
        loop {
            let recv_result = {
                let mut rx = match self.receiver.lock() {
                    Ok(guard) => guard,
                    Err(_) => return None,
                };
                rx.try_recv()
            };
            match recv_result {
                Ok(incoming) => {
                    if incoming.payload.is_empty() {
                        if std::time::Instant::now() >= deadline {
                            return None;
                        }
                        continue;
                    }
                    let mut engine = match self.mls_engine.lock() {
                        Ok(guard) => guard,
                        Err(_) => return None,
                    };
                    match engine.process_incoming(&self.channel_id, &incoming.payload) {
                        Ok(ProcessedMessage::Application(plaintext)) => {
                            return Some(ReceivedMessage { payload: plaintext });
                        }
                        Ok(ProcessedMessage::Commit) | Ok(ProcessedMessage::Other) => {
                            if std::time::Instant::now() >= deadline {
                                return None;
                            }
                            continue;
                        }
                        Err(_) => {
                            if std::time::Instant::now() >= deadline {
                                return None;
                            }
                            continue;
                        }
                    }
                }
                Err(mpsc::error::TryRecvError::Empty) => {
                    if std::time::Instant::now() >= deadline {
                        return None;
                    }
                    std::thread::sleep(std::time::Duration::from_millis(5));
                }
                Err(mpsc::error::TryRecvError::Disconnected) => return None,
            }
        }
    }
}

impl Iterator for MessageIterator {
    type Item = ReceivedMessage;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let incoming = {
                let mut rx = match self.receiver.lock() {
                    Ok(guard) => guard,
                    Err(_) => return None,
                };
                self.runtime.block_on(rx.recv())?
            };

            // Skip empty payloads (e.g., acks with no MLS content).
            if incoming.payload.is_empty() {
                continue;
            }

            // Use MLS-native type detection instead of session_message_type,
            // because the relay labels ALL messages as Msg.
            let mut engine = match self.mls_engine.lock() {
                Ok(guard) => guard,
                Err(_) => return None,
            };
            match engine.process_incoming(&self.channel_id, &incoming.payload) {
                Ok(ProcessedMessage::Application(plaintext)) => {
                    return Some(ReceivedMessage { payload: plaintext });
                }
                Ok(ProcessedMessage::Commit) | Ok(ProcessedMessage::Other) => {
                    // Commits advance epoch silently; proposals/other are skipped.
                    continue;
                }
                Err(_) => {
                    // Skip messages that fail to process (e.g., our own messages).
                    continue;
                }
            }
        }
    }
}

/// Hash a 3-component channel name into a 32-byte channel ID using SHA-256.
///
/// Uses "/" separators between components to match supernode's `hash_name()`.
pub fn channel_id_from_name(org: &str, namespace: &str, service: &str) -> [u8; 32] {
    let mut hasher = sha2::Sha256::new();
    hasher.update(org.as_bytes());
    hasher.update(b"/");
    hasher.update(namespace.as_bytes());
    hasher.update(b"/");
    hasher.update(service.as_bytes());
    hasher.finalize().into()
}

/// Maximum total length of a channel name in bytes.
const MAX_CHANNEL_NAME_LEN: usize = 256;

/// Maximum length of a single channel name component in bytes.
const MAX_COMPONENT_LEN: usize = 64;

/// Parse a "org/namespace/service" name into its three components.
///
/// Validates that the name is well-formed:
/// - Exactly three non-empty components separated by `/`
/// - Total length at most 256 bytes
/// - Each component at most 64 bytes
/// - Only ASCII alphanumeric characters, `/`, `-`, `_`, and `.`
pub fn parse_channel_name(name: &str) -> Result<(String, String, String), SdkError> {
    if name.len() > MAX_CHANNEL_NAME_LEN {
        return Err(SdkError::InvalidChannelName(format!(
            "channel name exceeds {MAX_CHANNEL_NAME_LEN} byte limit"
        )));
    }

    // Validate character allowlist (before splitting, so '/' is included).
    if name
        .chars()
        .any(|c| !(c.is_ascii_alphanumeric() || matches!(c, '/' | '-' | '_' | '.')))
    {
        return Err(SdkError::InvalidChannelName(
            "channel name contains invalid characters (allowed: alphanumeric, /, -, _, .)".into(),
        ));
    }

    let parts: Vec<&str> = name.splitn(3, '/').collect();
    if parts.len() != 3 || parts.iter().any(|p| p.is_empty()) {
        return Err(SdkError::InvalidChannelName(
            "channel name must have exactly 3 non-empty components (org/namespace/service)".into(),
        ));
    }

    for part in &parts {
        if part.len() > MAX_COMPONENT_LEN {
            return Err(SdkError::InvalidChannelName(format!(
                "channel name component exceeds {MAX_COMPONENT_LEN} byte limit"
            )));
        }
    }

    Ok((
        parts[0].to_string(),
        parts[1].to_string(),
        parts[2].to_string(),
    ))
}
