use super::entity::{Podcast, PodcastInput};
use crate::podcast::entity::{
    FindPodcastEpisodeResponse, FindPodcastResponse, ListPodcastEpisodeParams,
    ListPodcastEpisodeResponse, ListPodcastParams, ListPodcastResponse, PodcastEpisode,
    PodcastEpisodeProgress, PodcastEpisodeProgressInput, PodcastSubscription,
    PodcastSubscriptionUpdate,
};
use crate::task::entity::{Task, TaskMethod};

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait PodcastService: Send + Sync + 'static {
    fn find_podcast_by_id(
        &self,
        user_id: u64,
        podcast_id: u64,
    ) -> impl Future<Output = anyhow::Result<Option<FindPodcastResponse>>> + Send;
    fn find_podcast_episode_by_id(
        &self,
        user_id: u64,
        episode_id: u64,
    ) -> impl Future<Output = anyhow::Result<Option<FindPodcastEpisodeResponse>>> + Send;

    fn list_podcast<'a>(
        &self,
        params: ListPodcastParams<'a>,
    ) -> impl Future<Output = anyhow::Result<ListPodcastResponse>> + Send;
    fn list_podcast_episode<'a>(
        &self,
        params: ListPodcastEpisodeParams<'a>,
    ) -> impl Future<Output = anyhow::Result<ListPodcastEpisodeResponse>> + Send;
    fn subscribe(
        &self,
        user_id: u64,
        feed_url: &str,
    ) -> impl Future<Output = anyhow::Result<Podcast>> + Send;
    fn unsubscribe(
        &self,
        user_id: u64,
        podcast_id: u64,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn find_subscription_by_id(
        &self,
        user_id: u64,
        podcast_id: u64,
    ) -> impl Future<Output = anyhow::Result<Option<PodcastSubscription>>> + Send;
    fn update_subscription_by_id(
        &self,
        user_id: u64,
        podcast_id: u64,
        body: PodcastSubscriptionUpdate,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;

    fn delete_podcast_episode_progressions(
        &self,
        user_id: u64,
        episode_ids: &[u64],
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn upsert_podcast_episode_progressions(
        &self,
        inputs: &[PodcastEpisodeProgressInput],
    ) -> impl Future<Output = anyhow::Result<Vec<PodcastEpisodeProgress>>> + Send;

    fn request_synchronize_all(
        &self,
        user_id: Option<u64>,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn request_synchronize<It: Iterator<Item = u64> + Send + 'static>(
        &self,
        user_id: Option<u64>,
        podcast_ids: It,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn synchronize_all(
        &self,
        user_id: Option<u64>,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn synchronize(
        &self,
        user_id: Option<u64>,
        podcast_id: u64,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
}

#[derive(Debug, thiserror::Error)]
pub enum RssFeedLoaderError {
    #[error("unable to fetch rss feed")]
    UnableToFetch(#[source] anyhow::Error),
    #[error("rss file invalid")]
    InvalidStructure(#[source] anyhow::Error),
    #[error("rss file invalid, missing podcast title")]
    MissingPodcastTitle,
    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait RssFeedLoader: Send + Sync + 'static {
    fn load(
        &self,
        feed_url: &str,
    ) -> impl Future<Output = Result<PodcastInput, RssFeedLoaderError>> + Send;
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait PodcastTaskRepository: Send + Sync + 'static {
    fn last(
        &self,
        method: TaskMethod,
        podcast_ids: &[u64],
    ) -> impl Future<Output = anyhow::Result<Vec<Task>>> + Send;
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait PodcastRepository: Send + Sync + 'static {
    fn find_by_id(
        &self,
        podcast_id: u64,
    ) -> impl Future<Output = anyhow::Result<Option<Podcast>>> + Send;
    fn find_by_feed_url(
        &self,
        feed_url: &str,
    ) -> impl Future<Output = anyhow::Result<Option<Podcast>>> + Send;
    fn list<'a>(
        &self,
        params: ListPodcastParams<'a>,
    ) -> impl Future<Output = anyhow::Result<Vec<Podcast>>> + Send;
    fn upsert(&self, entity: &PodcastInput)
    -> impl Future<Output = anyhow::Result<Podcast>> + Send;
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait PodcastSubscriptionRepository: Send + Sync + 'static {
    fn find(
        &self,
        user_id: u64,
        podcast_id: u64,
    ) -> impl Future<Output = anyhow::Result<Option<PodcastSubscription>>> + Send;
    fn list(
        &self,
        user_id: u64,
        podcast_ids: &[u64],
    ) -> impl Future<Output = anyhow::Result<Vec<PodcastSubscription>>> + Send;
    fn create(
        &self,
        user_id: u64,
        subscription_id: u64,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn update(
        &self,
        user_id: u64,
        podcast_id: u64,
        body: PodcastSubscriptionUpdate,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn delete(
        &self,
        user_id: u64,
        subscription_id: u64,
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait PodcastEpisodeRepository: Send + Sync + 'static {
    fn find_by_id(
        &self,
        episode_id: u64,
    ) -> impl Future<Output = anyhow::Result<Option<PodcastEpisode>>> + Send;
    fn list<'a>(
        &self,
        params: ListPodcastEpisodeParams<'a>,
    ) -> impl Future<Output = anyhow::Result<Vec<PodcastEpisode>>> + Send;
    fn delete_progressions(
        &self,
        user_id: u64,
        episode_ids: &[u64],
    ) -> impl Future<Output = anyhow::Result<()>> + Send;
    fn upsert_progressions(
        &self,
        inputs: &[PodcastEpisodeProgressInput],
    ) -> impl Future<Output = anyhow::Result<Vec<PodcastEpisodeProgress>>> + Send;
    fn list_progressions(
        &self,
        user_id: u64,
        episode_ids: &[u64],
    ) -> impl Future<Output = anyhow::Result<Vec<PodcastEpisodeProgress>>> + Send;
}
