/*
Package tablelistconvert provides a client that will use a table client but convert *UnstructuredList and *Unstructured objects
returned by ByID and List to resemble those returned by non-table clients while preserving some table-related data.
*/
package tablelistconvert

import (
	"context"
	"fmt"

	"github.com/rancher/wrangler/v3/pkg/data"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	k8sWatch "k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/dynamic"
)

type Client struct {
	dynamic.ResourceInterface
}

var _ dynamic.ResourceInterface = (*Client)(nil)

type tableConvertWatch struct {
	done   chan struct{}
	events chan k8sWatch.Event
	k8sWatch.Interface
}

// List will return an *UnstructuredList that contains Items instead of just using the Object field to store a table as
// Table Clients do. The items will preserve values for columns in the form of metadata.fields.
func (c *Client) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
	list, err := c.ResourceInterface.List(ctx, opts)
	if err != nil {
		return nil, err
	}
	tableToList(list)
	return list, nil
}

func (c *Client) Watch(ctx context.Context, opts metav1.ListOptions) (k8sWatch.Interface, error) {
	w, err := c.ResourceInterface.Watch(ctx, opts)
	if err != nil {
		return nil, err
	}
	events := make(chan k8sWatch.Event)
	done := make(chan struct{})
	eventWatch := &tableConvertWatch{done: done, events: events, Interface: w}
	eventWatch.feed()
	return eventWatch, nil
}

func (w *tableConvertWatch) feed() {
	tableEvents := w.Interface.ResultChan()
	go func() {
		for {
			select {
			case e, ok := <-tableEvents:
				if !ok {
					close(w.events)
					return
				}
				if unstr, ok := e.Object.(*unstructured.Unstructured); ok {
					rowToObject(unstr)
					w.events <- e
				}
			case <-w.done:
				close(w.events)
				return
			}
		}
	}()
}

func (w *tableConvertWatch) ResultChan() <-chan k8sWatch.Event {
	return w.events
}

func (w *tableConvertWatch) Stop() {
	fmt.Println("stop")
	close(w.done)
	w.Interface.Stop()
}

func rowToObject(obj *unstructured.Unstructured) {
	if obj == nil {
		return
	}
	if obj.Object["kind"] != "Table" ||
		(obj.Object["apiVersion"] != "meta.k8s.io/v1" &&
			obj.Object["apiVersion"] != "meta.k8s.io/v1beta1") {
		return
	}

	items := tableToObjects(obj.Object)
	if len(items) == 1 {
		obj.Object = items[0].Object
	}
}

func tableToList(obj *unstructured.UnstructuredList) {
	if obj.Object["kind"] != "Table" ||
		(obj.Object["apiVersion"] != "meta.k8s.io/v1" &&
			obj.Object["apiVersion"] != "meta.k8s.io/v1beta1") {
		return
	}

	obj.Items = tableToObjects(obj.Object)
}

func tableToObjects(obj map[string]interface{}) []unstructured.Unstructured {
	var result []unstructured.Unstructured

	rows, _ := obj["rows"].([]interface{})
	for _, row := range rows {
		m, ok := row.(map[string]interface{})
		if !ok {
			continue
		}
		cells := m["cells"]
		object, ok := m["object"].(map[string]interface{})
		if !ok {
			continue
		}

		data.PutValue(object, cells, "metadata", "fields")
		result = append(result, unstructured.Unstructured{
			Object: object,
		})
	}

	return result
}