use std::time::Duration;

use juniper::graphql_object;

use crate::{
    cmd::{
        blob::{blob_load::BlobLoadParams, blob_size::BlobSizeParams},
        oid::ObjectId,
    },
    graphql::{
        GraphqlContext,
        cache_helper::{IMMUTABLE_TTL, cache_key, cached_json},
    },
};

const BLOB_SIZE_TTL: Duration = IMMUTABLE_TTL;

#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct BlobResult {
    pub oid: String,
    pub size: i32,
    pub is_binary: bool,
}

#[graphql_object(context = GraphqlContext)]
impl BlobResult {
    fn oid(&self) -> &str {
        &self.oid
    }
    fn size(&self) -> i32 {
        self.size
    }
    fn is_binary(&self) -> bool {
        self.is_binary
    }
}

pub async fn resolve_blob_size(
    ctx: &GraphqlContext,
    oid: String,
) -> anyhow::Result<i32> {
    let key = cache_key("query:git:blob_size", &[&oid]);
    cached_json(&ctx.cache, &key, BLOB_SIZE_TTL, || {
        let repo = ctx.repo.clone();
        let oid_obj = ObjectId::new(&oid);
        async move {
            let size = tokio::task::spawn_blocking(move || {
                repo.blob_size(&BlobSizeParams {
                    id: oid_obj,
                    path: String::new(),
                })
            })
            .await?
            .map_err(|e| anyhow::anyhow!(e))?;
            Ok(size as i32)
        }
    })
    .await
}

pub async fn resolve_blob(
    ctx: &GraphqlContext,
    oid: String,
) -> anyhow::Result<BlobResult> {
    let repo = ctx.repo.clone();
    let oid_obj = ObjectId::new(&oid);
    let result = tokio::task::spawn_blocking(move || {
        let size = repo.blob_size(&BlobSizeParams {
            id: oid_obj.clone(),
            path: String::new(),
        })?;
        let loaded = repo.blob_load(&BlobLoadParams {
            id: oid_obj,
            path: String::new(),
        })?;
        Ok::<(u64, Vec<u8>, String), crate::errors::GitError>((
            size,
            loaded.blob,
            oid,
        ))
    })
    .await?
    .map_err(|e| anyhow::anyhow!(e))?;

    let (size, bytes, oid_str) = result;
    let is_binary = bytes.contains(&0);

    Ok(BlobResult {
        oid: oid_str,
        size: size as i32,
        is_binary,
    })
}
