//! Core publisher traits for the service bus.

use std::error::Error;
use std::fmt;

/// An event to be published to the bus.
#[derive(Clone, Debug)]
pub struct Event {
    /// Unique identifier for this event
    pub id: String,
    /// Event type (e.g., "OrderCreated", "PaymentSucceeded")
    pub event_type: String,
    /// Serialized payload (typically JSON or binary)
    pub payload: Vec<u8>,
    /// Optional metadata (headers, correlation IDs, etc.)
    pub metadata: Option<Vec<(String, String)>>,
}

impl Event {
    /// Create a new event with the given type and payload.
    pub fn new(id: impl Into<String>, event_type: impl Into<String>, payload: Vec<u8>) -> Self {
        Self {
            id: id.into(),
            event_type: event_type.into(),
            payload,
            metadata: None,
        }
    }

    /// Create an event with bitcode-serialized payload.
    pub fn encode<T: serde::Serialize>(
        id: impl Into<String>,
        event_type: impl Into<String>,
        payload: &T,
    ) -> Result<Self, bitcode::Error> {
        let bytes = bitcode::serialize(payload)?;
        Ok(Self::new(id, event_type, bytes))
    }

    /// Decode the payload from bitcode binary format.
    pub fn decode<T: serde::de::DeserializeOwned>(&self) -> Result<T, bitcode::Error> {
        bitcode::deserialize(&self.payload)
    }

    /// Create an event with JSON-serialized payload.
    pub fn json_encode<T: serde::Serialize>(
        id: impl Into<String>,
        event_type: impl Into<String>,
        payload: &T,
    ) -> Result<Self, serde_json::Error> {
        let bytes = serde_json::to_vec(payload)?;
        Ok(Self::new(id, event_type, bytes))
    }

    /// Decode the payload from JSON format.
    pub fn json_decode<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
        serde_json::from_slice(&self.payload)
    }

    /// Create an event with a string payload.
    pub fn with_string_payload(
        id: impl Into<String>,
        event_type: impl Into<String>,
        payload: impl Into<String>,
    ) -> Self {
        Self::new(id, event_type, payload.into().into_bytes())
    }

    /// Add metadata to the event.
    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata
            .get_or_insert_with(Vec::new)
            .push((key.into(), value.into()));
        self
    }

    /// Get a metadata value by key.
    pub fn meta(&self, key: &str) -> Option<&str> {
        self.metadata
            .as_ref()
            .and_then(|m| m.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str()))
    }

    /// Get the correlation ID, if set.
    pub fn correlation_id(&self) -> Option<&str> {
        self.meta("correlation_id")
    }

    /// Get the causation ID, if set.
    pub fn causation_id(&self) -> Option<&str> {
        self.meta("causation_id")
    }

    /// Get the payload as a string (if valid UTF-8).
    pub fn payload_str(&self) -> Option<&str> {
        std::str::from_utf8(&self.payload).ok()
    }
}

/// Error type for publish operations.
#[derive(Debug)]
pub enum PublishError {
    /// Connection to the bus failed
    ConnectionFailed(String),
    /// Serialization of the event failed
    SerializationFailed(String),
    /// The bus rejected the event
    Rejected(String),
    /// Timeout waiting for acknowledgment
    Timeout,
    /// Other error
    Other(Box<dyn Error + Send + Sync>),
}

impl fmt::Display for PublishError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PublishError::ConnectionFailed(msg) => write!(f, "Connection failed: {}", msg),
            PublishError::SerializationFailed(msg) => write!(f, "Serialization failed: {}", msg),
            PublishError::Rejected(msg) => write!(f, "Event rejected: {}", msg),
            PublishError::Timeout => write!(f, "Publish timeout"),
            PublishError::Other(e) => write!(f, "Publish error: {}", e),
        }
    }
}

impl Error for PublishError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            PublishError::Other(e) => Some(e.as_ref()),
            _ => None,
        }
    }
}

/// Trait for publishing events to a message bus.
///
/// Implementations might include:
/// - `InMemoryBus` - For testing and single-process scenarios
/// - `KafkaPublisher` - For Apache Kafka
/// - `NatsPublisher` - For NATS
/// - `RabbitMqPublisher` - For RabbitMQ
/// - `CloudEventsPublisher` - For CloudEvents-compatible systems
pub trait Publisher: Send + Sync {
    /// Publish a single event to the bus.
    fn publish(&self, event: Event) -> Result<(), PublishError>;

    /// Publish multiple events to the bus.
    ///
    /// Default implementation publishes events sequentially.
    /// Implementations may override for batch optimization.
    fn publish_batch(&self, events: Vec<Event>) -> Result<(), PublishError> {
        for event in events {
            self.publish(event)?;
        }
        Ok(())
    }
}

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

    #[test]
    fn event_construction() {
        let event = Event::new("evt-1", "OrderCreated", b"{}".to_vec());
        assert_eq!(event.id, "evt-1");
        assert_eq!(event.event_type, "OrderCreated");
        assert_eq!(event.payload_str(), Some("{}"));
    }

    #[test]
    fn event_with_metadata() {
        let event = Event::new("evt-1", "OrderCreated", b"{}".to_vec())
            .with_metadata("correlation-id", "abc-123")
            .with_metadata("source", "order-service");

        let meta = event.metadata.unwrap();
        assert_eq!(meta.len(), 2);
        assert_eq!(
            meta[0],
            ("correlation-id".to_string(), "abc-123".to_string())
        );
    }
}
