// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package xray

import (
	"context"
	"fmt"
	"strconv"

	"github.com/derailed/k9s/internal"
	"github.com/derailed/k9s/internal/client"
	"github.com/derailed/k9s/internal/dao"
	"github.com/derailed/k9s/internal/render"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
)

// Pod represents an xray renderer.
type Pod struct{}

// Render renders an xray node.
func (p *Pod) Render(ctx context.Context, ns string, o any) error {
	pwm, ok := o.(*render.PodWithMetrics)
	if !ok {
		return fmt.Errorf("expected PodWithMetrics, but got %T", o)
	}

	var po v1.Pod
	err := runtime.DefaultUnstructuredConverter.FromUnstructured(pwm.Raw.Object, &po)
	if err != nil {
		return err
	}

	f, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
	if !ok {
		return fmt.Errorf("no factory found in context")
	}

	node := NewTreeNode(client.PodGVR, client.FQN(po.Namespace, po.Name))
	parent, ok := ctx.Value(KeyParent).(*TreeNode)
	if !ok {
		return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
	}

	if err := p.containerRefs(ctx, node, po.Namespace, &po.Spec); err != nil {
		return err
	}
	p.podVolumeRefs(f, node, po.Namespace, po.Spec.Volumes)
	if err := p.serviceAccountRef(ctx, f, node, po.Namespace, &po.Spec); err != nil {
		return err
	}

	gvr, nsID := client.NsGVR, client.FQN(client.ClusterScope, po.Namespace)
	nsn := parent.Find(gvr, nsID)
	if nsn == nil {
		nsn = NewTreeNode(gvr, nsID)
		parent.Add(nsn)
	}
	nsn.Add(node)

	return p.validate(node, po)
}

func (p *Pod) validate(node *TreeNode, po v1.Pod) error {
	var re render.Pod
	phase := re.Phase(po.DeletionTimestamp, &po.Spec, &po.Status)
	ss := po.Status.ContainerStatuses
	readyCnt, _, _, _ := re.ContainerStats(ss)
	status := OkStatus
	if readyCnt != len(ss) {
		status = ToastStatus
	}
	if phase == "Completed" {
		status = CompletedStatus
	}

	node.Extras[StatusKey] = status
	node.Extras[InfoKey] = strconv.Itoa(readyCnt) + "/" + strconv.Itoa(len(ss))

	return nil
}

func (*Pod) containerRefs(ctx context.Context, parent *TreeNode, ns string, spec *v1.PodSpec) error {
	ctx = context.WithValue(ctx, KeyParent, parent)
	var cre Container
	for i := range len(spec.InitContainers) {
		if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.InitContainers[i]}); err != nil {
			return err
		}
	}
	for i := range len(spec.Containers) {
		if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.Containers[i]}); err != nil {
			return err
		}
	}
	for i := range len(spec.EphemeralContainers) {
		if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.Containers[i]}); err != nil {
			return err
		}
	}

	return nil
}

func (*Pod) serviceAccountRef(ctx context.Context, f dao.Factory, parent *TreeNode, ns string, spec *v1.PodSpec) error {
	if spec.ServiceAccountName == "" {
		return nil
	}

	fqn := client.FQN(ns, spec.ServiceAccountName)
	o, err := f.Get(client.SaGVR, fqn, true, labels.Everything())
	if err != nil {
		return err
	}
	if o == nil {
		addRef(f, parent, client.SaGVR, fqn, nil)
		return nil
	}

	var saRE ServiceAccount
	ctx = context.WithValue(ctx, KeyParent, parent)
	ctx = context.WithValue(ctx, KeySAAutomount, spec.AutomountServiceAccountToken)
	return saRE.Render(ctx, ns, o)
}

func (*Pod) podVolumeRefs(f dao.Factory, parent *TreeNode, ns string, vv []v1.Volume) {
	for i := range vv {
		sec := vv[i].Secret
		if sec != nil {
			addRef(f, parent, client.SecGVR, client.FQN(ns, sec.SecretName), sec.Optional)
			continue
		}

		cm := vv[i].ConfigMap
		if cm != nil {
			addRef(f, parent, client.CmGVR, client.FQN(ns, cm.Name), cm.Optional)
			continue
		}

		pvc := vv[i].PersistentVolumeClaim
		if pvc != nil {
			addRef(f, parent, client.PvcGVR, client.FQN(ns, pvc.ClaimName), nil)
		}
	}
}
