use std::str::FromStr;

use entertainarr_domain::podcast::entity::{PodcastEpisodeInput, PodcastInput};
use entertainarr_domain::podcast::prelude::RssFeedLoaderError;

#[derive(Debug, thiserror::Error)]
enum ParsePodcastEpisodeError {
    #[error("missing title attribute")]
    MissingTitle,
    #[error("missing enclosure url attribute")]
    MissingEnclosureUrl,
}

impl super::ChannelItem {
    fn into_podcast_episode(self) -> Result<PodcastEpisodeInput, ParsePodcastEpisodeError> {
        Ok(PodcastEpisodeInput {
            guid: self.guid,
            published_at: self.publication_date,
            title: self.title.ok_or(ParsePodcastEpisodeError::MissingTitle)?,
            description: self.description,
            link: self.link,
            duration: self.itunes_duration,
            file_url: self
                .enclosure_url
                .ok_or(ParsePodcastEpisodeError::MissingEnclosureUrl)?,
            file_size: self.enclosure_length,
            file_type: self.enclosure_type,
        })
    }
}

impl super::Channel {
    fn into_podcast_input(self, feed_url: &str) -> Result<PodcastInput, RssFeedLoaderError> {
        let episodes = self
            .items
            .into_iter()
            .filter_map(|item| {
                item.into_podcast_episode()
                    .inspect_err(|err| tracing::warn!(error = ?err, "unable to parse episode"))
                    .ok()
            })
            .collect::<Vec<_>>();
        Ok(PodcastInput {
            feed_url: self.atom_link_href.unwrap_or_else(|| feed_url.to_string()),
            title: self.title.ok_or(RssFeedLoaderError::MissingPodcastTitle)?,
            description: self.description,
            image_url: self.image_url.or(self.itunes_image_href),
            language: self.language,
            website: self.link,
            episodes,
        })
    }
}

impl entertainarr_domain::podcast::prelude::RssFeedLoader for super::RssClient {
    #[tracing::instrument(skip(self), err(Debug))]
    async fn load(&self, feed_url: &str) -> Result<PodcastInput, RssFeedLoaderError> {
        let res = self
            .client
            .get(feed_url)
            .send()
            .await
            .map_err(|err| RssFeedLoaderError::UnableToFetch(err.into()))?;
        let body = res
            .text()
            .await
            .map_err(|err| RssFeedLoaderError::UnableToFetch(err.into()))?;
        let mut rss =
            super::Rss::from_str(body.as_str()).map_err(RssFeedLoaderError::InvalidStructure)?;
        let channel = rss.channels.pop().ok_or_else(|| {
            RssFeedLoaderError::InvalidStructure(anyhow::anyhow!("empty rss feed"))
        })?;
        channel.into_podcast_input(feed_url)
    }
}

#[cfg(test)]
mod tests {
    use entertainarr_domain::podcast::prelude::RssFeedLoader;

    #[tokio::test]
    async fn should_load_la_derniere() {
        let mut server = mockito::Server::new_async().await;
        let mock = server
            .mock("GET", "/feed.xml")
            .with_body(include_bytes!("../assets/podcast/la-derniere.xml"))
            .create_async()
            .await;

        let client = crate::Config.build().unwrap();
        let _res = client
            .load(&format!("{}/feed.xml", server.url()))
            .await
            .unwrap();

        mock.assert_async().await;
    }
}
