use std::time::Duration;

use juniper::graphql_object;
use serde::{Deserialize, Serialize};

use crate::{
    cmd::tag::{TagItem, TagSummary},
    graphql::{
        GraphqlContext,
        cache_helper::{
            MUTABLE_TTL, cached_json, mutable_cache_key, repo_revision,
        },
    },
};

const TAG_TTL: Duration = MUTABLE_TTL;

#[derive(Clone, Serialize, Deserialize)]
pub struct TagGql {
    pub name: String,
    pub oid: String,
    pub target: String,
    pub is_annotated: bool,
    pub message: Option<String>,
    pub tagger: Option<String>,
    pub tagger_email: Option<String>,
}

#[graphql_object(context = GraphqlContext)]
impl TagGql {
    fn name(&self) -> &str {
        &self.name
    }
    fn oid(&self) -> &str {
        &self.oid
    }
    fn target(&self) -> &str {
        &self.target
    }
    fn is_annotated(&self) -> bool {
        self.is_annotated
    }
    fn message(&self) -> Option<&str> {
        self.message.as_deref()
    }
    fn tagger(&self) -> Option<&str> {
        self.tagger.as_deref()
    }
    fn tagger_email(&self) -> Option<&str> {
        self.tagger_email.as_deref()
    }
}

impl From<TagItem> for TagGql {
    fn from(item: TagItem) -> Self {
        TagGql {
            name: item.name,
            oid: item.oid.0,
            target: item.target.0,
            is_annotated: item.is_annotated,
            message: item.message,
            tagger: item.tagger,
            tagger_email: item.tagger_email,
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct TagSummaryGql {
    pub total_count: i32,
}

#[graphql_object(context = GraphqlContext)]
impl TagSummaryGql {
    fn total_count(&self) -> i32 {
        self.total_count
    }
}

impl From<TagSummary> for TagSummaryGql {
    fn from(s: TagSummary) -> Self {
        TagSummaryGql {
            total_count: s.total_count as i32,
        }
    }
}

pub async fn resolve_tags(ctx: &GraphqlContext) -> anyhow::Result<Vec<TagGql>> {
    let revision = repo_revision(ctx).await;
    let key = mutable_cache_key(ctx, "query:git:tags", &[], &revision);
    cached_json(&ctx.cache, &key, TAG_TTL, || {
        let repo = ctx.repo.clone();
        async move {
            let items = tokio::task::spawn_blocking(move || repo.tag_list())
                .await?
                .map_err(|e| anyhow::anyhow!(e))?;
            Ok(items.into_iter().map(TagGql::from).collect())
        }
    })
    .await
}

pub async fn resolve_tag(
    ctx: &GraphqlContext,
    name: String,
) -> anyhow::Result<TagGql> {
    let revision = repo_revision(ctx).await;
    let key = mutable_cache_key(ctx, "query:git:tag", &[&name], &revision);
    cached_json(&ctx.cache, &key, TAG_TTL, || {
        let repo = ctx.repo.clone();
        let tag_name = name.clone();
        async move {
            let item =
                tokio::task::spawn_blocking(move || repo.tag_info(tag_name))
                    .await?
                    .map_err(|e| anyhow::anyhow!(e))?;
            Ok(TagGql::from(item))
        }
    })
    .await
}

pub async fn resolve_tag_summary(
    ctx: &GraphqlContext,
) -> anyhow::Result<TagSummaryGql> {
    let revision = repo_revision(ctx).await;
    let key = mutable_cache_key(ctx, "query:git:tag_summary", &[], &revision);
    cached_json(&ctx.cache, &key, TAG_TTL, || {
        let repo = ctx.repo.clone();
        async move {
            let summary =
                tokio::task::spawn_blocking(move || repo.tag_summary())
                    .await?
                    .map_err(|e| anyhow::anyhow!(e))?;
            Ok(TagSummaryGql::from(summary))
        }
    })
    .await
}
