Add Github wehbook management

Change-Id: I4b7a23a77838f345d65adf51877fab5978bd6055
diff --git a/server/git/git.go b/server/git/git.go
index 4cd7508..29cee16 100644
--- a/server/git/git.go
+++ b/server/git/git.go
@@ -69,6 +69,11 @@
 	RefreshAgentClone(agentName string) error
 	CleanupAgentClone(agentName string) error
 	CleanupAllClones() error
+
+	// Webhook operations (GitHub only)
+	ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error)
+	CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error)
+	UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error)
 }
 
 // Status represents the current state of the repository
@@ -679,6 +684,35 @@
 	return g.cloneManager.CleanupAllClones()
 }
 
+// Webhook operations (GitHub only)
+
+// ListWebhooks lists existing webhooks for the repository
+func (g *Git) ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error) {
+	webhookProvider, ok := g.prProvider.(WebhookProvider)
+	if !ok {
+		return nil, &GitError{Command: "ListWebhooks", Output: "webhook operations not supported by current provider"}
+	}
+	return webhookProvider.ListWebhooks(ctx)
+}
+
+// CreateWebhook creates a new webhook for the repository
+func (g *Git) CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+	webhookProvider, ok := g.prProvider.(WebhookProvider)
+	if !ok {
+		return nil, &GitError{Command: "CreateWebhook", Output: "webhook operations not supported by current provider"}
+	}
+	return webhookProvider.CreateWebhook(ctx, webhookURL, secret)
+}
+
+// UpdateWebhook updates an existing webhook
+func (g *Git) UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+	webhookProvider, ok := g.prProvider.(WebhookProvider)
+	if !ok {
+		return nil, &GitError{Command: "UpdateWebhook", Output: "webhook operations not supported by current provider"}
+	}
+	return webhookProvider.UpdateWebhook(ctx, webhookID, webhookURL, secret)
+}
+
 // Helper methods
 
 func (g *Git) runCommand(cmd *exec.Cmd, command string) error {
@@ -982,3 +1016,10 @@
 	ClosePullRequest(ctx context.Context, id string) error
 	MergePullRequest(ctx context.Context, id string, options MergePullRequestOptions) error
 }
+
+// WebhookProvider defines the interface for webhook operations
+type WebhookProvider interface {
+	ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error)
+	CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error)
+	UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error)
+}
diff --git a/server/git/github.go b/server/git/github.go
index 1d37b1d..a73c4c6 100644
--- a/server/git/github.go
+++ b/server/git/github.go
@@ -112,6 +112,29 @@
 	MergeMethod string `json:"merge_method,omitempty"`
 }
 
+// GitHub webhook API types
+type githubWebhookRequest struct {
+	Name   string                `json:"name"`
+	Active bool                  `json:"active"`
+	Events []string              `json:"events"`
+	Config githubWebhookConfig   `json:"config"`
+}
+
+type githubWebhookConfig struct {
+	URL         string `json:"url"`
+	ContentType string `json:"content_type"`
+	Secret      string `json:"secret"`
+}
+
+type GitHubWebhookResponse struct {
+	ID     int                 `json:"id"`
+	Name   string              `json:"name"`
+	Active bool                `json:"active"`
+	Events []string            `json:"events"`
+	Config githubWebhookConfig `json:"config"`
+	URL    string              `json:"url"`
+}
+
 // CreatePullRequest creates a new pull request on GitHub
 func (g *GitHubPullRequestProvider) CreatePullRequest(ctx context.Context, options PullRequestOptions) (*PullRequest, error) {
 	reqBody := githubCreatePRRequest{
@@ -415,3 +438,140 @@
 		URL:        fmt.Sprintf("https://github.com/%s/%s/pull/%d", g.owner, g.repo, githubPR.Number),
 	}
 }
+
+// ListWebhooks lists existing webhooks for the repository
+func (g *GitHubPullRequestProvider) ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error) {
+	url := fmt.Sprintf("%s/repos/%s/%s/hooks", g.config.BaseURL, g.owner, g.repo)
+	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request: %w", err)
+	}
+
+	req.Header.Set("Authorization", "token "+g.config.Token)
+	req.Header.Set("Accept", "application/vnd.github.v3+json")
+
+	resp, err := g.config.HTTPClient.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make request: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		var errorBody bytes.Buffer
+		_, _ = errorBody.ReadFrom(resp.Body)
+		return nil, fmt.Errorf("GitHub API error: %d - %s", resp.StatusCode, errorBody.String())
+	}
+
+	var webhooks []GitHubWebhookResponse
+	if err := json.NewDecoder(resp.Body).Decode(&webhooks); err != nil {
+		return nil, fmt.Errorf("failed to decode response: %w", err)
+	}
+
+	return webhooks, nil
+}
+
+// CreateWebhook creates a new webhook for the repository
+func (g *GitHubPullRequestProvider) CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+	reqBody := githubWebhookRequest{
+		Name:   "web",
+		Active: true,
+		Events: []string{"pull_request"},
+		Config: githubWebhookConfig{
+			URL:         webhookURL,
+			ContentType: "json",
+			Secret:      secret,
+		},
+	}
+
+	jsonBody, err := json.Marshal(reqBody)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal request body: %w", err)
+	}
+
+	g.logger.Info("Creating GitHub webhook",
+		slog.String("url", fmt.Sprintf("%s/repos/%s/%s/hooks", g.config.BaseURL, g.owner, g.repo)),
+		slog.String("webhook_url", webhookURL),
+		slog.Any("events", reqBody.Events))
+
+	url := fmt.Sprintf("%s/repos/%s/%s/hooks", g.config.BaseURL, g.owner, g.repo)
+	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request: %w", err)
+	}
+
+	req.Header.Set("Authorization", "token "+g.config.Token)
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Accept", "application/vnd.github.v3+json")
+
+	resp, err := g.config.HTTPClient.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make request: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusCreated {
+		var errorBody bytes.Buffer
+		_, _ = errorBody.ReadFrom(resp.Body)
+		return nil, fmt.Errorf("GitHub API error: %d - %s", resp.StatusCode, errorBody.String())
+	}
+
+	var webhook GitHubWebhookResponse
+	if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil {
+		return nil, fmt.Errorf("failed to decode response: %w", err)
+	}
+
+	return &webhook, nil
+}
+
+// UpdateWebhook updates an existing webhook
+func (g *GitHubPullRequestProvider) UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+	reqBody := githubWebhookRequest{
+		Name:   "web",
+		Active: true,
+		Events: []string{"pull_request"},
+		Config: githubWebhookConfig{
+			URL:         webhookURL,
+			ContentType: "json",
+			Secret:      secret,
+		},
+	}
+
+	jsonBody, err := json.Marshal(reqBody)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal request body: %w", err)
+	}
+
+	g.logger.Info("Updating GitHub webhook",
+		slog.Int("webhook_id", webhookID),
+		slog.String("webhook_url", webhookURL),
+		slog.Any("events", reqBody.Events))
+
+	url := fmt.Sprintf("%s/repos/%s/%s/hooks/%d", g.config.BaseURL, g.owner, g.repo, webhookID)
+	req, err := http.NewRequestWithContext(ctx, "PATCH", url, bytes.NewBuffer(jsonBody))
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request: %w", err)
+	}
+
+	req.Header.Set("Authorization", "token "+g.config.Token)
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Accept", "application/vnd.github.v3+json")
+
+	resp, err := g.config.HTTPClient.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to make request: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		var errorBody bytes.Buffer
+		_, _ = errorBody.ReadFrom(resp.Body)
+		return nil, fmt.Errorf("GitHub API error: %d - %s", resp.StatusCode, errorBody.String())
+	}
+
+	var webhook GitHubWebhookResponse
+	if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil {
+		return nil, fmt.Errorf("failed to decode response: %w", err)
+	}
+
+	return &webhook, nil
+}