use std::{collections::HashSet, fmt};

use radicle::Profile;

use crate::{
    ci_event::CiEvent,
    logger,
    node_event_source::{NodeEventError, NodeEventSource},
};

pub struct CiEventSource<'a> {
    source: NodeEventSource,
    profile: &'a Profile,
}

impl<'a> CiEventSource<'a> {
    pub fn new(profile: &'a Profile) -> Result<Self, CiEventSourceError> {
        let source = Self {
            source: NodeEventSource::new(profile).map_err(CiEventSourceError::Subscribe)?,
            profile,
        };
        logger::ci_event_source_created(&source);
        Ok(source)
    }

    pub fn event(&mut self) -> Result<Option<Vec<CiEvent>>, CiEventSourceError> {
        let result = self.source.node_event();
        let mut unhandled = HashSet::new();
        match result {
            Err(err) if matches!(err, NodeEventError::BrokenConnection) => {
                logger::ci_event_source_disconnected();
                Err(CiEventSourceError::BrokenConnection(err))
            }
            Err(err) => {
                logger::error("error reading event from node", &err);
                if let NodeEventError::Node(_) = err {
                    Ok(Some(Vec::new()))
                } else {
                    Err(CiEventSourceError::NodeEventError(err))
                }
            }
            Ok(None) => {
                logger::ci_event_source_end();
                Ok(None)
            }
            Ok(Some(event)) => {
                let ci_events = CiEvent::from_node_event(&event, self.profile, &mut unhandled)
                    .map_err(CiEventSourceError::CiEvent)?;
                if !ci_events.is_empty() {
                    logger::ci_event_source_got_events(&ci_events);
                }
                Ok(Some(ci_events))
            }
        }
    }
}

impl<'a> fmt::Debug for CiEventSource<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "CiEventSource<path={:?}", self.source)
    }
}

#[derive(Debug, thiserror::Error)]
pub enum CiEventSourceError {
    #[error("failed to subscribe to node events")]
    Subscribe(#[source] crate::node_event_source::NodeEventError),

    #[error("connection to node control socket broke")]
    BrokenConnection(#[source] crate::node_event_source::NodeEventError),

    #[error("failed to read event from node")]
    NodeEventError(#[source] crate::node_event_source::NodeEventError),

    #[error("failed to create CI events from node event")]
    CiEvent(#[source] crate::ci_event::CiEventError),
}
