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

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

pub struct GitMergeBaseTool;

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

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

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

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

    fn description(&self) -> &'static str {
        "Find the common ancestor (merge base) of two commits."
    }

    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "workspace": { "type": "string", "description": "Workspace name" },
                "repo": { "type": "string", "description": "Repository name" },
                "oid_a": { "type": "string", "description": "First commit OID" },
                "oid_b": { "type": "string", "description": "Second commit OID" }
            },
            "required": ["workspace", "repo", "oid_a", "oid_b"]
        })
    }

    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 oid_a = arg_str(&args, "oid_a")?;
        let oid_b = arg_str(&args, "oid_b")?;

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

        let mut client = MergeServiceClient::new(git.channel.clone());
        let resp = client
            .merge_base(p::MergeBaseRequest {
                repo_id: repo.id.to_string(),
                oid_a: Some(p::ObjectId {
                    value: oid_a.to_string(),
                }),
                oid_b: Some(p::ObjectId {
                    value: oid_b.to_string(),
                }),
            })
            .await
            .map_err(rpc_err)?
            .into_inner();

        Ok(json!({ "base_oid": resp.base_oid.map(|o| o.value) }))
    }
}

pub struct GitMergeAnalysisTool;

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

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

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

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

    fn description(&self) -> &'static str {
        "Analyze whether two commits can be merged (fast-forward, normal, up-to-date, etc)."
    }

    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "workspace": { "type": "string", "description": "Workspace name" },
                "repo": { "type": "string", "description": "Repository name" },
                "oid_a": { "type": "string", "description": "First commit OID" },
                "oid_b": { "type": "string", "description": "Second commit OID" }
            },
            "required": ["workspace", "repo", "oid_a", "oid_b"]
        })
    }

    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 oid_a = arg_str(&args, "oid_a")?;
        let oid_b = arg_str(&args, "oid_b")?;

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

        let mut client = MergeServiceClient::new(git.channel.clone());
        let resp = client
            .merge_analysis(p::MergeAnalysisRequest {
                repo_id: repo.id.to_string(),
                oid_a: Some(p::ObjectId {
                    value: oid_a.to_string(),
                }),
                oid_b: Some(p::ObjectId {
                    value: oid_b.to_string(),
                }),
            })
            .await
            .map_err(rpc_err)?
            .into_inner();

        let analysis = resp
            .analysis
            .ok_or_else(|| AiError::Response("no analysis".to_string()))?;
        let pref = resp
            .preference
            .ok_or_else(|| AiError::Response("no preference".to_string()))?;

        // Determine overall status
        let status = if analysis.is_up_to_date {
            "up_to_date"
        } else if analysis.is_fast_forward {
            "fast_forward"
        } else if analysis.is_normal {
            "normal_merge"
        } else if analysis.is_unborn {
            "unborn"
        } else {
            "none"
        };

        Ok(json!({
            "status": status,
            "analysis": {
                "is_none": analysis.is_none,
                "is_normal": analysis.is_normal,
                "is_up_to_date": analysis.is_up_to_date,
                "is_fast_forward": analysis.is_fast_forward,
                "is_unborn": analysis.is_unborn,
            },
            "preference": {
                "is_none": pref.is_none,
                "is_no_fast_forward": pref.is_no_fast_forward,
                "is_fastforward_only": pref.is_fastforward_only,
            },
        }))
    }
}

pub struct GitMergeIsConflictedTool;

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

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

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

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

    fn description(&self) -> &'static str {
        "Check if the repository is currently in a conflicted merge state."
    }

    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "workspace": { "type": "string", "description": "Workspace name" },
                "repo": { "type": "string", "description": "Repository name" }
            },
            "required": ["workspace", "repo"]
        })
    }

    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 repo =
            require_repo_member(git, ctx.user_id, workspace, repo_name).await?;

        let mut client = MergeServiceClient::new(git.channel.clone());
        let resp = client
            .merge_is_conflicted(p::MergeIsConflictedRequest {
                repo_id: repo.id.to_string(),
            })
            .await
            .map_err(rpc_err)?
            .into_inner();

        Ok(json!({ "is_conflicted": resp.is_conflicted }))
    }
}
