use std::time::Duration;

use anyhow::Context;
use entertainarr_domain::media::prelude::MediaService;
use entertainarr_domain::podcast::prelude::PodcastService;
use entertainarr_domain::task::entity::{Task, TaskPayload};
use entertainarr_domain::task::prelude::TaskService;
use entertainarr_domain::tvshow::prelude::TvShowService;
use tokio_util::sync::CancellationToken;

mod media;
mod podcast;
mod tvshow;

#[derive(serde::Deserialize)]
pub struct Config {
    #[serde(default = "Config::default_heartbeat")]
    pub heartbeat: Duration,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            heartbeat: Self::default_heartbeat(),
        }
    }
}

impl Config {
    pub const fn default_heartbeat() -> Duration {
        Duration::from_secs(10)
    }

    pub fn build(self) -> Runner<(), (), (), ()> {
        Runner {
            media_service: (),
            podcast_service: (),
            task_service: (),
            tvshow_service: (),
            heartbeat: self.heartbeat,
        }
    }
}

pub struct Runner<MS, PS, TS, TSS> {
    media_service: MS,
    podcast_service: PS,
    task_service: TS,
    tvshow_service: TSS,
    heartbeat: Duration,
}

impl<MS, PS, TS, TSS> Runner<MS, PS, TS, TSS> {
    pub fn with_media_service<MS2>(self, media_service: MS2) -> Runner<MS2, PS, TS, TSS> {
        Runner {
            media_service,
            podcast_service: self.podcast_service,
            task_service: self.task_service,
            tvshow_service: self.tvshow_service,
            heartbeat: self.heartbeat,
        }
    }
    pub fn with_podcast_service<PS2>(self, podcast_service: PS2) -> Runner<MS, PS2, TS, TSS> {
        Runner {
            media_service: self.media_service,
            podcast_service,
            task_service: self.task_service,
            tvshow_service: self.tvshow_service,
            heartbeat: self.heartbeat,
        }
    }
    pub fn with_task_service<TS2>(self, task_service: TS2) -> Runner<MS, PS, TS2, TSS> {
        Runner {
            media_service: self.media_service,
            podcast_service: self.podcast_service,
            task_service,
            tvshow_service: self.tvshow_service,
            heartbeat: self.heartbeat,
        }
    }
    pub fn with_tvshow_service<TSS2>(self, tvshow_service: TSS2) -> Runner<MS, PS, TS, TSS2> {
        Runner {
            media_service: self.media_service,
            podcast_service: self.podcast_service,
            task_service: self.task_service,
            tvshow_service,
            heartbeat: self.heartbeat,
        }
    }
}

impl<MS, PS, TS, TSS> Runner<MS, PS, TS, TSS>
where
    MS: MediaService,
    PS: PodcastService,
    TS: TaskService,
    TSS: TvShowService,
{
    #[tracing::instrument(
        parent = None,
        skip_all,
        fields(
            task_id = task.id,
            target_kind = ?task.payload.target_kind(),
            method = ?task.payload.method()
        ),
        err(Debug)
    )]
    async fn handle(&self, task: Task) -> anyhow::Result<()> {
        tracing::info!("processing");
        let params = task.params();
        match task.payload {
            TaskPayload::Media(inner) => self.handle_media(inner, &params).await,
            TaskPayload::Noop => Ok(()),
            TaskPayload::Podcast(inner) => self.handle_podcast(inner, &params).await,
            TaskPayload::TvShow(inner) => self.handle_tvshow(inner, &params).await,
        }
        .inspect(|_| tracing::info!("completed"))
        .inspect_err(|err| tracing::warn!(error = ?err, "task failed processing"))
    }

    async fn iterate(&self) -> anyhow::Result<()> {
        while let Some(task) = self.task_service.start_task().await? {
            let task_id = task.id;
            match self.handle(task).await {
                Ok(_) => {
                    self.task_service.complete_task(task_id).await?;
                }
                Err(err) => {
                    self.task_service.fail_task(task_id, err).await?;
                }
            }
        }
        Ok(())
    }

    async fn run(self, token: CancellationToken) {
        while !token.is_cancelled() {
            match self.iterate().await {
                Ok(_) => {
                    tracing::debug!("nothing left in the queue, waiting for the next heartbeat");
                    tokio::time::sleep(self.heartbeat).await;
                }
                Err(err) => {
                    tracing::error!(error = ?err, "failed iterating over tasks");
                }
            }
        }
    }
}

impl<MS, PS, TS, TSS> Runner<MS, PS, TS, TSS>
where
    MS: MediaService,
    PS: PodcastService,
    TS: TaskService,
    TSS: TvShowService,
{
    pub fn start(self) -> Worker {
        let token = CancellationToken::new();
        Worker {
            handler: tokio::task::spawn(self.run(token.clone())),
            token,
        }
    }
}

pub struct Worker {
    handler: tokio::task::JoinHandle<()>,
    token: CancellationToken,
}

impl Worker {
    pub async fn shutdown(self) -> anyhow::Result<()> {
        self.token.cancel();
        self.handler.await.context("worker failed shutting down")
    }
}
