use std::sync::Arc;

use async_trait::async_trait;
use dashmap::DashMap;
use sha2::Digest;
use tokio::sync::mpsc;

use crate::transport::slim_proto::{self, Content, SessionMessageType};
use crate::transport::{IncomingMessage, Transport, TransportError};

/// In-memory mock transport for local development and testing.
///
/// Routes messages between subscribers entirely in-process, with no
/// network I/O. Multiple `SkytaleClient` instances sharing the same
/// `MockTransport` can exchange MLS-encrypted messages locally.
///
/// # Example
///
/// ```no_run
/// use std::sync::Arc;
/// use skytale_sdk::mock_transport::MockTransport;
/// let transport = Arc::new(MockTransport::new());
/// ```
pub struct MockTransport {
    /// Maps channel_id -> list of subscriber senders.
    subscribers: Arc<DashMap<[u8; 32], Vec<mpsc::Sender<IncomingMessage>>>>,
}

impl MockTransport {
    /// Create a new in-memory mock transport.
    pub fn new() -> Self {
        Self {
            subscribers: Arc::new(DashMap::new()),
        }
    }
}

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

#[async_trait]
impl Transport for MockTransport {
    async fn subscribe(
        &self,
        _channel_name: (&str, &str, &str),
        channel_id: [u8; 32],
    ) -> Result<mpsc::Receiver<IncomingMessage>, TransportError> {
        let (tx, rx) = mpsc::channel::<IncomingMessage>(64);
        self.subscribers.entry(channel_id).or_default().push(tx);
        Ok(rx)
    }

    async fn publish(
        &self,
        channel_name: (&str, &str, &str),
        session_message_type: SessionMessageType,
        content: Content,
    ) -> Result<(), TransportError> {
        let channel_id = channel_id_from_name(channel_name);
        let payload = extract_payload(&content);
        let session_type_i32 = session_message_type as i32;

        let incoming = IncomingMessage {
            channel_id,
            session_message_type: session_type_i32,
            payload,
        };

        if let Some(mut senders) = self.subscribers.get_mut(&channel_id) {
            // Deliver to all subscribers; lazily remove dead senders.
            senders.retain(|tx| tx.try_send(incoming.clone()).is_ok());
        }

        Ok(())
    }
}

/// Derive a 32-byte channel ID from a 3-component name via SHA-256.
fn channel_id_from_name(name: (&str, &str, &str)) -> [u8; 32] {
    let mut hasher = sha2::Sha256::new();
    hasher.update(name.0.as_bytes());
    hasher.update(b"/");
    hasher.update(name.1.as_bytes());
    hasher.update(b"/");
    hasher.update(name.2.as_bytes());
    hasher.finalize().into()
}

/// Extract raw payload bytes from a Content message for mock delivery.
///
/// Unlike the gRPC path, mock transport does not append sequence numbers,
/// so no stripping is needed.
fn extract_payload(content: &Content) -> Vec<u8> {
    match &content.content_type {
        Some(slim_proto::content::ContentType::AppPayload(app)) => app.blob.clone(),
        Some(slim_proto::content::ContentType::CommandPayload(cmd)) => {
            extract_mls_content_mock(cmd)
        }
        None => Vec::new(),
    }
}

/// Extract MLS content bytes from command payloads (mock variant, no seq suffix).
fn extract_mls_content_mock(cmd: &slim_proto::CommandPayload) -> Vec<u8> {
    use slim_proto::command_payload::CommandPayloadType;
    match &cmd.command_payload_type {
        Some(CommandPayloadType::GroupAdd(p)) => p
            .mls
            .as_ref()
            .map(|m| m.mls_content.clone())
            .unwrap_or_default(),
        Some(CommandPayloadType::GroupRemove(p)) => p
            .mls
            .as_ref()
            .map(|m| m.mls_content.clone())
            .unwrap_or_default(),
        Some(CommandPayloadType::GroupWelcome(p)) => p
            .mls
            .as_ref()
            .map(|m| m.mls_content.clone())
            .unwrap_or_default(),
        Some(CommandPayloadType::GroupProposal(p)) => p.mls_proposal.clone(),
        Some(CommandPayloadType::JoinReply(p)) => p.key_package.clone().unwrap_or_default(),
        _ => Vec::new(),
    }
}
