use crate::media::entity::{MediaTask, VideoStreamResponse};
use crate::media::prelude::TreeWalker;
use crate::task::entity::{TaskParams, TaskPayload};

pub mod entity;
pub mod prelude;

#[derive(Clone, Debug, bon::Builder)]
pub struct MediaService<MR, MS, TR> {
    media_repository: MR,
    media_store: MS,
    task_publisher: TR,
}

impl<MR, MS, TR> prelude::MediaService for MediaService<MR, MS, TR>
where
    MR: prelude::MediaRepository,
    MS: prelude::MediaStore,
    TR: crate::task::prelude::TaskPublisher,
{
    #[tracing::instrument(skip(self), err(Debug))]
    async fn video_stream(&self, media_id: u64) -> anyhow::Result<Option<VideoStreamResponse>> {
        let media = self.media_repository.find_by_id(media_id).await?;
        let Some(media) = media else {
            return Ok(None);
        };
        self.media_store
            .video_stream(&media.disk, media.filepath)
            .await
    }

    #[tracing::instrument(skip(self), err(Debug))]
    async fn synchronize(&self, disk: &str) -> anyhow::Result<()> {
        let Some(mut walker) = self.media_store.traverse(disk, "/")? else {
            tracing::warn!("disk empty");
            return Ok(());
        };

        let mut buffer = Vec::with_capacity(1024);
        while let Some(item) = walker.next().await? {
            buffer.push(item);
            if buffer.capacity() == buffer.len() {
                self.media_repository.upsert(disk, &buffer).await?;
                buffer.clear();
            }
        }

        if !buffer.is_empty() {
            self.media_repository.upsert(disk, &buffer).await?;
        }

        Ok(())
    }

    async fn request_synchronize_all(&self, user_id: Option<u64>) -> anyhow::Result<()> {
        self.task_publisher
            .publish(
                std::iter::once(TaskPayload::Media(MediaTask::SynchronizeAll)),
                &TaskParams::new(user_id),
            )
            .await
    }

    async fn synchronize_all(&self) -> anyhow::Result<()> {
        let disks = self.media_store.disks();
        self.task_publisher
            .publish(
                disks
                    .into_iter()
                    .map(|disk| TaskPayload::Media(MediaTask::Synchronize { disk })),
                &TaskParams::default(),
            )
            .await
    }
}
