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

package dao

import (
	"context"
	"fmt"

	"github.com/derailed/k9s/internal"
	"github.com/derailed/k9s/internal/client"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/dynamic"
)

type Grace int64

const (
	// DefaultGrace uses delete default termination policy.
	DefaultGrace Grace = -1

	// ForceGrace sets delete grace-period to 0.
	ForceGrace Grace = 0

	// NowGrace set delete grace-period to 1,
	NowGrace Grace = 1
)

var _ Describer = (*Generic)(nil)

// Generic represents a generic resource.
type Generic struct {
	NonResource
}

// List returns a collection of resources.
// BOZO!! no auth check??
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
	labelSel, ok := ctx.Value(internal.KeyLabels).(labels.Selector)
	if !ok {
		labelSel = labels.Everything()
	}
	if client.IsAllNamespace(ns) {
		ns = client.BlankNamespace
	}

	dial, err := g.dynClient()
	if err != nil {
		return nil, err
	}

	opts := metav1.ListOptions{LabelSelector: labelSel.String()}
	var ll *unstructured.UnstructuredList
	if client.IsClusterScoped(ns) {
		ll, err = dial.List(ctx, opts)
	} else {
		ll, err = dial.Namespace(ns).List(ctx, opts)
	}
	if err != nil {
		return nil, err
	}

	oo := make([]runtime.Object, len(ll.Items))
	for i := range ll.Items {
		oo[i] = &ll.Items[i]
	}

	return oo, nil
}

// Get returns a given resource.
func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) {
	ns, n := client.Namespaced(path)
	dial, err := g.dynClient()
	if err != nil {
		return nil, err
	}

	var opts metav1.GetOptions
	if client.IsClusterScoped(ns) {
		return dial.Get(ctx, n, opts)
	}

	return dial.Namespace(ns).Get(ctx, n, opts)
}

// Describe describes a resource.
func (g *Generic) Describe(path string) (string, error) {
	return Describe(g.Client(), g.gvr, path)
}

// ToYAML returns a resource yaml.
func (g *Generic) ToYAML(path string, showManaged bool) (string, error) {
	o, err := g.Get(context.Background(), path)
	if err != nil {
		return "", err
	}

	raw, err := ToYAML(o, showManaged)
	if err != nil {
		return "", fmt.Errorf("unable to marshal resource %w", err)
	}
	return raw, nil
}

// Delete deletes a resource.
func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error {
	ns, n := client.Namespaced(path)
	auth, err := g.Client().CanI(ns, g.gvr, n, []string{client.DeleteVerb})
	if err != nil {
		return err
	}
	if !auth {
		return fmt.Errorf("user is not authorized to delete %s", path)
	}

	var gracePeriod *int64
	if grace != DefaultGrace {
		gracePeriod = (*int64)(&grace)
	}
	opts := metav1.DeleteOptions{
		PropagationPolicy:  propagation,
		GracePeriodSeconds: gracePeriod,
	}

	dial, err := g.dynClient()
	if err != nil {
		return err
	}
	if client.IsClusterScoped(ns) {
		return dial.Delete(ctx, n, opts)
	}
	ctx, cancel := context.WithTimeout(ctx, g.Client().Config().CallTimeout())
	defer cancel()

	return dial.Namespace(ns).Delete(ctx, n, opts)
}

func (g *Generic) dynClient() (dynamic.NamespaceableResourceInterface, error) {
	dial, err := g.Client().DynDial()
	if err != nil {
		return nil, err
	}

	return dial.Resource(g.gvr.GVR()), nil
}
