//! Git blob tools — raw object-level operations on blob OIDs.

use super::ctx::GitToolCtx;
use agent::{ToolDefinition, ToolHandler, ToolParam, ToolRegistry, ToolSchema};
use base64::Engine;
use std::collections::HashMap;

async fn git_blob_info_exec(
    ctx: GitToolCtx,
    args: serde_json::Value,
) -> Result<serde_json::Value, String> {
    let p: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).map_err(|e| e.to_string())?;
    let project_name = p.get("project_name").and_then(|v| v.as_str()).ok_or("missing project_name")?;
    let repo_name = p.get("repo_name").and_then(|v| v.as_str()).ok_or("missing repo_name")?;
    let oid = p.get("oid").and_then(|v| v.as_str()).ok_or("missing oid")?;

    let domain = ctx.open_repo(project_name, repo_name).await?;
    let commit_oid = resolve_oid(&domain, oid)?;
    let info = domain.blob_get(&commit_oid).map_err(|e| e.to_string())?;

    Ok(serde_json::json!({
        "oid": info.oid.to_string(),
        "size": info.size,
        "is_binary": info.is_binary,
    }))
}

async fn git_blob_exists_exec(
    ctx: GitToolCtx,
    args: serde_json::Value,
) -> Result<serde_json::Value, String> {
    let p: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).map_err(|e| e.to_string())?;
    let project_name = p.get("project_name").and_then(|v| v.as_str()).ok_or("missing project_name")?;
    let repo_name = p.get("repo_name").and_then(|v| v.as_str()).ok_or("missing repo_name")?;
    let oid = p.get("oid").and_then(|v| v.as_str()).ok_or("missing oid")?;

    let domain = ctx.open_repo(project_name, repo_name).await?;
    let commit_oid = resolve_oid(&domain, oid)?;
    let exists = domain.blob_exists(&commit_oid);

    Ok(serde_json::json!({ "oid": commit_oid.to_string(), "exists": exists }))
}

async fn git_blob_content_exec(
    ctx: GitToolCtx,
    args: serde_json::Value,
) -> Result<serde_json::Value, String> {
    let p: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).map_err(|e| e.to_string())?;
    let project_name = p.get("project_name").and_then(|v| v.as_str()).ok_or("missing project_name")?;
    let repo_name = p.get("repo_name").and_then(|v| v.as_str()).ok_or("missing repo_name")?;
    let oid = p.get("oid").and_then(|v| v.as_str()).ok_or("missing oid")?;
    let max_size = p.get("max_size").and_then(|v| v.as_u64()).unwrap_or(1_048_576) as usize; // 1MB default

    let domain = ctx.open_repo(project_name, repo_name).await?;
    let commit_oid = resolve_oid(&domain, oid)?;
    let blob = domain.blob_content(&commit_oid).map_err(|e| e.to_string())?;

    if blob.size > max_size {
        return Err(format!(
            "blob too large ({} bytes), max {} bytes. Use a smaller max_size or retrieve the raw OID.",
            blob.size, max_size
        ));
    }

    let (content, is_binary) = if blob.is_binary {
        (base64::engine::general_purpose::STANDARD.encode(&blob.content), true)
    } else {
        (String::from_utf8_lossy(&blob.content).to_string(), false)
    };

    Ok(serde_json::json!({
        "oid": blob.oid.to_string(),
        "size": blob.size,
        "is_binary": is_binary,
        "content": content,
    }))
}

async fn git_blob_create_exec(
    ctx: GitToolCtx,
    args: serde_json::Value,
) -> Result<serde_json::Value, String> {
    let p: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).map_err(|e| e.to_string())?;
    let project_name = p.get("project_name").and_then(|v| v.as_str()).ok_or("missing project_name")?;
    let repo_name = p.get("repo_name").and_then(|v| v.as_str()).ok_or("missing repo_name")?;
    let content = p.get("content").and_then(|v| v.as_str()).ok_or("missing content")?;
    let encoding = p.get("encoding").and_then(|v| v.as_str()).unwrap_or("utf-8");

    let data = match encoding {
        "base64" => base64::engine::general_purpose::STANDARD
            .decode(content)
            .map_err(|e| format!("invalid base64: {}", e))?,
        "utf-8" => content.as_bytes().to_vec(),
        other => return Err(format!("unsupported encoding '{}'. Use 'utf-8' or 'base64'.", other)),
    };

    let domain = ctx.open_repo(project_name, repo_name).await?;
    let oid = domain.blob_create(&data).map_err(|e| e.to_string())?;
    let info = domain.blob_get(&oid).map_err(|e| e.to_string())?;

    Ok(serde_json::json!({
        "oid": info.oid.to_string(),
        "size": info.size,
        "is_binary": info.is_binary,
    }))
}

fn resolve_oid(
    domain: &git::GitDomain,
    rev: &str,
) -> Result<git::commit::types::CommitOid, String> {
    if rev.len() == 40 && rev.chars().all(|c| c.is_ascii_hexdigit()) {
        Ok(git::commit::types::CommitOid::new(rev))
    } else {
        domain.commit_get_prefix(rev).map_err(|e| e.to_string()).map(|m| m.oid)
    }
}

pub fn register_git_tools(registry: &mut ToolRegistry) {
    // git_blob_info
    let p = HashMap::from([
        ("project_name".into(), ToolParam {
            name: "project_name".into(), param_type: "string".into(),
            description: Some("Project name (slug)".into()),
            required: true, properties: None, items: None,
        }),
        ("repo_name".into(), ToolParam {
            name: "repo_name".into(), param_type: "string".into(),
            description: Some("Repository name".into()),
            required: true, properties: None, items: None,
        }),
        ("oid".into(), ToolParam {
            name: "oid".into(), param_type: "string".into(),
            description: Some("Blob OID (full 40-char hex or short prefix)".into()),
            required: true, properties: None, items: None,
        }),
    ]);
    let schema = ToolSchema {
        schema_type: "object".into(),
        properties: Some(p),
        required: Some(vec!["project_name".into(), "repo_name".into(), "oid".into()]),
    };
    registry.register(
        ToolDefinition::new("git_blob_info")
            .description("Get metadata about a git blob by its OID. Returns size and whether the blob is binary.")
            .parameters(schema),
        ToolHandler::new(|ctx, args| {
            let gctx = GitToolCtx::new(ctx);
            Box::pin(async move {
                git_blob_info_exec(gctx, args).await.map_err(agent::ToolError::ExecutionError)
            })
        }),
    );

    // git_blob_exists
    let p = HashMap::from([
        ("project_name".into(), ToolParam {
            name: "project_name".into(), param_type: "string".into(),
            description: Some("Project name (slug)".into()),
            required: true, properties: None, items: None,
        }),
        ("repo_name".into(), ToolParam {
            name: "repo_name".into(), param_type: "string".into(),
            description: Some("Repository name".into()),
            required: true, properties: None, items: None,
        }),
        ("oid".into(), ToolParam {
            name: "oid".into(), param_type: "string".into(),
            description: Some("Blob OID to check".into()),
            required: true, properties: None, items: None,
        }),
    ]);
    let schema = ToolSchema {
        schema_type: "object".into(),
        properties: Some(p),
        required: Some(vec!["project_name".into(), "repo_name".into(), "oid".into()]),
    };
    registry.register(
        ToolDefinition::new("git_blob_exists")
            .description("Check whether a git blob exists in the repository by its OID.")
            .parameters(schema),
        ToolHandler::new(|ctx, args| {
            let gctx = GitToolCtx::new(ctx);
            Box::pin(async move {
                git_blob_exists_exec(gctx, args).await.map_err(agent::ToolError::ExecutionError)
            })
        }),
    );

    // git_blob_content
    let p = HashMap::from([
        ("project_name".into(), ToolParam {
            name: "project_name".into(), param_type: "string".into(),
            description: Some("Project name (slug)".into()),
            required: true, properties: None, items: None,
        }),
        ("repo_name".into(), ToolParam {
            name: "repo_name".into(), param_type: "string".into(),
            description: Some("Repository name".into()),
            required: true, properties: None, items: None,
        }),
        ("oid".into(), ToolParam {
            name: "oid".into(), param_type: "string".into(),
            description: Some("Blob OID to retrieve content for".into()),
            required: true, properties: None, items: None,
        }),
        ("max_size".into(), ToolParam {
            name: "max_size".into(), param_type: "integer".into(),
            description: Some("Maximum blob size in bytes (default: 1MB)".into()),
            required: false, properties: None, items: None,
        }),
    ]);
    let schema = ToolSchema {
        schema_type: "object".into(),
        properties: Some(p),
        required: Some(vec!["project_name".into(), "repo_name".into(), "oid".into()]),
    };
    registry.register(
        ToolDefinition::new("git_blob_content")
            .description("Retrieve the raw content of a git blob by its OID. Binary content is base64-encoded. Limits to 1MB by default.")
            .parameters(schema),
        ToolHandler::new(|ctx, args| {
            let gctx = GitToolCtx::new(ctx);
            Box::pin(async move {
                git_blob_content_exec(gctx, args).await.map_err(agent::ToolError::ExecutionError)
            })
        }),
    );

    // git_blob_create
    let p = HashMap::from([
        ("project_name".into(), ToolParam {
            name: "project_name".into(), param_type: "string".into(),
            description: Some("Project name (slug)".into()),
            required: true, properties: None, items: None,
        }),
        ("repo_name".into(), ToolParam {
            name: "repo_name".into(), param_type: "string".into(),
            description: Some("Repository name".into()),
            required: true, properties: None, items: None,
        }),
        ("content".into(), ToolParam {
            name: "content".into(), param_type: "string".into(),
            description: Some("Blob content (utf-8 string or base64-encoded bytes)".into()),
            required: true, properties: None, items: None,
        }),
        ("encoding".into(), ToolParam {
            name: "encoding".into(), param_type: "string".into(),
            description: Some("Encoding of content: 'utf-8' (default) or 'base64'".into()),
            required: false, properties: None, items: None,
        }),
    ]);
    let schema = ToolSchema {
        schema_type: "object".into(),
        properties: Some(p),
        required: Some(vec!["project_name".into(), "repo_name".into(), "content".into()]),
    };
    registry.register(
        ToolDefinition::new("git_blob_create")
            .description("Create a new git blob in the repository. Writes the raw content to the object database and returns the new blob OID. Supports both utf-8 text and base64-encoded binary content.")
            .parameters(schema),
        ToolHandler::new(|ctx, args| {
            let gctx = GitToolCtx::new(ctx);
            Box::pin(async move {
                git_blob_create_exec(gctx, args).await.map_err(agent::ToolError::ExecutionError)
            })
        }),
    );
}
