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

package view

import (
	"context"
	"fmt"
	"log/slog"

	"github.com/derailed/k9s/internal"
	"github.com/derailed/k9s/internal/client"
	"github.com/derailed/k9s/internal/dao"
	"github.com/derailed/k9s/internal/render"
	"github.com/derailed/k9s/internal/slogs"
	"github.com/derailed/k9s/internal/ui"
	"github.com/derailed/k9s/internal/ui/dialog"
	"github.com/derailed/tcell/v2"
	"github.com/go-errors/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// OwnerExtender adds owner actions to a given viewer.
type OwnerExtender struct {
	ResourceViewer
}

// NewOwnerExtender returns a new extender.
func NewOwnerExtender(r ResourceViewer) ResourceViewer {
	v := &OwnerExtender{ResourceViewer: r}
	v.AddBindKeysFn(v.bindKeys)

	return v
}

func (v *OwnerExtender) bindKeys(aa *ui.KeyActions) {
	aa.Add(ui.KeyShiftJ, ui.NewKeyAction("Jump Owner", v.ownerCmd, true))
}

func (v *OwnerExtender) ownerCmd(evt *tcell.EventKey) *tcell.EventKey {
	path := v.GetTable().GetSelectedItem()
	if path == "" {
		return evt
	}

	if err := v.findOwnerFor(path); err != nil {
		slog.Warn("Unable to jump to owner of resource",
			slogs.FQN, path,
			slogs.Error, err,
		)
		v.App().Flash().Warnf("Unable to jump owner: %s", err)
	}
	return nil
}

func (v *OwnerExtender) findOwnerFor(path string) error {
	res, err := dao.AccessorFor(v.App().factory, v.GVR())
	if err != nil {
		return err
	}

	o, err := res.Get(v.defaultCtx(), path)
	if err != nil {
		return err
	}

	u, ok := v.asUnstructuredObject(o)
	if !ok {
		return errors.Errorf("unsupported object type: %t", o)
	}

	ns, _ := client.Namespaced(path)
	ownerReferences := u.GetOwnerReferences()
	if len(ownerReferences) == 1 {
		return v.jumpOwner(ns, &ownerReferences[0])
	} else if len(ownerReferences) > 1 {
		owners := make([]string, 0, len(ownerReferences))
		for idx, ownerRef := range ownerReferences {
			owners = append(owners, fmt.Sprintf("%d: %s", idx, ownerRef.Kind))
		}

		d := v.App().Styles.Dialog()
		dialog.ShowSelection(&d, v.App().Content.Pages, "Jump To", owners, func(index int) {
			if index >= 0 {
				err = v.jumpOwner(ns, &ownerReferences[index])
			}
		})
		return err
	}

	return errors.Errorf("no owner found")
}

func (v *OwnerExtender) jumpOwner(ns string, owner *metav1.OwnerReference) error {
	gv, err := schema.ParseGroupVersion(owner.APIVersion)
	if err != nil {
		return err
	}

	gvr, namespaced, found := dao.MetaAccess.GVK2GVR(gv, owner.Kind)
	if !found {
		return errors.Errorf("unsupported GVK: %s/%s", owner.APIVersion, owner.Kind)
	}

	var ownerFQN string
	if namespaced {
		ownerFQN = client.FQN(ns, owner.Name)
	} else {
		ownerFQN = owner.Name
	}

	v.App().gotoResource(gvr.String(), ownerFQN, false, true)
	return nil
}

func (v *OwnerExtender) defaultCtx() context.Context {
	return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory)
}

func (*OwnerExtender) asUnstructuredObject(o runtime.Object) (*unstructured.Unstructured, bool) {
	switch v := o.(type) {
	case *unstructured.Unstructured:
		return v, true
	case *render.PodWithMetrics:
		return v.Raw, true
	default:
		return nil, false
	}
}
