package k8s

import (
	"encoding/json"
	"net/url"
	"os"

	sdkresource "github.com/pingcap/test-infra/sdk/resource"

	"github.com/pingcap/errors"

	apiresource "github.com/pingcap/test-infra/common/model/resource"

	"github.com/pingcap/test-infra/sdk/core"
)

var (
	_ sdkresource.Client = LightningClient{}
)

type LightningClient struct {
	WorkloadNodeClient
}

func NewLightningClient(k *KubeClientSet) LightningClient {
	return LightningClient{WorkloadNodeClient{k}}
}

// Create ...
//
//	_, err := client.Create([]byte(`{
//        "name": "tidb-lightning",
//        "testbed": "testbed-1",
//        "type": "TIDB_LIGHTNING",
//        "spec": {
//            "image": "docker.io/pingcap/tidb-lightning:v5.0.1",
//            "resources": {
//                "requests": {
//                    "memory": "64Mi",
//                    "cpu": "250m",
//                    "storage": "1Gi"
//                }
//            }
//        }
//	}`))
func (t LightningClient) Create(spec json.RawMessage) (core.Resource, error) {
	apiRes, err := t.buildLightningObject(spec)
	if err != nil {
		return nil, err
	}
	wSpec := buildWorkloadNodeFromLighting(apiRes)
	spec, err = json.Marshal(wSpec)
	if err != nil {
		return nil, err
	}
	w, err := t.WorkloadNodeClient.Create(spec)
	if err != nil {
		return nil, err
	}
	return t.buildLightningFromWorkloadNode(w.(*WorkloadNode)), nil
}

func (t LightningClient) Get(handle apiresource.ResourceHandle) (core.Resource, error) {
	res, err := t.WorkloadNodeClient.Get(handle)
	if err != nil {
		return nil, err
	}
	return t.buildLightningFromWorkloadNode(res.(*WorkloadNode)), nil
}

func (t LightningClient) GetEndpoints(_ apiresource.ResourceHandle) (apiresource.ResourceEndpoints, error) {
	return apiresource.ResourceEndpoints{}, nil
}

func (t LightningClient) Delete(handle apiresource.ResourceHandle) error {
	return t.WorkloadNodeClient.Delete(handle)
}

func (t LightningClient) ReserveEnv(handle apiresource.ResourceHandle, action apiresource.GcReserveEnvAction) error {
	return nil
}

func (t LightningClient) Artifact(handle apiresource.ResourceHandle, action apiresource.GcArtifactAction, archiver sdkresource.ArchiverFunc) (map[string]*url.URL, error) {
	resource, err := t.Get(handle)
	if err != nil {
		return nil, err
	}
	lightning := resource.(*TiDBLightning)
	if err = lightning.Sync(); err != nil {
		return nil, err
	}
	return artifactWorkloadNode(t.KubeClientSet, &lightning.WorkloadNode, archiver)
}

func (t LightningClient) FQDN() string {
	return sdkresource.FQDN(t)
}

func (t LightningClient) buildLightningObject(params json.RawMessage) (resHandle *apiresource.TiDBLightning, err error) {
	resHandle = &apiresource.TiDBLightning{}

	// TODO: add validation on TiDBLightning
	if err := json.Unmarshal(params, resHandle); err != nil {
		return nil, errors.Trace(err)
	}

	return resHandle, nil
}

func (t LightningClient) buildLightningFromWorkloadNode(node *WorkloadNode) *TiDBLightning {
	res := &TiDBLightning{*node}
	res.Provider = t.FQDN()
	return res
}

func buildWorkloadNodeFromLighting(lightning *apiresource.TiDBLightning) apiresource.WorkloadNode {
	if lightning.Spec.Name == "" {
		lightning.Spec.Name = "lightning"
	}
	if len(lightning.Spec.Command) == 0 {
		lightning.Spec.Command = []string{"tail", "-f", "/dev/null"}
	}
	return apiresource.WorkloadNode{
		ResourceHandle: lightning.ResourceHandle,
		Spec: apiresource.WorkloadNodeSpec{
			Container: lightning.Spec.WorkloadUnit,
			Artifact:  lightning.Spec.Artifact,
		},
		Qos: lightning.Qos,
	}
}

type LightningGetter struct{ handle apiresource.ResourceHandle }

func (t *LightningGetter) Get() (core.Resource, error) {
	k, err := NewKubeClientFromKubeConfig(t.handle.Testbed, os.Getenv("KUBECONFIG"))
	if err != nil {
		return nil, err
	}
	return NewLightningClient(k).Get(t.handle)
}

func init() {
	core.RegisterProvider(sdkresource.FQDN(LightningClient{}),
		func(handle apiresource.ResourceHandle) core.ResourceGetter { return &LightningGetter{handle: handle} })
}
