use std::time::Duration;

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

use crate::{
    cmd::branch::{branch_list::BranchListItem, branch_summary::BranchSummary},
    graphql::{
        GraphqlContext,
        cache_helper::{
            MUTABLE_TTL, cached_json, mutable_cache_key, repo_revision,
        },
    },
};

const BRANCH_TTL: Duration = MUTABLE_TTL;

#[derive(Clone, Serialize, Deserialize)]
pub struct BranchGql {
    pub name: String,
    pub oid: String,
    pub is_head: bool,
    pub is_remote: bool,
    pub is_current: bool,
    pub upstream: Option<String>,
}

#[graphql_object(context = GraphqlContext)]
impl BranchGql {
    fn name(&self) -> &str {
        &self.name
    }
    fn oid(&self) -> &str {
        &self.oid
    }
    fn is_head(&self) -> bool {
        self.is_head
    }
    fn is_remote(&self) -> bool {
        self.is_remote
    }
    fn is_current(&self) -> bool {
        self.is_current
    }
    fn upstream(&self) -> Option<&str> {
        self.upstream.as_deref()
    }
}

impl From<BranchListItem> for BranchGql {
    fn from(item: BranchListItem) -> Self {
        BranchGql {
            name: item.name,
            oid: item.oid.0,
            is_head: item.is_head,
            is_remote: item.is_remote,
            is_current: item.is_current,
            upstream: item.upstream,
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct BranchSummaryGql {
    pub local_count: i32,
    pub remote_count: i32,
    pub all_count: i32,
}

#[graphql_object(context = GraphqlContext)]
impl BranchSummaryGql {
    fn local_count(&self) -> i32 {
        self.local_count
    }
    fn remote_count(&self) -> i32 {
        self.remote_count
    }
    fn all_count(&self) -> i32 {
        self.all_count
    }
}

impl From<BranchSummary> for BranchSummaryGql {
    fn from(s: BranchSummary) -> Self {
        BranchSummaryGql {
            local_count: s.local_count as i32,
            remote_count: s.remote_count as i32,
            all_count: s.all_count as i32,
        }
    }
}

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

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

pub async fn resolve_head_branch(
    ctx: &GraphqlContext,
) -> anyhow::Result<BranchGql> {
    let revision = repo_revision(ctx).await;
    let key = mutable_cache_key(ctx, "query:git:head_branch", &[], &revision);
    cached_json(&ctx.cache, &key, BRANCH_TTL, || {
        let repo1 = ctx.repo.clone();
        let repo2 = ctx.repo.clone();
        async move {
            let head_name =
                tokio::task::spawn_blocking(move || repo1.branch_head_name())
                    .await?
                    .map_err(|e| anyhow::anyhow!(e))?;
            let branch = tokio::task::spawn_blocking(move || {
                repo2.branch_info(head_name)
            })
            .await?
            .map_err(|e| anyhow::anyhow!(e))?;
            Ok(BranchGql::from(branch))
        }
    })
    .await
}

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