use std::time::Duration;

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

use crate::{
    cmd::{
        oid::ObjectId,
        tree::{TreeEntry, TreeInfo, TreeKind},
    },
    graphql::{
        GraphqlContext,
        cache_helper::{IMMUTABLE_TTL, cache_key, cached_json},
    },
};

const TREE_TTL: Duration = IMMUTABLE_TTL;

#[derive(Clone, Serialize, Deserialize)]
pub struct TreeInfoGql {
    pub oid: String,
    pub entry_count: i32,
    pub is_empty: bool,
}

#[graphql_object(context = GraphqlContext)]
impl TreeInfoGql {
    fn oid(&self) -> &str {
        &self.oid
    }
    fn entry_count(&self) -> i32 {
        self.entry_count
    }
    fn is_empty(&self) -> bool {
        self.is_empty
    }
}

impl From<TreeInfo> for TreeInfoGql {
    fn from(info: TreeInfo) -> Self {
        TreeInfoGql {
            oid: info.oid.0,
            entry_count: info.entry_count as i32,
            is_empty: info.is_empty,
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct TreeEntryGql {
    pub name: String,
    pub oid: String,
    pub kind: String,
    pub filemode: i32,
    pub is_binary: bool,
    pub is_lfs: bool,
}

#[graphql_object(context = GraphqlContext)]
impl TreeEntryGql {
    fn name(&self) -> &str {
        &self.name
    }
    fn oid(&self) -> &str {
        &self.oid
    }
    fn kind(&self) -> &str {
        &self.kind
    }
    fn filemode(&self) -> i32 {
        self.filemode
    }
    fn is_binary(&self) -> bool {
        self.is_binary
    }
    fn is_lfs(&self) -> bool {
        self.is_lfs
    }
}

impl From<TreeEntry> for TreeEntryGql {
    fn from(entry: TreeEntry) -> Self {
        TreeEntryGql {
            name: entry.name,
            oid: entry.oid.0,
            kind: match entry.kind {
                TreeKind::Blob => "blob".to_string(),
                TreeKind::Tree => "tree".to_string(),
                TreeKind::LfsPointer => "lfs_pointer".to_string(),
            },
            filemode: entry.filemode as i32,
            is_binary: entry.is_binary,
            is_lfs: entry.is_lfs,
        }
    }
}

pub async fn resolve_tree(
    ctx: &GraphqlContext,
    oid: String,
) -> anyhow::Result<TreeInfoGql> {
    let key = cache_key("query:git:tree", &[&oid]);
    cached_json(&ctx.cache, &key, TREE_TTL, || {
        let repo = ctx.repo.clone();
        let oid_obj = ObjectId::new(&oid);
        async move {
            let info =
                tokio::task::spawn_blocking(move || repo.resolve_tree(oid_obj))
                    .await?
                    .map_err(|e| anyhow::anyhow!(e))?;
            Ok(TreeInfoGql::from(info))
        }
    })
    .await
}

pub async fn resolve_tree_entries(
    ctx: &GraphqlContext,
    tree_oid: String,
) -> anyhow::Result<Vec<TreeEntryGql>> {
    let key = cache_key("query:git:tree_entries", &[&tree_oid]);
    cached_json(&ctx.cache, &key, TREE_TTL, || {
        let repo = ctx.repo.clone();
        let oid_obj = ObjectId::new(&tree_oid);
        async move {
            let entries =
                tokio::task::spawn_blocking(move || repo.tree_entries(oid_obj))
                    .await?
                    .map_err(|e| anyhow::anyhow!(e))?;
            Ok(entries.into_iter().map(TreeEntryGql::from).collect())
        }
    })
    .await
}
