package dynamic

import (
	"context"

	"github.com/porter-dev/porter/internal/templater/utils"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"k8s.io/client-go/dynamic"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// TemplateWriter satisfies the templater.TemplateWriter interface
// by creating/updating dynamic k8s resources
type TemplateWriter struct {
	// The object to read from, identified by its group-version-kind
	Object *Object

	Client dynamic.Interface

	// The resource that's being written to
	resource dynamic.ResourceInterface

	// The values to be written
	vals map[string]interface{}

	// The base values
	base map[string]interface{}
}

// NewDynamicTemplateWriter returns a dynamic TemplateWriter
func NewDynamicTemplateWriter(
	client dynamic.Interface,
	obj *Object,
	base map[string]interface{},
) *TemplateWriter {
	w := &TemplateWriter{
		Object: obj,
		Client: client,
		base:   base,
	}

	objRes := schema.GroupVersionResource{
		Group:    w.Object.Group,
		Version:  w.Object.Version,
		Resource: w.Object.Resource,
	}

	w.resource = w.Client.Resource(objRes).Namespace(w.Object.Namespace)

	return w
}

// Transform merges base configuration with vals
func (w *TemplateWriter) Transform() error {
	w.vals = utils.CoalesceValues(w.base, w.vals)

	return nil
}

// Create creates a new dynamic resource, this must be registered with the API server
func (w *TemplateWriter) Create(vals map[string]interface{}) (map[string]interface{}, error) {
	w.vals = vals
	err := w.Transform()
	if err != nil {
		return nil, err
	}

	create, err := w.resource.Create(context.TODO(), &unstructured.Unstructured{
		Object: w.vals,
	}, metav1.CreateOptions{})
	if err != nil {
		return nil, err
	}

	return create.Object, nil
}

// Update performs an update operation on a k8s resource. The resource must be
// registered with the API server.
func (w *TemplateWriter) Update(vals map[string]interface{}) (map[string]interface{}, error) {
	w.vals = vals
	err := w.Transform()
	if err != nil {
		return nil, err
	}

	update, err := w.resource.Update(context.TODO(), &unstructured.Unstructured{
		Object: w.vals,
	}, metav1.UpdateOptions{})
	if err != nil {
		return nil, err
	}

	return update.Object, nil
}

// Delete deletes a dynamic resource, this must be registered with the API server
func (w *TemplateWriter) Delete() error {
	return w.resource.Delete(context.TODO(), w.Object.Name, metav1.DeleteOptions{})
}
