func LazyCreateTknClient(client tknC.Interface) (tknC.Interface, error) {
	if client != nil {
		return client, nil
	}
	f := kubeclient.NewFactory()
	cfg, err := f.CreateKubeConfig()
	if err != nil {
		return client, errors.Wrap(err, "failed to get kubernetes config")
	}
	client, err = tknC.NewForConfig(cfg)
	if err != nil {
		return client, errors.Wrap(err, "error building tekton clientset")
	}
	return client, nil
}
LazyCreateTknClient lazy creates the tekton client if its not defined
package activities
import (
	"context"
	"sort"
	"strings"
	"time"
	v1 "github.com/jenkins-x/jx-api/v4/pkg/apis/jenkins.io/v1"
	jxc "github.com/jenkins-x/jx-api/v4/pkg/client/clientset/versioned"
	jv1 "github.com/jenkins-x/jx-api/v4/pkg/client/clientset/versioned/typed/jenkins.io/v1"
	"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/helper"
	"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/templates"
	"github.com/jenkins-x/jx-helpers/v3/pkg/kube/jxclient"
	"github.com/jenkins-x/jx-helpers/v3/pkg/termcolor"
	"github.com/jenkins-x/jx-kube-client/v3/pkg/kubeclient"
	"github.com/jenkins-x/jx-logging/v3/pkg/log"
	lhclient "github.com/jenkins-x/lighthouse-client/pkg/client/clientset/versioned"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
	tknC "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
)
type Options struct {
	DryRun                  bool
	ReleaseHistoryLimit     int
	PullRequestHistoryLimit int
	ReleaseAgeLimit         time.Duration
	PullRequestAgeLimit     time.Duration
	PipelineRunAgeLimit     time.Duration
	ProwJobAgeLimit         time.Duration
	Namespace               string
	JXClient                jxc.Interface
	LHClient                lhclient.Interface
	TknClient               tknC.Interface
}
const PrLabel = "tekton.dev/pipeline"
var (
	info = termcolor.ColorInfo
	cmdLong = templates.LongDesc(`
		Garbage collect the Jenkins X PipelineActivity resources
`)
	cmdExample = templates.Examples(`
		# garbage collect PipelineActivity resources
		jx gitops gc activities
		# dry run mode
		jx gitops gc pa --dry-run
`)
)
type buildCounter struct {
	ReleaseCount int
	PRCount      int
}
type buildsCount struct {
	cache map[string]*buildCounter
}
func (c *buildsCount) AddBuild(repoAndBranch string, isPR bool) int {
	if c.cache == nil {
		c.cache = map[string]*buildCounter{}
	}
	bc := c.cache[repoAndBranch]
	if bc == nil {
		bc = &buildCounter{}
		c.cache[repoAndBranch] = bc
	}
	if isPR {
		bc.PRCount++
		return bc.PRCount
	}
	bc.ReleaseCount++
	return bc.ReleaseCount
}
func NewCmdGCActivities() (*cobra.Command, *Options) {
	o := &Options{}
	cmd := &cobra.Command{
		Use:     "activities",
		Aliases: []string{"pa", "act", "pr"},
		Short:   "garbage collection for PipelineActivity resources",
		Long:    cmdLong,
		Example: cmdExample,
		Run: func(cmd *cobra.Command, args []string) {
			err := o.Run()
			helper.CheckErr(err)
		},
	}
	cmd.Flags().BoolVarP(&o.DryRun, "dry-run", "d", false, "Dry run mode. If enabled just list the resources that would be removed")
	cmd.Flags().IntVarP(&o.ReleaseHistoryLimit, "release-history-limit", "l", 5, "Maximum number of PipelineActivities to keep around per repository release")
	cmd.Flags().IntVarP(&o.PullRequestHistoryLimit, "pr-history-limit", "", 2, "Minimum number of PipelineActivities to keep around per repository Pull Request")
	cmd.Flags().DurationVarP(&o.PullRequestAgeLimit, "pull-request-age", "p", time.Hour*48, "Maximum age to keep PipelineActivities for Pull Requests")
	cmd.Flags().DurationVarP(&o.ReleaseAgeLimit, "release-age", "r", time.Hour*24*30, "Maximum age to keep PipelineActivities for Releases")
	cmd.Flags().DurationVarP(&o.PipelineRunAgeLimit, "pipelinerun-age", "", time.Hour*12, "Maximum age to keep completed PipelineRuns for all pipelines")
	cmd.Flags().DurationVarP(&o.ProwJobAgeLimit, "prowjob-age", "", time.Hour*24*7, "Maximum age to keep completed ProwJobs for all pipelines")
	return cmd, o
}
func (o *Options) Run() error {
	var err error
	o.JXClient, o.Namespace, err = jxclient.LazyCreateJXClientAndNamespace(o.JXClient, o.Namespace)
	if err != nil {
		return errors.Wrapf(err, "failed to create jx client")
	}
	o.LHClient, err = LazyCreateLHClient(o.LHClient)
	if err != nil {
		return errors.Wrapf(err, "failed to create the lighthouse client")
	}
	o.TknClient, err = LazyCreateTknClient(o.TknClient)
	if err != nil {
		return errors.Wrapf(err, "failed to create the tekton client")
	}
	client := o.JXClient
	currentNs := o.Namespace
	ctx := context.TODO()
	activityInterface := client.JenkinsV1().PipelineActivities(currentNs)
	activities, err := activityInterface.List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}
	if len(activities.Items) == 0 {
		log.Logger().Debug("no activities found")
		return nil
	}
	now := time.Now()
	counters := &buildsCount{}
	var completedActivities []v1.PipelineActivity
	for _, a := range activities.Items {
		if a.Spec.CompletedTimestamp != nil {
			completedActivities = append(completedActivities, a)
		}
	}
	sort.Slice(completedActivities, func(i, j int) bool {
		return !completedActivities[i].Spec.CompletedTimestamp.Before(completedActivities[j].Spec.CompletedTimestamp)
	})
	for _, a := range completedActivities {
		activity := a
		branchName := a.BranchName()
		isPR, isBatch := o.isPullRequestOrBatchBranch(branchName)
		maxAge, revisionHistory := o.ageAndHistoryLimits(isPR, isBatch)
		if activity.Spec.CompletedTimestamp != nil && activity.Spec.CompletedTimestamp.Add(maxAge).Before(now) {
			err = o.deleteLighthouseJob(ctx, &activity)
			if err != nil {
				return err
			}
			prName := activity.Labels[PrLabel]
			pr, err := o.getPipelineRun(ctx, currentNs, prName)
			if err != nil {
				log.Logger().Warnf("pipelinerun %s not found, skipping", prName)
			}
			if pr != nil {
				err = o.deletePipelineRun(ctx, currentNs, prName)
				if err != nil {
					return err
				}
			}
			err = o.deleteActivity(ctx, activityInterface, &activity)
			if err != nil {
				return err
			}
			continue
		}
		repoBranchAndContext := activity.RepositoryOwner() + "/" + activity.RepositoryName() + "/" + activity.BranchName() + "/" + activity.Spec.Context
		c := counters.AddBuild(repoBranchAndContext, isPR)
		if c > revisionHistory && a.Spec.CompletedTimestamp != nil {
			err = o.deleteActivity(ctx, activityInterface, &activity)
			if err != nil {
				return err
			}
			continue
		}
	}
	return nil
}
func (o *Options) deleteActivity(ctx context.Context, activityInterface jv1.PipelineActivityInterface, a *v1.PipelineActivity) error {
	prefix := ""
	if o.DryRun {
		prefix = "not "
	}
	log.Logger().Infof("%sdeleting PipelineActivity %s", prefix, info(a.Name))
	if o.DryRun {
		return nil
	}
	return activityInterface.Delete(ctx, a.Name, *metav1.NewDeleteOptions(0))
}
func (o *Options) ageAndHistoryLimits(isPR, isBatch bool) (time.Duration, int) {
	maxAge := o.ReleaseAgeLimit
	revisionLimit := o.ReleaseHistoryLimit
	if isPR || isBatch {
		maxAge = o.PullRequestAgeLimit
		revisionLimit = o.PullRequestHistoryLimit
	}
	return maxAge, revisionLimit
}
func (o *Options) isPullRequestOrBatchBranch(branchName string) (bool, bool) {
	return strings.HasPrefix(branchName, "PR-"), branchName == "batch"
}
func (o *Options) deleteLighthouseJob(ctx context.Context, pa *v1.PipelineActivity) error {
	if pa.Labels == nil {
		return nil
	}
	labelMap := map[string]string{}
	for k, v := range pa.Labels {
		if k != "lighthouse.jenkins-x.io/id" && strings.HasPrefix(k, "lighthouse.jenkins-x.io/") {
			labelMap[k] = v
		}
	}
	if len(labelMap) < 4 {
		log.Logger().Infof("ignoring PipelineActivity %s which only has lighthouse labels %v", pa.Name, labelMap)
		return nil
	}
	selector := labels.SelectorFromSet(labelMap).String()
	lighthouseJobInterface := o.LHClient.LighthouseV1alpha1().LighthouseJobs(o.Namespace)
	list, err := lighthouseJobInterface.List(ctx, metav1.ListOptions{
		LabelSelector: selector,
	})
	if err != nil {
		if apierrors.IsNotFound(err) {
			return nil
		}
		return errors.Wrapf(err, "failed to list LighthouseJob resources with selector %s", selector)
	}
	if list == nil {
		return nil
	}
	for i := range list.Items {
		r := &list.Items[i]
		prefix := ""
		if o.DryRun {
			prefix = "not "
		}
		log.Logger().Infof("%sdeleting LighthouseJob %s", prefix, info(r.Name))
		if o.DryRun {
			continue
		}
		err = lighthouseJobInterface.Delete(ctx, r.Name, metav1.DeleteOptions{})
		if err != nil {
			return errors.Wrapf(err, "failed to delete LighthouseJob %s", r.Name)
		}
	}
	return nil
}
func (o *Options) deletePipelineRun(ctx context.Context, ns, prName string) error {
	prefix := ""
	if o.DryRun {
		prefix = "not "
	}
	log.Logger().Infof("%sdeleting PipelineRun %s", prefix, info(prName))
	if o.DryRun {
		return nil
	}
	return o.TknClient.TektonV1beta1().PipelineRuns(ns).Delete(ctx, prName, metav1.DeleteOptions{})
}
func (o *Options) getPipelineRun(ctx context.Context, ns, prName string) (*v1beta1.PipelineRun, error) {
	return o.TknClient.TektonV1beta1().PipelineRuns(ns).Get(ctx, prName, metav1.GetOptions{})
}
func LazyCreateLHClient(client lhclient.Interface) (lhclient.Interface, error) {
	if client != nil {
		return client, nil
	}
	f := kubeclient.NewFactory()
	cfg, err := f.CreateKubeConfig()
	if err != nil {
		return client, errors.Wrap(err, "failed to get kubernetes config")
	}
	client, err = lhclient.NewForConfig(cfg)
	if err != nil {
		return client, errors.Wrap(err, "error building lighthouse clientset")
	}
	return client, nil
}