use ai::error::AiResult;
use ai::tool::tools::FunctionCall;
use async_trait::async_trait;
use git::rpc::proto as p;
use git::rpc::proto::blame_service_client::BlameServiceClient;
use serde_json::{Value, json};

use super::helpers::{
    arg_opt_str, arg_str, git_ctx, require_repo_member, rpc_err,
};
use crate::agent::run::AppAgentContext;

pub struct GitBlameTool;

impl GitBlameTool {
    pub fn new() -> Self {
        Self
    }
}

impl Default for GitBlameTool {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl FunctionCall for GitBlameTool {
    type Context = AppAgentContext;

    fn name(&self) -> &'static str {
        "git_blame"
    }

    fn description(&self) -> &'static str {
        "Blame a file to see which commits authored each line range."
    }

    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "workspace": { "type": "string", "description": "Workspace name" },
                "repo": { "type": "string", "description": "Repository name" },
                "path": { "type": "string", "description": "File path to blame" },
                "rev": { "type": "string", "description": "Revision/branch (optional)" },
                "start_line": { "type": "integer", "description": "Start line number (optional)" },
                "end_line": { "type": "integer", "description": "End line number (optional)" }
            },
            "required": ["workspace", "repo", "path"]
        })
    }

    async fn call(
        &self,
        ctx: &mut AppAgentContext,
        args: Value,
    ) -> AiResult<Value> {
        let git = git_ctx(ctx)?;
        let workspace = arg_str(&args, "workspace")?;
        let repo_name = arg_str(&args, "repo")?;
        let path = arg_str(&args, "path")?;
        let rev = arg_opt_str(&args, "rev").map(String::from);
        let start_line = args
            .get("start_line")
            .and_then(|v| v.as_u64())
            .map(|v| v as u32);
        let end_line = args
            .get("end_line")
            .and_then(|v| v.as_u64())
            .map(|v| v as u32);

        let repo =
            require_repo_member(git, ctx.user_id, workspace, repo_name).await?;

        let mut client = BlameServiceClient::new(git.channel.clone());

        if let (Some(start), Some(end)) = (start_line, end_line) {
            let resp = client
                .blame_lines(p::BlameLinesRequest {
                    repo_id: repo.id.to_string(),
                    path: path.to_string(),
                    rev,
                    start_line: start,
                    end_line: end,
                })
                .await
                .map_err(rpc_err)?
                .into_inner();

            let lines: Vec<Value> = resp
                .lines
                .iter()
                .map(|l| {
                    json!({
                        "line_no": l.line_no,
                        "content": l.content,
                        "commit_oid": l.commit_oid.as_ref().map(|o| &o.value),
                    })
                })
                .collect();

            Ok(json!({ "lines": lines, "count": lines.len() }))
        } else {
            let resp = client
                .blame_file(p::BlameFileRequest {
                    repo_id: repo.id.to_string(),
                    path: path.to_string(),
                    rev,
                    options: None,
                })
                .await
                .map_err(rpc_err)?
                .into_inner();

            let hunks: Vec<Value> = resp
                .hunks
                .iter()
                .map(|h| {
                    json!({
                        "commit_oid": h.commit_oid.as_ref().map(|o| &o.value),
                        "final_start_line": h.final_start_line,
                        "final_lines": h.final_lines,
                    })
                })
                .collect();

            Ok(json!({ "hunks": hunks, "count": hunks.len() }))
        }
    }
}
