use crate::message::{
    down_topic_name, parse_channel_from_topic, SerialChunk, DEFAULT_HISTORY_DEPTH, TYPE_NAME,
    up_topic_name,
};
use anyhow::{Context, Result};
use rustdds::no_key::DataReader;
use rustdds::{
    policy::{Durability, History, Reliability},
    CDRDeserializerAdapter, CDRSerializerAdapter, DomainParticipant, QosPolicyBuilder, TopicKind,
};
use std::collections::BTreeSet;
use std::time::Duration;
use tokio::time::sleep;
use tracing::info;

pub type ChunkWriter =
    rustdds::no_key::DataWriter<SerialChunk, CDRSerializerAdapter<SerialChunk>>;
pub type ChunkReader = DataReader<SerialChunk, CDRDeserializerAdapter<SerialChunk>>;

pub struct DdsParticipant {
    pub participant: DomainParticipant,
}

impl DdsParticipant {
    pub fn new(domain_id: u16) -> Result<Self> {
        let participant = DomainParticipant::new(domain_id)
            .map_err(|e| anyhow::anyhow!("failed to create DDS participant: {e:?}"))?;
        Ok(Self { participant })
    }
}

pub struct DdsChannel {
    pub up_writer: ChunkWriter,
    pub down_writer: ChunkWriter,
    pub up_reader: ChunkReader,
    pub down_reader: ChunkReader,
}

pub fn channel_qos() -> rustdds::QosPolicies {
    QosPolicyBuilder::new()
        .reliability(Reliability::Reliable {
            max_blocking_time: rustdds::Duration::ZERO,
        })
        .history(History::KeepLast {
            depth: DEFAULT_HISTORY_DEPTH,
        })
        .durability(Durability::Volatile)
        .build()
}

impl DdsChannel {
    pub fn open(participant: &DomainParticipant, channel: &str) -> Result<Self> {
        let qos = channel_qos();

        let publisher = participant
            .create_publisher(&qos)
            .map_err(|e| anyhow::anyhow!("failed to create publisher: {e:?}"))?;
        let subscriber = participant
            .create_subscriber(&qos)
            .map_err(|e| anyhow::anyhow!("failed to create subscriber: {e:?}"))?;

        let up_topic = participant
            .create_topic(
                up_topic_name(channel),
                TYPE_NAME.to_string(),
                &qos,
                TopicKind::NoKey,
            )
            .map_err(|e| anyhow::anyhow!("failed to create up topic: {e:?}"))?;
        let down_topic = participant
            .create_topic(
                down_topic_name(channel),
                TYPE_NAME.to_string(),
                &qos,
                TopicKind::NoKey,
            )
            .map_err(|e| anyhow::anyhow!("failed to create down topic: {e:?}"))?;

        let up_writer = publisher
            .create_datawriter_no_key::<SerialChunk, CDRSerializerAdapter<SerialChunk>>(
                &up_topic,
                None,
            )
            .map_err(|e| anyhow::anyhow!("failed to create up writer: {e:?}"))?;
        let down_writer = publisher
            .create_datawriter_no_key::<SerialChunk, CDRSerializerAdapter<SerialChunk>>(
                &down_topic,
                None,
            )
            .map_err(|e| anyhow::anyhow!("failed to create down writer: {e:?}"))?;

        let up_reader = subscriber
            .create_datareader_no_key::<SerialChunk, CDRDeserializerAdapter<SerialChunk>>(
                &up_topic,
                None,
            )
            .map_err(|e| anyhow::anyhow!("failed to create up reader: {e:?}"))?;
        let down_reader = subscriber
            .create_datareader_no_key::<SerialChunk, CDRDeserializerAdapter<SerialChunk>>(
                &down_topic,
                None,
            )
            .map_err(|e| anyhow::anyhow!("failed to create down reader: {e:?}"))?;

        info!(
            channel,
            up = %up_topic_name(channel),
            down = %down_topic_name(channel),
            "DDS channel opened"
        );

        Ok(Self {
            up_writer,
            down_writer,
            up_reader,
            down_reader,
        })
    }
}

pub async fn write_chunk_async(writer: &ChunkWriter, chunk: SerialChunk) -> Result<()> {
    writer
        .async_write(chunk, None)
        .await
        .map_err(|e| anyhow::anyhow!("DDS async write failed: {e:?}"))
}

pub async fn discover_channels(
    participant: &DomainParticipant,
    wait_secs: u64,
) -> Result<Vec<String>> {
    info!(wait_secs, "waiting for DDS topic discovery");
    sleep(Duration::from_secs(wait_secs)).await;

    let mut channels = BTreeSet::new();
    for topic in participant.discovered_topics() {
        if let Some((channel, _direction)) = parse_channel_from_topic(&topic.topic_name()) {
            channels.insert(channel);
        }
    }

    Ok(channels.into_iter().collect())
}

pub async fn wait_for_shutdown() -> Result<()> {
    tokio::signal::ctrl_c()
        .await
        .context("failed to listen for ctrl-c")?;
    info!("shutdown signal received");
    Ok(())
}
