// 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 review

import (
	"crypto/sha256"
	"errors"
	"fmt"

	"bytes"

	"github.com/ngaut/log"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap.com/v1"
	"github.com/pingcap/tidb-operator/pkg/client/clientset/versioned"
	"github.com/pingcap/tidb-operator/pkg/util/label"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ReviewEnabled is set by the main program at startup.
var ReviewEnabled bool

// Info explains the review.
// UID must be supplied to unique the TidbReview object.
type Info struct {
	UID         string
	Namespace   string
	ClusterName string
	Reason      string
	Message     string
}

func (ro *Info) String() string {
	var buffer bytes.Buffer

	buffer.WriteString("Review, ")
	buffer.WriteString("Namespace: ")
	buffer.WriteString(ro.Namespace)
	buffer.WriteString(", ")
	buffer.WriteString("ClusterName: ")
	buffer.WriteString(ro.ClusterName)
	buffer.WriteString(", ")
	buffer.WriteString("UID: ")
	buffer.WriteString(ro.UID)
	buffer.WriteString(", ")
	buffer.WriteString("Reason: ")
	buffer.WriteString(ro.Reason)
	buffer.WriteString(", ")
	buffer.WriteString("Message: ")
	buffer.WriteString(ro.Reason)

	return buffer.String()
}

// Review will block until approved
func Review(cli versioned.Interface, options Info) error {
	if !ReviewEnabled {
		return nil
	}

	if options.UID == "" || options.Reason == "" || options.Message == "" {
		return errors.New("Info.UID/Reason/Message must not be empty")
	}

	log.Info(options.String())

	h := sha256.New()
	_, err := h.Write([]byte(options.String()))
	if err != nil {
		return err
	}
	reviewName := fmt.Sprintf("%x", h.Sum(nil))

	r, err := cli.PingcapV1().TidbReviews().Get(reviewName, metav1.GetOptions{})
	if err != nil {
		if !apierrs.IsNotFound(err) {
			log.Errorf("failed to get TidbReview: [%s], %v", reviewName, err)
			return err
		}

		r = &v1.TidbReview{
			ObjectMeta: metav1.ObjectMeta{
				Name:   reviewName,
				Labels: label.New().Namespace(options.Namespace).Cluster(options.ClusterName).Labels(),
			},
			Spec: v1.TidbReviewSpec{
				Reason:  options.Reason,
				Message: options.Message,
			},
		}
		r, err = cli.PingcapV1().TidbReviews().Create(r)
		if err != nil {
			log.Errorf("failed to create TidbReview: [%s], %v", reviewName, err)
			return err
		}
		log.Infof("create TidbReview: [%s], %s, successfully", reviewName, options.String())
	}

	if !r.Status.Approved {
		msg := fmt.Sprintf("Waiting for TidbReview: [%s], %s to be approved", reviewName, options.String())
		log.Infof(msg)
		return errors.New(msg)
	}

	log.Infof("TidbReview: [%s], %s has been approved", reviewName, options.String())
	return nil
}
