You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: github.go
    path of file: ./repos/porter/internal/porter_app
    
    the code of the file until where you have to start completion: package porter_app

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/bradleyfalzon/ghinstallation/v2"
	"github.com/google/go-github/v39/github"
	"github.com/google/uuid"
	"github.com/porter-dev/porter/internal/models"
	"github.com/porter-dev/porter/internal/repository"
	"github.com/porter-dev/porter/internal/telemetry"
)

// CreateAppWebhookInput is the input to the CreateAppWebhook function
type CreateAppWebhookInput struct {
	ProjectID           uint
	ClusterID           uint
	PorterAppName       string
	GithubAppSecret     []byte
	GithubAppID         string
	GithubWebhookSecret string
	ServerURL           string

	PorterAppRepository     repository.PorterAppRepository
	GithubWebhookRepository repository.GithubWebhookRepository
}

// CreateAppWebhook creates or updates a github webhook for a porter app associated with a given project / cluster / app
// The webhook watches for pull request and push events, used for managing preview environments
func CreateAppWebhook(ctx context.Context, inp CreateAppWebhookInput) error {
	ctx, span := telemetry.NewSpan(ctx, "porter-app-create-app-webhook")
	defer span.End()

	if inp.PorterAppName == "" {
		return telemetry.Error(ctx, span, nil, "porter app name is empty")
	}
	if inp.ProjectID == 0 {
		return telemetry.Error(ctx, span, nil, "project id is empty")
	}
	if inp.ClusterID == 0 {
		return telemetry.Error(ctx, span, nil, "cluster id is empty")
	}
	if inp.GithubAppSecret == nil {
		return telemetry.Error(ctx, span, nil, "github app secret is nil")
	}
	if inp.GithubAppID == "" {
		return telemetry.Error(ctx, span
    
func CreateAppWebhook(ctx context.Context, inp CreateAppWebhookInput) error {
	ctx, span := telemetry.NewSpan(ctx, "porter-app-create-app-webhook")
	defer span.End()

	if inp.PorterAppName == "" {
		return telemetry.Error(ctx, span, nil, "porter app name is empty")
	}
	if inp.ProjectID == 0 {
		return telemetry.Error(ctx, span, nil, "project id is empty")
	}
	if inp.ClusterID == 0 {
		return telemetry.Error(ctx, span, nil, "cluster id is empty")
	}
	if inp.GithubAppSecret == nil {
		return telemetry.Error(ctx, span, nil, "github app secret is nil")
	}
	if inp.GithubAppID == "" {
		return telemetry.Error(ctx, span, nil, "github app id is empty")
	}
	if inp.GithubWebhookSecret == "" {
		return telemetry.Error(ctx, span, nil, "github webhook secret is empty")
	}
	if inp.PorterAppRepository == nil {
		return telemetry.Error(ctx, span, nil, "porter app repository is nil")
	}
	if inp.GithubWebhookRepository == nil {
		return telemetry.Error(ctx, span, nil, "github webhook repository is nil")
	}

	porterApp, err := inp.PorterAppRepository.ReadPorterAppByName(inp.ClusterID, inp.PorterAppName)
	if err != nil {
		return telemetry.Error(ctx, span, err, "could not read porter app by name")
	}
	if porterApp.ID == 0 {
		return telemetry.Error(ctx, span, nil, "porter app not found")
	}
	if porterApp.GitRepoID == 0 {
		return telemetry.Error(ctx, span, nil, "porter app git repo id is empty")
	}

	githubClient, err := GetGithubClientByRepoID(ctx, porterApp.GitRepoID, inp.GithubAppSecret, inp.GithubAppID)
	if err != nil {
		return telemetry.Error(ctx, span, err, "error creating github client")
	}

	repoDetails := strings.Split(porterApp.RepoName, "/")
	if len(repoDetails) != 2 {
		return telemetry.Error(ctx, span, nil, "repo name is not in the format <org>/<repo>")
	}
	if _, _, err := githubClient.Repositories.Get(ctx, repoDetails[0], repoDetails[1]); err != nil {
		return telemetry.Error(ctx, span, err, "error getting github repo")
	}

	hook := &github.Hook{
		Config: map[string]interface{}{
			"content_type": "json",
			"secret":       inp.GithubWebhookSecret,
		},
		Events: []string{"pull_request", "push"},
		Active: github.Bool(true),
	}

	// check if the webhook already exists
	webhook, err := inp.GithubWebhookRepository.GetByClusterAndAppID(ctx, inp.ClusterID, porterApp.ID)
	if err != nil {
		return telemetry.Error(ctx, span, err, "error getting github webhook")
	}

	if webhook.ID != uuid.Nil {
		hook.Config["url"] = fmt.Sprintf("%s/api/webhooks/github/%s", inp.ServerURL, webhook.ID.String())
		_, _, err := githubClient.Repositories.EditHook(ctx, repoDetails[0], repoDetails[1], webhook.GithubWebhookID, hook)
		if err != nil {
			return telemetry.Error(ctx, span, err, "error editing github webhook")
		}

		return nil
	}

	webhookID := uuid.New()

	hook.Config["url"] = fmt.Sprintf("%s/api/webhooks/github/%s", inp.ServerURL, webhookID)
	hook, _, err = githubClient.Repositories.CreateHook(ctx, repoDetails[0], repoDetails[1], hook)
	if err != nil {
		return telemetry.Error(ctx, span, err, "error creating github webhook")
	}

	webhook = &models.GithubWebhook{
		ID:              webhookID,
		ProjectID:       int(porterApp.ProjectID),
		ClusterID:       int(porterApp.ClusterID),
		PorterAppID:     int(porterApp.ID),
		GithubWebhookID: hook.GetID(),
	}

	_, err = inp.GithubWebhookRepository.Insert(ctx, webhook)
	if err != nil {
		return telemetry.Error(ctx, span, err, "error saving github webhook")
	}

	return nil
}