use crate::media::entity::MediaTask;
use crate::podcast::entity::PodcastTask;
use crate::tvshow::entity::TvShowTask;

#[derive(Debug)]
pub enum TargetKind {
    Media,
    Noop,
    Podcast,
    TvShow,
}

impl TargetKind {
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::Media => "media",
            Self::Noop => "noop",
            Self::Podcast => "podcast",
            Self::TvShow => "tvshow",
        }
    }
}

#[derive(Debug)]
pub enum TaskMethod {
    MediaSynchronize,
    MediaSynchronizeAll,
    Noop,
    PodcastSynchronize,
    PodcastSynchronizeAll,
    TvShowSynchronize,
    TvShowSynchronizeAll,
    TvShowSynchronizeLocated,
    TvShowSynchronizeSeasonLocated,
}

impl TaskMethod {
    pub const fn target_kind(&self) -> TargetKind {
        match self {
            Self::MediaSynchronize | Self::MediaSynchronizeAll => TargetKind::Media,
            Self::Noop => TargetKind::Noop,
            Self::PodcastSynchronize | Self::PodcastSynchronizeAll => TargetKind::Podcast,
            Self::TvShowSynchronize
            | Self::TvShowSynchronizeAll
            | Self::TvShowSynchronizeLocated
            | Self::TvShowSynchronizeSeasonLocated => TargetKind::TvShow,
        }
    }

    pub const fn method(&self) -> &'static str {
        match self {
            Self::Noop => "noop",
            Self::MediaSynchronize | Self::PodcastSynchronize | Self::TvShowSynchronize => {
                "synchronize"
            }
            Self::MediaSynchronizeAll
            | Self::PodcastSynchronizeAll
            | Self::TvShowSynchronizeAll => "synchronize-all",
            Self::TvShowSynchronizeLocated => "synchronize-located",
            Self::TvShowSynchronizeSeasonLocated => "synchronize-season-located",
        }
    }
}

#[derive(Debug)]
pub enum TaskStatus {
    Pending,
    Running,
    Completed,
    Failed { message: Option<String> },
}

impl TaskStatus {
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::Pending => "pending",
            Self::Running => "running",
            Self::Completed => "completed",
            Self::Failed { .. } => "failed",
        }
    }

    pub fn message(&self) -> Option<&str> {
        match self {
            Self::Failed { message } => message.as_deref(),
            _ => None,
        }
    }
}

#[derive(Debug)]
pub struct Task {
    pub id: u64,
    pub user_id: Option<u64>,
    pub after: Option<chrono::DateTime<chrono::Utc>>,
    pub payload: TaskPayload,
    pub status: TaskStatus,
    pub retries: u8,
    pub max_retries: u8,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

impl Task {
    pub fn params(&self) -> TaskParams {
        TaskParams {
            user_id: self.user_id,
            after: self.after,
            retry: self.retries,
        }
    }
}

#[derive(Debug, Default)]
pub struct TaskParams {
    pub user_id: Option<u64>,
    pub after: Option<chrono::DateTime<chrono::Utc>>,
    pub retry: u8,
}

impl TaskParams {
    pub fn new(user_id: Option<u64>) -> Self {
        Self {
            user_id,
            after: None,
            retry: 0,
        }
    }
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(tag = "origin")]
pub enum TaskPayload {
    Media(MediaTask),
    Podcast(PodcastTask),
    TvShow(TvShowTask),
    Noop,
}

impl TaskPayload {
    pub const fn target_kind(&self) -> TargetKind {
        match self {
            Self::Media(_) => TargetKind::Media,
            Self::Podcast(_) => TargetKind::Podcast,
            Self::Noop => TargetKind::Noop,
            Self::TvShow(_) => TargetKind::TvShow,
        }
    }

    pub const fn target_id(&self) -> Option<u64> {
        match self {
            Self::Media(_) => None,
            Self::Podcast(PodcastTask::Synchronize { podcast_id }) => Some(*podcast_id),
            Self::Podcast(PodcastTask::SynchronizeAll) => None,
            Self::Noop => None,
            Self::TvShow(TvShowTask::SynchronizeLocated {
                tvshow_id,
                language: _,
            }) => Some(*tvshow_id),
            Self::TvShow(TvShowTask::SynchronizeSeasonLocated(params)) => Some(params.tvshow_id),
            Self::TvShow(TvShowTask::Synchronize { tvshow_id }) => Some(*tvshow_id),
            Self::TvShow(TvShowTask::SynchronizeAll) => None,
        }
    }

    pub const fn method(&self) -> TaskMethod {
        match self {
            Self::Media(MediaTask::Synchronize { .. }) => TaskMethod::MediaSynchronize,
            Self::Media(MediaTask::SynchronizeAll) => TaskMethod::MediaSynchronizeAll,
            Self::Podcast(PodcastTask::Synchronize { .. }) => TaskMethod::PodcastSynchronize,
            Self::Podcast(PodcastTask::SynchronizeAll) => TaskMethod::PodcastSynchronizeAll,
            Self::Noop => TaskMethod::Noop,
            Self::TvShow(TvShowTask::Synchronize { .. }) => TaskMethod::TvShowSynchronize,
            Self::TvShow(TvShowTask::SynchronizeAll) => TaskMethod::TvShowSynchronizeAll,
            Self::TvShow(TvShowTask::SynchronizeLocated { .. }) => {
                TaskMethod::TvShowSynchronizeLocated
            }
            Self::TvShow(TvShowTask::SynchronizeSeasonLocated(_)) => {
                TaskMethod::TvShowSynchronizeSeasonLocated
            }
        }
    }
}
