/*
Copyright 2024 The Kruise Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package describe

import (
	"context"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"text/tabwriter"
	"time"

	kruiseappsv1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1"
	kruiseappsv1beta1 "github.com/openkruise/kruise-api/apps/v1beta1"
	rolloutsapi "github.com/openkruise/kruise-rollout-api/client/clientset/versioned"
	rolloutv1alpha1types "github.com/openkruise/kruise-rollout-api/client/clientset/versioned/typed/rollouts/v1alpha1"
	rolloutsv1beta1types "github.com/openkruise/kruise-rollout-api/client/clientset/versioned/typed/rollouts/v1beta1"
	rolloutsapiv1alpha1 "github.com/openkruise/kruise-rollout-api/rollouts/v1alpha1"
	rolloutsapiv1beta1 "github.com/openkruise/kruise-rollout-api/rollouts/v1beta1"
	internalapi "github.com/openkruise/kruise-tools/pkg/api"
	internalpolymorphichelpers "github.com/openkruise/kruise-tools/pkg/internal/polymorphichelpers"
	"github.com/spf13/cobra"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/duration"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes/scheme"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

const (
	tableFormat = "%-19s%v\n"
)

var (
	rolloutLong = templates.LongDesc(i18n.T(`
		Get details about and visual representation of a rollout.`))

	rolloutExample = templates.Examples(`
		# Describe the rollout named rollout-demo within namespace default
		kubectl-kruise describe rollout rollout-demo -n default

		# Watch for changes to the rollout named rollout-demo
		kubectl-kruise describe rollout rollout-demo -n default -w`)
)

type DescribeRolloutOptions struct {
	genericclioptions.IOStreams
	Builder                func() *resource.Builder
	Namespace              string
	EnforceNamespace       bool
	Resources              []string
	RolloutViewerFn        func(runtime.Object) (interface{}, error)
	Watch                  bool
	NoColor                bool
	All                    bool
	TimeoutSeconds         int
	RolloutsV1beta1Client  rolloutsv1beta1types.RolloutInterface
	RolloutsV1alpha1Client rolloutv1alpha1types.RolloutInterface
}

type WorkloadInfo struct {
	Name     string
	Kind     string
	Images   []string
	Replicas struct {
		Desired   int32
		Current   int32
		Updated   int32
		Ready     int32
		Available int32
	}
	Pod []struct {
		Name     string
		BatchID  string
		Status   string
		Ready    string
		Age      string
		Restarts string
		Revision string
	}
	CurrentRevision string
	UpdateRevision  string
}

type RolloutInfo struct {
	Name               string
	Namespace          string
	Phase              string
	Message            string
	ObservedGeneration int64
	Generation         int64
	CurrentStepIndex   int32
	CurrentStepState   string
	CanaryStrategy     rolloutsapiv1beta1.CanaryStrategy
	BlueGreenStrategy  rolloutsapiv1beta1.BlueGreenStrategy
	TrafficRoutingRef  string
	WorkloadRef        RolloutWorkloadRef
	StrategyType       string
}

func NewCmdDescribeRollout(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
	o := &DescribeRolloutOptions{IOStreams: streams}
	cmd := &cobra.Command{
		Use:                   "rollout SUBCOMMAND",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Get details about a rollout"),
		Long:                  rolloutLong,
		Example:               rolloutExample,
		Aliases:               []string{"rollouts", "ro"},
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, args))
			cmdutil.CheckErr(o.Run())
		},
	}

	cmd.Flags().BoolVarP(&o.Watch, "watch", "w", false, "Watch for changes to the rollout")
	cmd.Flags().BoolVar(&o.NoColor, "no-color", false, "If true, print output without color")
	cmd.Flags().IntVar(&o.TimeoutSeconds, "timeout", 0, "Timeout after specified seconds")
	cmd.Flags().BoolVar(&o.All, "all", false, "Show all pods in the rollout")

	return cmd
}

func (o *DescribeRolloutOptions) Complete(f cmdutil.Factory, args []string) error {
	var err error

	if len(args) == 0 {
		return fmt.Errorf("required rollout name not specified")
	}

	o.Resources = args
	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()

	if err != nil {
		return err
	}

	o.Resources = []string{args[0]}

	o.RolloutViewerFn = internalpolymorphichelpers.RolloutViewerFn
	o.Builder = f.NewBuilder

	config, err := f.ToRESTConfig()
	if err != nil {
		return err
	}

	rolloutsClientset, err := rolloutsapi.NewForConfig(config)
	if err != nil {
		return err
	}
	o.RolloutsV1beta1Client = rolloutsClientset.RolloutsV1beta1().Rollouts(o.Namespace)

	o.RolloutsV1alpha1Client = rolloutsClientset.RolloutsV1alpha1().Rollouts(o.Namespace)

	return nil
}

func (o *DescribeRolloutOptions) Run() error {
	rolloutName := o.Resources[0]

	r := o.Builder().
		WithScheme(internalapi.GetScheme(), scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		ResourceNames("rollouts.rollouts.kruise.io", rolloutName).
		ContinueOnError().
		Latest().
		Flatten().
		Do()

	if err := r.Err(); err != nil {
		return err
	}

	if !o.Watch {
		return r.Visit(o.describeRollout)
	}

	return o.watchRollout(r)
}

func (o *DescribeRolloutOptions) describeRollout(info *resource.Info, err error) error {
	if err != nil {
		return err
	}

	rollout, err := o.RolloutViewerFn(info.Object)
	if err != nil {
		return err
	}

	o.printRolloutInfo(rollout)
	return nil
}

func (o *DescribeRolloutOptions) watchRollout(r *resource.Result) error {
	infos, err := r.Infos()
	if err != nil {
		return err
	}
	if len(infos) != 1 {
		return fmt.Errorf("watch is only supported on a single rollout")
	}
	info := infos[0]

	var watcher watch.Interface

	switch info.Object.(type) {
	case *rolloutsapiv1beta1.Rollout:
		watcher, err = o.RolloutsV1beta1Client.Watch(context.TODO(), metav1.ListOptions{
			FieldSelector: "metadata.name=" + info.Name,
		})
	case *rolloutsapiv1alpha1.Rollout:
		watcher, err = o.RolloutsV1alpha1Client.Watch(context.TODO(), metav1.ListOptions{
			FieldSelector: "metadata.name=" + info.Name,
		})
	default:
		return fmt.Errorf("unsupported rollout type %T", info.Object)
	}

	if err != nil {
		return err
	}
	defer watcher.Stop()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	if o.TimeoutSeconds > 0 {
		ctx, cancel = context.WithTimeout(ctx, time.Duration(o.TimeoutSeconds)*time.Second)
		defer cancel()
	}

	return o.watchRolloutUpdates(ctx, watcher)
}

func (o *DescribeRolloutOptions) watchRolloutUpdates(ctx context.Context, watcher watch.Interface) error {
	for {
		select {
		case event, ok := <-watcher.ResultChan():
			if !ok {
				return nil
			}
			if event.Type == watch.Added || event.Type == watch.Modified {
				var rollout interface{}
				switch obj := event.Object.(type) {
				case *rolloutsapiv1beta1.Rollout:
					rollout = obj
				case *rolloutsapiv1alpha1.Rollout:
					rollout = obj
				default:
					continue
				}
				o.clearScreen()
				o.printRolloutInfo(rollout)
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

func (o *DescribeRolloutOptions) clearScreen() {
	fmt.Fprint(o.Out, "\033[2J\033[H")
}

type RolloutWorkloadRef struct {
	Kind             string
	Name             string
	StableRevision   string
	CanaryRevision   string
	UpdatedRevision  string
	PodTemplateHash  string
	CurrentStepIndex int32
}

func (o *DescribeRolloutOptions) GetResources(rollout RolloutWorkloadRef) (*WorkloadInfo, error) {
	resources := []string{rollout.Kind + "/" + rollout.Name}
	r := o.Builder().
		WithScheme(internalapi.GetScheme(), scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		ResourceTypeOrNameArgs(true, resources...).
		ContinueOnError().
		Latest().
		Flatten().
		Do()

	if err := r.Err(); err != nil {
		return nil, err
	}

	obj, err := r.Object()
	if err != nil {
		return nil, err
	}

	workloadInfo := &WorkloadInfo{}
	objValue := reflect.ValueOf(obj).Elem()
	workloadInfo.Name = objValue.FieldByName("Name").String()
	workloadInfo.Kind = objValue.Type().Name()

	podTemplateSpec := objValue.FieldByName("Spec").FieldByName("Template").FieldByName("Spec")
	containers := podTemplateSpec.FieldByName("Containers")
	for i := 0; i < containers.Len(); i++ {
		container := containers.Index(i)
		workloadInfo.Images = append(workloadInfo.Images, container.FieldByName("Image").String())
	}

	// Deployment,StatefulSet,CloneSet,Advanced StatefulSet,Advanced DaemonSet

	switch o := obj.(type) {
	case *appsv1.Deployment:
		workloadInfo.Replicas.Desired = *o.Spec.Replicas
		workloadInfo.Replicas.Current = o.Status.Replicas
		workloadInfo.Replicas.Updated = o.Status.UpdatedReplicas
		workloadInfo.Replicas.Ready = o.Status.ReadyReplicas
		workloadInfo.Replicas.Available = o.Status.AvailableReplicas
	case *appsv1.StatefulSet:
		workloadInfo.Replicas.Desired = *o.Spec.Replicas
		workloadInfo.Replicas.Current = o.Status.Replicas
		workloadInfo.Replicas.Updated = o.Status.UpdatedReplicas
		workloadInfo.Replicas.Ready = o.Status.ReadyReplicas
		workloadInfo.Replicas.Available = o.Status.AvailableReplicas
	case *kruiseappsv1alpha1.DaemonSet:
		workloadInfo.Replicas.Desired = o.Spec.BurstReplicas.IntVal
		workloadInfo.Replicas.Current = o.Status.CurrentNumberScheduled
		workloadInfo.Replicas.Updated = o.Status.UpdatedNumberScheduled
		workloadInfo.Replicas.Ready = o.Status.NumberReady
		workloadInfo.Replicas.Available = o.Status.NumberAvailable
	case *kruiseappsv1beta1.StatefulSet:
		workloadInfo.Replicas.Desired = *o.Spec.Replicas
		workloadInfo.Replicas.Current = o.Status.Replicas
		workloadInfo.Replicas.Updated = o.Status.UpdatedReplicas
		workloadInfo.Replicas.Ready = o.Status.ReadyReplicas
		workloadInfo.Replicas.Available = o.Status.AvailableReplicas
	case *kruiseappsv1alpha1.CloneSet:
		workloadInfo.Replicas.Desired = *o.Spec.Replicas
		workloadInfo.Replicas.Current = o.Status.Replicas
		workloadInfo.Replicas.Updated = o.Status.UpdatedReplicas
		workloadInfo.Replicas.Ready = o.Status.ReadyReplicas
		workloadInfo.Replicas.Available = o.Status.AvailableReplicas
	default:
		return nil, fmt.Errorf("unsupported workload kind %T", obj)
	}

	workloadInfo.CurrentRevision = rollout.StableRevision
	workloadInfo.UpdateRevision = rollout.CanaryRevision

	if rollout.UpdatedRevision != "" {
		workloadInfo.UpdateRevision = rollout.UpdatedRevision
	}

	var labelSelectorParam string
	switch obj.(type) {
	case *appsv1.Deployment:
		labelSelectorParam = "pod-template-hash"
	default:
		labelSelectorParam = "controller-revision-hash"
	}

	selectorParam := rollout.PodTemplateHash
	if selectorParam == "" {
		selectorParam = rollout.StableRevision
	}

	labelSelectors := []string{
		fmt.Sprintf("%s=%s", labelSelectorParam, selectorParam),
	}

	if !o.All && rollout.CurrentStepIndex != 0 {
		labelSelectors = append(labelSelectors,
			fmt.Sprintf("rollouts.kruise.io/rollout-batch-id=%v",
				rollout.CurrentStepIndex))
	}

	// Fetch pods
	r2 := o.Builder().
		WithScheme(internalapi.GetScheme(), scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		ResourceTypes("pods").
		LabelSelectorParam(strings.Join(labelSelectors, ",")).
		Latest().
		Flatten().
		Do()

	if err := r2.Err(); err != nil {
		return nil, err
	}

	err = r2.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		pod, ok := info.Object.(*corev1.Pod)
		if !ok {
			return fmt.Errorf("expected *corev1.Pod, got %T", info.Object)
		}

		podInfo := struct {
			Name     string
			BatchID  string
			Status   string
			Ready    string
			Age      string
			Restarts string
			Revision string
		}{
			Name:     pod.Name,
			BatchID:  pod.Labels["rollouts.kruise.io/rollout-batch-id"],
			Status:   string(pod.Status.Phase),
			Age:      duration.HumanDuration(time.Since(pod.CreationTimestamp.Time)),
			Restarts: "0",
		}

		if pod.DeletionTimestamp != nil {
			podInfo.Status = "Terminating"
		}

		if len(pod.Status.ContainerStatuses) > 0 {
			restartCount := 0
			for _, containerStatus := range pod.Status.ContainerStatuses {
				restartCount += int(containerStatus.RestartCount)
			}

			podInfo.Restarts = strconv.Itoa(restartCount)
		}

		// Calculate ready status
		readyContainers := 0
		for _, containerStatus := range pod.Status.ContainerStatuses {
			if containerStatus.Ready {
				readyContainers++
			}
		}
		podInfo.Ready = fmt.Sprintf("%d/%d", readyContainers, len(pod.Spec.Containers))

		// Calculate revision
		if pod.Labels["pod-template-hash"] != "" {
			podInfo.Revision = pod.Labels["pod-template-hash"]
		} else if pod.Labels["controller-revision-hash"] != "" {
			podInfo.Revision = pod.Labels["controller-revision-hash"]
		}

		workloadInfo.Pod = append(workloadInfo.Pod, podInfo)
		return nil
	})

	if err != nil {
		return nil, err
	}

	// Sort pods by batch ID and ready count
	sort.Slice(workloadInfo.Pod, func(i, j int) bool {
		if workloadInfo.Pod[i].BatchID != workloadInfo.Pod[j].BatchID {
			return workloadInfo.Pod[i].BatchID < workloadInfo.Pod[j].BatchID
		}

		iReady := strings.Split(workloadInfo.Pod[i].Ready, "/")
		jReady := strings.Split(workloadInfo.Pod[j].Ready, "/")

		iReadyCount, _ := strconv.Atoi(iReady[0])
		jReadyCount, _ := strconv.Atoi(jReady[0])

		return iReadyCount > jReadyCount
	})

	return workloadInfo, nil
}

func (o *DescribeRolloutOptions) colorizeIcon(phase string) string {
	if o.NoColor || phase == "" {
		return ""
	}
	switch phase {
	case string(rolloutsapiv1beta1.RolloutPhaseHealthy), string(corev1.PodRunning):
		return "\033[32m✔\033[0m"
	case string(rolloutsapiv1beta1.RolloutPhaseProgressing), string(corev1.PodPending):
		return "\033[33m⚠\033[0m"
	case string(rolloutsapiv1beta1.RolloutPhaseDisabled), string(corev1.PodUnknown), string(rolloutsapiv1beta1.RolloutPhaseTerminating), string(rolloutsapiv1beta1.RolloutPhaseDisabling), string(corev1.PodFailed):
		return "\033[31m✘\033[0m"
	case string(rolloutsapiv1beta1.RolloutPhaseInitial):
		return "\033[33m⚠\033[0m"
	default:
		return ""
	}
}

func (o *DescribeRolloutOptions) printTrafficRouting(trafficRouting []rolloutsapiv1beta1.TrafficRoutingRef) {
	if len(trafficRouting) == 0 {
		return
	}

	fmt.Fprint(o.Out, "Traffic Routings:\n")
	for _, trafficRouting := range trafficRouting {
		fmt.Fprintf(o.Out, tableFormat, "  -  Service: ", trafficRouting.Service)
		if trafficRouting.Ingress != nil {
			fmt.Fprintln(o.Out, `     Ingress: `)
			fmt.Fprintf(o.Out, tableFormat, "      classType: ", trafficRouting.Ingress.ClassType)
			fmt.Fprintf(o.Out, tableFormat, "      name: ", trafficRouting.Ingress.Name)
		}
		if trafficRouting.Gateway != nil {
			fmt.Fprintln(o.Out, `     Gateway: `)
			fmt.Fprintf(o.Out, tableFormat, "      HttpRouteName: ", trafficRouting.Gateway.HTTPRouteName)
		}
		if trafficRouting.CustomNetworkRefs != nil {
			fmt.Fprintln(o.Out, `     CustomNetworkRefs: `)
			for _, customNetworkRef := range trafficRouting.CustomNetworkRefs {
				fmt.Fprintf(o.Out, tableFormat, "      name: ", customNetworkRef.Name)
				fmt.Fprintf(o.Out, tableFormat, "      kind: ", customNetworkRef.Kind)
				fmt.Fprintf(o.Out, tableFormat, "      apiVersion: ", customNetworkRef.APIVersion)
			}
		}
	}
}

func convertCustomNetworkRefs(refs []rolloutsapiv1alpha1.CustomNetworkRef) []rolloutsapiv1beta1.ObjectRef {
	var result []rolloutsapiv1beta1.ObjectRef
	for _, ref := range refs {
		result = append(result, rolloutsapiv1beta1.ObjectRef{
			Name:       ref.Name,
			Kind:       ref.Kind,
			APIVersion: ref.APIVersion,
		})
	}
	return result
}

func extractRolloutInfo(obj interface{}) *RolloutInfo {
	info := &RolloutInfo{}

	switch r := obj.(type) {
	case *rolloutsapiv1beta1.Rollout:
		info.Name = r.Name
		info.Namespace = r.Namespace
		info.Phase = string(r.Status.Phase)
		info.Message = r.Status.Message
		info.ObservedGeneration = r.Status.ObservedGeneration
		info.Generation = r.GetObjectMeta().GetGeneration()
		if r.Spec.Strategy.BlueGreen != nil {
			info.CurrentStepIndex = r.Status.BlueGreenStatus.CurrentStepIndex
			info.CurrentStepState = string(r.Status.BlueGreenStatus.CurrentStepState)
			info.WorkloadRef = RolloutWorkloadRef{
				Kind:             r.Spec.WorkloadRef.Kind,
				Name:             r.Spec.WorkloadRef.Name,
				StableRevision:   r.Status.BlueGreenStatus.StableRevision,
				UpdatedRevision:  r.Status.BlueGreenStatus.UpdatedRevision,
				PodTemplateHash:  r.Status.BlueGreenStatus.PodTemplateHash,
				CurrentStepIndex: r.Status.BlueGreenStatus.CurrentStepIndex,
			}
		} else {
			info.CurrentStepIndex = r.Status.CanaryStatus.CurrentStepIndex
			info.CurrentStepState = string(r.Status.CanaryStatus.CurrentStepState)
			info.WorkloadRef = RolloutWorkloadRef{
				Kind:             r.Spec.WorkloadRef.Kind,
				Name:             r.Spec.WorkloadRef.Name,
				StableRevision:   r.Status.CanaryStatus.StableRevision,
				CanaryRevision:   r.Status.CanaryStatus.CanaryRevision,
				PodTemplateHash:  r.Status.CanaryStatus.PodTemplateHash,
				CurrentStepIndex: r.Status.CanaryStatus.CurrentStepIndex,
			}
		}

		if r.Spec.Strategy.Canary != nil {
			info.CanaryStrategy = *r.Spec.Strategy.Canary
			info.StrategyType = "Canary"
			info.TrafficRoutingRef = r.Spec.Strategy.Canary.TrafficRoutingRef
		} else if r.Spec.Strategy.BlueGreen != nil {
			info.BlueGreenStrategy = *r.Spec.Strategy.BlueGreen
			info.StrategyType = "BlueGreen"
		}

	case *rolloutsapiv1alpha1.Rollout:
		info.Name = r.Name
		info.Namespace = r.Namespace
		info.Phase = string(r.Status.Phase)
		info.Message = r.Status.Message
		info.ObservedGeneration = r.Status.ObservedGeneration
		info.Generation = r.GetObjectMeta().GetGeneration()
		info.CurrentStepIndex = r.Status.CanaryStatus.CurrentStepIndex
		info.CurrentStepState = string(r.Status.CanaryStatus.CurrentStepState)
		info.WorkloadRef = RolloutWorkloadRef{
			Kind:             r.Spec.ObjectRef.WorkloadRef.Kind,
			Name:             r.Spec.ObjectRef.WorkloadRef.Name,
			StableRevision:   r.Status.CanaryStatus.StableRevision,
			CanaryRevision:   r.Status.CanaryStatus.CanaryRevision,
			PodTemplateHash:  r.Status.CanaryStatus.PodTemplateHash,
			CurrentStepIndex: r.Status.CanaryStatus.CurrentStepIndex,
		}

		if r.Spec.Strategy.Canary != nil {
			info.StrategyType = "Canary"
			info.TrafficRoutingRef = r.ObjectMeta.Annotations["rollouts.kruise.io/trafficrouting"]
		}
		// BlueGreen strategy is not supported in v1alpha1 API
	}

	if obj.(*rolloutsapiv1beta1.Rollout).Spec.Strategy.Canary != nil {
		info.CanaryStrategy = *obj.(*rolloutsapiv1beta1.Rollout).Spec.Strategy.Canary
	} else if obj.(*rolloutsapiv1beta1.Rollout).Spec.Strategy.BlueGreen != nil {
		info.BlueGreenStrategy = *obj.(*rolloutsapiv1beta1.Rollout).Spec.Strategy.BlueGreen
	}

	return info
}

func (o *DescribeRolloutOptions) fetchAndPrintTrafficRoutingRef(ref string) {
	r := o.Builder().
		WithScheme(internalapi.GetScheme(), scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		ResourceNames("trafficroutings.rollouts.kruise.io", ref).
		ContinueOnError().
		Latest().
		Flatten().
		Do()

	if err := r.Err(); err != nil {
		fmt.Fprintf(o.Out, "Error getting TrafficRoutingRef: %v\n", err)
		return
	}

	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		trafficRouting, ok := info.Object.(*rolloutsapiv1alpha1.TrafficRouting)
		if !ok {
			return fmt.Errorf("expected *rolloutsapiv1alpha1.TrafficRouting")
		}

		var trafficRoutingRef []rolloutsapiv1beta1.TrafficRoutingRef
		for _, ref := range trafficRouting.Spec.ObjectRef {
			trafficRoutingRef = append(trafficRoutingRef, rolloutsapiv1beta1.TrafficRoutingRef{
				Service:           ref.Service,
				Ingress:           (*rolloutsapiv1beta1.IngressTrafficRouting)(ref.Ingress),
				Gateway:           (*rolloutsapiv1beta1.GatewayTrafficRouting)(ref.Gateway),
				CustomNetworkRefs: convertCustomNetworkRefs(ref.CustomNetworkRefs),
			})
		}

		o.printTrafficRouting(trafficRoutingRef)

		return nil
	})

	if err != nil {
		return
	}
}

func (o *DescribeRolloutOptions) printRolloutInfo(rollout interface{}) {
	info := extractRolloutInfo(rollout)

	// Print basic info
	fmt.Fprintf(o.Out, tableFormat, "Name:", info.Name)
	fmt.Fprintf(o.Out, tableFormat, "Namespace:", info.Namespace)

	if info.ObservedGeneration == info.Generation {
		fmt.Fprintf(o.Out, tableFormat, "Status:", o.colorizeIcon(info.Phase)+" "+info.Phase)
		if info.Message != "" {
			fmt.Fprintf(o.Out, tableFormat, "Message:", info.Message)
		}
	}

	// Print strategy
	fmt.Fprintf(o.Out, tableFormat, "Strategy:", info.StrategyType)

	if info.StrategyType == "Canary" {
		fmt.Fprintf(o.Out, tableFormat, " Step:", strconv.Itoa(int(info.CurrentStepIndex))+"/"+strconv.Itoa(len(info.CanaryStrategy.Steps)))
		fmt.Fprint(o.Out, " Steps:\n")
		o.printSteps(info)
		o.printTrafficRouting(info.CanaryStrategy.TrafficRoutings)

	} else if info.StrategyType == "BlueGreen" {
		fmt.Fprintf(o.Out, tableFormat, " Step:", strconv.Itoa(int(info.CurrentStepIndex))+"/"+strconv.Itoa(len(info.BlueGreenStrategy.Steps)))
		fmt.Fprint(o.Out, " Steps:\n")
		o.printSteps(info)
		o.printTrafficRouting(info.BlueGreenStrategy.TrafficRoutings)
	}

	if info.TrafficRoutingRef != "" {
		o.fetchAndPrintTrafficRoutingRef(info.TrafficRoutingRef)
	}

	// Print workload info
	workloadInfo, err := o.GetResources(info.WorkloadRef)
	if err != nil {
		fmt.Fprintf(o.Out, "Error getting resources: %v\n", err)
		return
	}

	// Print images
	for i, image := range workloadInfo.Images {
		if i == 0 {
			fmt.Fprintf(o.Out, tableFormat, "Images:", image)
		} else {
			fmt.Fprintf(o.Out, tableFormat, "", image)
		}
	}

	// Print revisions
	fmt.Fprintf(o.Out, tableFormat, "Current Revision:", workloadInfo.CurrentRevision)
	fmt.Fprintf(o.Out, tableFormat, "Update Revision:", workloadInfo.UpdateRevision)

	// Print replicas
	if info.ObservedGeneration == info.Generation {
		o.printReplicas(workloadInfo)
	}

	// Print pods
	if len(workloadInfo.Pod) > 0 {
		o.printPods(workloadInfo)
	}
}

func (o *DescribeRolloutOptions) printSteps(info *RolloutInfo) {
	currentStepIndex := int(info.CurrentStepIndex)

	Steps := info.CanaryStrategy.Steps

	if info.StrategyType == "BlueGreen" {
		Steps = info.BlueGreenStrategy.Steps
	}

	for i, step := range Steps {
		isCurrentStep := (i + 1) == currentStepIndex
		if isCurrentStep {
			fmt.Fprint(o.Out, "\033[32m")
		}

		if step.Replicas != nil {
			fmt.Fprintf(o.Out, tableFormat, "  -  Replicas: ", step.Replicas)
		}
		if step.Traffic != nil {
			fmt.Fprintf(o.Out, tableFormat, "     Traffic: ", *step.Traffic)
		}

		if len(step.Matches) > 0 {
			fmt.Fprintln(o.Out, "     Matches: ")
			for _, match := range step.Matches {
				fmt.Fprintln(o.Out, "      - Headers: ")
				for _, header := range match.Headers {
					fmt.Fprintf(o.Out, tableFormat, "       - Name:", header.Name)
					fmt.Fprintf(o.Out, tableFormat, "         Value:", header.Value)
					fmt.Fprintf(o.Out, tableFormat, "         Type:", *header.Type)
				}
			}
		}

		if isCurrentStep {
			fmt.Fprintf(o.Out, tableFormat, "     State:", info.CurrentStepState)
			fmt.Fprint(o.Out, "\033[0m")
		}
	}
}

func (o *DescribeRolloutOptions) printReplicas(info *WorkloadInfo) {
	fmt.Fprint(o.Out, "Replicas:\n")
	fmt.Fprintf(o.Out, tableFormat, " Desired:", info.Replicas.Desired)
	fmt.Fprintf(o.Out, tableFormat, " Updated:", info.Replicas.Updated)
	fmt.Fprintf(o.Out, tableFormat, " Current:", info.Replicas.Current)
	fmt.Fprintf(o.Out, tableFormat, " Ready:", info.Replicas.Ready)
	fmt.Fprintf(o.Out, tableFormat, " Available:", info.Replicas.Available)
}

func (o *DescribeRolloutOptions) printPods(info *WorkloadInfo) {
	w := tabwriter.NewWriter(o.Out, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "NAME\tREADY\tBATCH ID\tREVISION\tAGE\tRESTARTS\tSTATUS")

	for _, pod := range info.Pod {
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s %s\n",
			pod.Name,
			pod.Ready,
			pod.BatchID,
			pod.Revision,
			pod.Age,
			pod.Restarts,
			o.colorizeIcon(pod.Status),
			pod.Status,
		)
	}
	w.Flush()
}
