package k8s

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

	"github.com/hashicorp/go-multierror"
	"k8s.io/apimachinery/pkg/labels"

	"github.com/pingcap/errors"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/core"
	sdkresource "github.com/pingcap/test-infra/sdk/resource"
)

var (
	_ sdkresource.Client = MySQLClient{}
)

type MySQLClient struct {
	WorkloadNodeClient
}

func NewMySQLClient(k *KubeClientSet) MySQLClient {
	return MySQLClient{WorkloadNodeClient{k}}
}

// Create ...
//
//	_, err := client.Create([]byte(`{
//        "name": "mysql",
//        "testbed": "testbed-1",
//        "type": "MySQL_NODE",
//        "spec": {
//            "image": "docker.io/mysql:8",
//            "port": 3306,
//            "resources": {
//                "requests": {
//                    "memory": "64Mi",
//                    "cpu": "250m",
//                    "storage": "1Gi"
//                }
//            },
//        	  "env": {
//        		  "MYSQL_ALLOW_EMPTY_PASSWORD": "yes",
//        		  "MYSQL_ROOT_HOST":            "%",
//        	  }
//        },
//	}`))
func (t MySQLClient) Create(spec json.RawMessage) (core.Resource, error) {
	apiRes, err := t.buildMySQLObj(spec)
	if err != nil {
		return nil, err
	}

	workload := apiRes.BuildWorkload()
	wSpec, err := json.Marshal(workload)
	if err != nil {
		return nil, err
	}

	w, err := t.WorkloadNodeClient.Create(wSpec)
	if err != nil {
		return nil, err
	}

	node := w.(*WorkloadNode)
	reader := t.WorkloadNodeClient.buildService(apiRes.Name, apiRes.Name, "",
		corev1.ServiceTypeNodePort, &node.service.OwnerReferences[0], apiRes.Spec.Port)
	reader, err = t.ClientSet.CoreV1().Services(t.Namespace).Create(context.TODO(), reader, metav1.CreateOptions{})
	if err != nil {
		return nil, errors.Annotate(err, "create reader service for mysql")
	}

	mysql := t.buildMySQLFromWorkloadNode(node)
	mysql.ReaderService = reader

	return mysql, nil
}

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

	mysql := t.buildMySQLFromWorkloadNode(res.(*WorkloadNode))
	reader, err := t.ClientSet.CoreV1().Services(t.Namespace).Get(context.TODO(), handle.Name, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	mysql.ReaderService = reader

	return mysql, nil
}

func (t MySQLClient) GetEndpoints(handle apiresource.ResourceHandle) (apiresource.ResourceEndpoints, error) {
	res := apiresource.ResourceEndpoints{}

	svcList, err := t.ClientSet.CoreV1().
		Services(handle.Testbed).
		List(context.TODO(), metav1.ListOptions{
			LabelSelector: labels.Set{"test-infra/name": handle.Name}.AsSelector().String(),
		})
	if err != nil {
		return res, err
	}

	for _, svc := range svcList.Items {
		if svc.Spec.Type != corev1.ServiceTypeNodePort {
			continue
		}
		if svc.Name != handle.Name {
			continue
		}

		endPoint := apiresource.Endpoint{
			Type: apiresource.EndpointTypeDsn,
			Name: svc.Name,
		}

		pods, err := t.ClientSet.CoreV1().Pods(handle.Testbed).List(context.TODO(), metav1.ListOptions{
			LabelSelector: labels.FormatLabels(svc.Spec.Selector),
		})
		if err != nil {
			return res, err
		}
		if len(pods.Items) == 0 {
			return res, fmt.Errorf("service %s/%s has no target pod", svc.Namespace, svc.Name)
		}

		endPoint.ExternalSvcAddress = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", "root", "", pods.Items[0].Status.HostIP, svc.Spec.Ports[0].NodePort, "")

		res = append(res, endPoint)
		break

	}

	return res, nil
}

func (t MySQLClient) Delete(handle apiresource.ResourceHandle) error {
	var errs error
	if err := t.WorkloadNodeClient.Delete(handle); err != nil {
		errs = multierror.Append(errs, err)
	}

	if err := t.ClientSet.CoreV1().Services(t.Namespace).Delete(context.TODO(), handle.Name, metav1.DeleteOptions{}); err != nil {
		errs = multierror.Append(errs, err)
	}
	return errs
}

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

func (t MySQLClient) Artifact(handle apiresource.ResourceHandle, action apiresource.GcArtifactAction, archiver sdkresource.ArchiverFunc) (map[string]*url.URL, error) {
	return nil, nil
}

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

func (t MySQLClient) buildMySQLFromWorkloadNode(node *WorkloadNode) *MySQLNode {
	res := &MySQLNode{WorkloadNode: *node}
	res.Provider = t.FQDN()
	return res
}

func (t MySQLClient) buildMySQLObj(spec json.RawMessage) (*apiresource.MySQLNode, error) {
	var mysql apiresource.MySQLNode
	// TODO: add validation on MySQLNode
	if err := json.Unmarshal(spec, &mysql); err != nil {
		return nil, errors.Trace(err)
	}
	return &mysql, nil
}

type MySQLGetter struct{ handle apiresource.ResourceHandle }

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

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