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

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os/exec"
	"time"

	"net/http"

	. "github.com/onsi/ginkgo"
	"github.com/pingcap/tidb-cloud-manager/pkg/server"
	tiapi "github.com/pingcap/tidb-cloud-manager/pkg/tidbinstance/api"
	tictl "github.com/pingcap/tidb-cloud-manager/pkg/tidbinstance/controller"
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
)

func nowStamp() string {
	return time.Now().Format(time.StampMilli)
}

// Infof log a message in GREEN
func Infof(format string, args ...interface{}) {
	format = fmt.Sprintf("\x1b[32m[INFO]: %s: %s\x1b[0m\n", nowStamp(), format)
	fmt.Fprintf(GinkgoWriter, format, args...)
}

// Errorf log a message in RED
func Errorf(format string, args ...interface{}) {
	format = fmt.Sprintf("\x1b[31m[ERROR]: %s: %s\x1b[0m\n", nowStamp(), format)
	fmt.Fprintf(GinkgoWriter, format, args...)
}

// deploy one zk + one kafka for test
func EnsureKafkaIsReady(b *Bootstrap, ns string) error {
	cmd := exec.Command("/bin/sh", "-c", "/usr/local/bin/kubectl apply -f /tmp/data/kafka/zookeeper.yaml")
	data, err := cmd.CombinedOutput()
	if err != nil {
		Infof(string(data))
	}
	err = waitPodReady(b, ns, zkPodName)
	if err != nil {
		return err
	}

	cmd = exec.Command("/bin/sh", "-c", "/usr/local/bin/kubectl apply -f /tmp/data/kafka/kafka.yaml")
	data, err = cmd.CombinedOutput()
	if err != nil {
		Infof(string(data))
	}
	err = waitPodReady(b, ns, kafkaPodName)
	return err
}

func waitPodReady(b *Bootstrap, ns, podName string) error {
	return wait.Poll(10*time.Second, 10*time.Minute, func() (bool, error) {
		pod, err := b.Client.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{})
		if err != nil || pod.Status.Phase != v1.PodRunning {
			return false, nil
		}
		for _, cond := range pod.Status.Conditions {
			if cond.Type == v1.PodReady && cond.Status == v1.ConditionTrue {
				return true, nil
			}
		}
		return false, nil
	})
}

// HasAllBinlogEnabled test whether all of tidb pods have upgrade to binlog
func HasAllBinlogEnabled(tidbPL *v1.PodList) bool {
	binlogEnabled := true
	for _, tidbPod := range tidbPL.Items {
		if len(tidbPod.Spec.Containers) == 1 {
			binlogEnabled = false
			break
		}
	}
	return binlogEnabled
}

// SetVersionByTiPodSpec set specify version for a container based on a specify container name and podSpec
func SetVersionByTiPodSpec(ctype tictl.ContainerType, tiPS *tiapi.PodSpec, version string) {
	for i, c := range tiPS.Containers {
		if string(ctype) == c.Name {
			tiPS.Containers[i].Version = version
			break
		}
	}
}

// AddOrUpdateBinlog add binlog container or update the binlog image.
func AddOrUpdateBinlog(tiPS *tiapi.PodSpec, blImage string) {
	if len(tiPS.Containers) == 2 {
		// need to update binlog image
		for i, c := range tiPS.Containers {
			if string(tictl.BinlogContainerType) == c.Name {
				tiPS.Containers[i].Image = blImage
				break
			}
		}
	} else {
		// need add a binlog container
		binlogContainer := tiapi.ContainerSpec{
			Name:  string(tictl.BinlogContainerType),
			Image: blImage,
		}
		tiPS.Containers = append(tiPS.Containers, binlogContainer)
	}
}

//custom http client for operate cloud manger APIs
type HttpClient struct {
	*http.Client
}

func (hc *HttpClient) DoPost(url string, bodyType string, body []byte) (*server.Response, error) {
	ioBody := bytes.NewReader(body)
	resp, err := hc.Client.Post(url, bodyType, ioBody)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	response := &server.Response{}
	err = json.Unmarshal(respBody, response)

	return response, err
}

func (hc *HttpClient) DoPut(url string, bodyType string, body []byte) (*server.Response, error) {
	ioBody := bytes.NewReader(body)
	req, err := http.NewRequest("PUT", url, ioBody)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", bodyType)

	resp, err := hc.Client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	response := &server.Response{}
	err = json.Unmarshal(respBody, response)

	return response, err
}

func (hc *HttpClient) DoGet(url string) (*server.Response, error) {
	resp, err := hc.Client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	response := &server.Response{}
	err = json.Unmarshal(respBody, response)

	return response, err
}

func (hc *HttpClient) DoDelete(url string) (*server.Response, error) {
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := hc.Client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	response := &server.Response{}
	err = json.Unmarshal(respBody, response)

	return response, err
}
