package tools

import (
	"context"
	"encoding/json"
	"fmt"

	"chat-switchboard/workspace"
)

// RegisterGitTools registers the 5 git-related workspace tools.
// Each declares RequireWorkspace + DenyVisitor availability via workspaceToolBase.
func RegisterGitTools(gitOps *workspace.GitOps) {
	Register(&gitStatusTool{gitOps: gitOps})
	Register(&gitDiffTool{gitOps: gitOps})
	Register(&gitCommitTool{gitOps: gitOps})
	Register(&gitLogTool{gitOps: gitOps})
	Register(&gitBranchTool{gitOps: gitOps})
}

// ── git_status ───────────────────────────────

type gitStatusTool struct {
	workspaceToolBase
	gitOps *workspace.GitOps
}

func (t *gitStatusTool) Definition() ToolDef {
	return ToolDef{
		Name:        "git_status",
		DisplayName: "Git Status",
		Description: "Show git working tree status: branch name, staged/modified/untracked files, ahead/behind remote counts.",
		Category:    "workspace",
		Parameters:  JSONSchema(map[string]interface{}{}, nil),
	}
}

func (t *gitStatusTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
	if err != nil {
		return "", err
	}
	status, err := t.gitOps.Status(ctx, w)
	if err != nil {
		return "", err
	}
	data, _ := json.MarshalIndent(status, "", "  ")
	return string(data), nil
}

// ── git_diff ─────────────────────────────────

type gitDiffTool struct {
	workspaceToolBase
	gitOps *workspace.GitOps
}

func (t *gitDiffTool) Definition() ToolDef {
	return ToolDef{
		Name:        "git_diff",
		DisplayName: "Git Diff",
		Description: "Show git diff for a specific file or all uncommitted changes.",
		Category:    "workspace",
		Parameters: JSONSchema(map[string]interface{}{
			"path": Prop("string", "File path to diff (empty for all changes)"),
		}, nil),
	}
}

func (t *gitDiffTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Path string `json:"path"`
	}
	json.Unmarshal([]byte(argsJSON), &args)

	w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
	if err != nil {
		return "", err
	}
	diff, err := t.gitOps.Diff(ctx, w, args.Path)
	if err != nil {
		return "", err
	}
	if diff == "" {
		return "No changes.", nil
	}
	if len(diff) > 50000 {
		diff = diff[:50000] + "\n... (truncated)"
	}
	return diff, nil
}

// ── git_commit ───────────────────────────────

type gitCommitTool struct {
	workspaceToolBase
	gitOps *workspace.GitOps
}

func (t *gitCommitTool) Definition() ToolDef {
	return ToolDef{
		Name:        "git_commit",
		DisplayName: "Git Commit",
		Description: "Stage and commit files. Stages all changes if no paths specified.",
		Category:    "workspace",
		Parameters: JSONSchema(map[string]interface{}{
			"message": Prop("string", "Commit message"),
			"paths":   PropArray("Specific file paths to stage (empty = stage all)"),
		}, []string{"message"}),
	}
}

func (t *gitCommitTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Message string   `json:"message"`
		Paths   []string `json:"paths"`
	}
	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
		return "", fmt.Errorf("invalid arguments: %w", err)
	}
	if args.Message == "" {
		return "", fmt.Errorf("commit message is required")
	}

	w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
	if err != nil {
		return "", err
	}
	if err := t.gitOps.Commit(ctx, w, args.Message, args.Paths); err != nil {
		return "", err
	}
	return fmt.Sprintf("Committed: %s", args.Message), nil
}

// ── git_log ──────────────────────────────────

type gitLogTool struct {
	workspaceToolBase
	gitOps *workspace.GitOps
}

func (t *gitLogTool) Definition() ToolDef {
	return ToolDef{
		Name:        "git_log",
		DisplayName: "Git Log",
		Description: "Show recent commit history.",
		Category:    "workspace",
		Parameters: JSONSchema(map[string]interface{}{
			"count": Prop("integer", "Number of commits to show (default 20, max 100)"),
		}, nil),
	}
}

func (t *gitLogTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Count int `json:"count"`
	}
	json.Unmarshal([]byte(argsJSON), &args)
	if args.Count <= 0 {
		args.Count = 20
	}
	if args.Count > 100 {
		args.Count = 100
	}

	w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
	if err != nil {
		return "", err
	}
	entries, err := t.gitOps.Log(ctx, w, args.Count)
	if err != nil {
		return "", err
	}
	data, _ := json.MarshalIndent(entries, "", "  ")
	return string(data), nil
}

// ── git_branch ───────────────────────────────

type gitBranchTool struct {
	workspaceToolBase
	gitOps *workspace.GitOps
}

func (t *gitBranchTool) Definition() ToolDef {
	return ToolDef{
		Name:        "git_branch",
		DisplayName: "Git Branch",
		Description: "List branches or switch to a different branch.",
		Category:    "workspace",
		Parameters: JSONSchema(map[string]interface{}{
			"checkout": Prop("string", "Branch name to switch to (empty = list only)"),
		}, nil),
	}
}

func (t *gitBranchTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Checkout string `json:"checkout"`
	}
	json.Unmarshal([]byte(argsJSON), &args)

	w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
	if err != nil {
		return "", err
	}

	if args.Checkout != "" {
		if err := t.gitOps.Checkout(ctx, w, args.Checkout); err != nil {
			return "", err
		}
		return fmt.Sprintf("Switched to branch: %s", args.Checkout), nil
	}

	branches, current, err := t.gitOps.BranchList(ctx, w)
	if err != nil {
		return "", err
	}
	result := map[string]interface{}{
		"current":  current,
		"branches": branches,
	}
	data, _ := json.MarshalIndent(result, "", "  ")
	return string(data), nil
}
