// Copyright 2017 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.package spec

package crd

import (
	"reflect"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	tapi "github.com/pingcap/tidb-cloud-manager/pkg/api"
	tiapi "github.com/pingcap/tidb-cloud-manager/pkg/tidbinstance/api"
	"github.com/pingcap/private-tidb-operator/pkg/client"
	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
)

// EnsureCRDExists creates TidbInstance CRD
func EnsureCRDExists(kubeconfig string) error {
	apiextensionsclientset, err := Client(kubeconfig)
	if err != nil {
		return errors.Trace(err)
	}

	tiCRD := &apiextensionsv1beta1.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name: tapi.TidbInstanceCRDName,
		},
		Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
			Group:   tapi.GroupName,
			Version: tapi.SchemeGroupVersion.Version,
			Scope:   apiextensionsv1beta1.NamespaceScoped,
			Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
				Plural: tiapi.TidbInstanceResourcePlural,
				Kind:   reflect.TypeOf(tiapi.TidbInstance{}).Name(),
			},
		},
	}

	return createCRD(apiextensionsclientset, tiCRD)
}

func createCRD(clientset *apiextensionsclient.Clientset, crd *apiextensionsv1beta1.CustomResourceDefinition) error {
	_, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
	if err != nil && !apierrs.IsAlreadyExists(err) {
		return errors.Trace(err)
	}

	return WaitForCRDReady(clientset, crd.Name)
}

// WaitForCRDReady wait for CRD being established
func WaitForCRDReady(clientset *apiextensionsclient.Clientset, crdName string) error {
	return wait.Poll(500*time.Millisecond, 60*time.Second, func() (bool, error) {
		crd, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
		if err != nil {
			log.Errorf("failed to get CRD %s: %v", crdName, err)
			return false, nil
		}

		for _, cond := range crd.Status.Conditions {
			switch cond.Type {
			case apiextensionsv1beta1.Established:
				if cond.Status == apiextensionsv1beta1.ConditionTrue {
					return true, nil
				}
			case apiextensionsv1beta1.NamesAccepted:
				if cond.Status == apiextensionsv1beta1.ConditionFalse {
					log.Warnf("Name conflict: %v\n", cond.Reason)
					return false, nil
				}
			}
		}

		return false, err
	})
}

// Client returns an apiextensions's client
func Client(kubeconfig string) (*apiextensionsclient.Clientset, error) {
	config, err := client.NewConfig(kubeconfig)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return apiextensionsclient.NewForConfig(config)
}
