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

import (
	"fmt"
	"net/http"

	restful "github.com/emicklei/go-restful"
	"github.com/ngaut/log"
	"github.com/pingcap/tidb-cloud-manager/pkg/manager"
	"k8s.io/api/core/v1"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (m Manager) NewService() *restful.WebService {
	ws := new(restful.WebService)
	ws.
		Path(apiPrefix).
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)
	ws.Route(ws.GET("/clusters").To(m.listCluster).
		Doc("list all clusters").
		Operation("listCluster").
		Writes(Response{}))
	ws.Route(ws.POST("/clusters").To(m.createCluster).
		Doc("create cluster").
		Operation("createCluster").
		Reads(manager.Cluster{}).
		Writes(Response{}))
	ws.Route(ws.GET("/clusters/{namespace}").To(m.getCluster).
		Doc("get cluster info").
		Operation("getCluster").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.PUT("/clusters/{namespace}").To(m.updateCluster).
		Doc("update a cluster").
		Operation("updateCluster").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Reads(manager.Cluster{}).
		Writes(Response{}))
	ws.Route(ws.DELETE("/clusters/{namespace}").To(m.deleteCluster).
		Doc("delete a cluster").
		Operation("deleteCluster").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.PUT("/clusters/{namespace}/restore").To(m.restoreCluster).
		Doc("restore a cluster").
		Operation("restoreCluster").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.DELETE("/clusters/{namespace}/pods/{pod_name}").To(m.deletePod).
		Doc("offline a pod").
		Operation("deletePod").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Param(ws.PathParameter("pod_name", "pod name").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.PUT("/clusters/{namespace}/password").To(m.resetPassword).
		Doc("reset password for tidb").
		Operation("resetPassword").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Reads(Password{}).
		Writes(Response{}))
	ws.Route(ws.GET("/clusters/{namespace}/jobs/{job_name}").To(m.getJob).
		Doc("query job status").
		Operation("getJob").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Param(ws.PathParameter("job_name", "job name").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.GET("/nodes").To(m.getNodes).
		Doc("query all nodes's status").
		Operation("getNodes").
		Writes(Response{}))
	ws.Route(ws.POST("/clusters/{namespace}/backup").To(m.createBackup).
		Doc("create backup for database").
		Operation("createBackup").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Reads(manager.BackupParam{}).
		Writes(Response{}))
	ws.Route(ws.GET("/clusters/{namespace}/backup/{backup_name}").To(m.getBackup).
		Doc("get backup status").
		Operation("getBackup").
		Param(ws.PathParameter("namespace", "cluster namespace").DataType("string")).
		Param(ws.PathParameter("backup_name", "backup name").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.GET("/volumes").To(m.getVolumes).
		Doc("query all volume's status").
		Operation("getVolumes").
		Param(ws.QueryParameter("namespace", "cluster namespace").DataType("string")).
		Param(ws.QueryParameter("node_name", "node name").DataType("string")).
		Param(ws.QueryParameter("can_safe_deleted", "get tombstoned and/or clusterDeleted volumes only").DataType("string")).
		Writes(Response{}))
	ws.Route(ws.DELETE("/volumes/{name}").To(m.deleteVolume).
		Doc("delete volume").
		Operation("deleteVolume").
		Param(ws.PathParameter("name", "volume name").DataType("string")).
		Writes(Response{}))
	return ws
}

func (m Manager) listCluster(req *restful.Request, resp *restful.Response) {
	res := newResponse("listCluster")

	log.Debug("getting all clusters info")
	clusters, err := m.ListCluster(v1.NamespaceAll)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: listCluster, error: %v", err)
		}
		return
	}
	res.payload(Payload{Clusters: clusters})
	if err = resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: listCluster, error: %v", err)
	}
}

func (m Manager) getCluster(req *restful.Request, resp *restful.Response) {
	res := newResponse("getCluster")
	namespace := req.PathParameter("namespace")

	log.Debugf("getting a cluster detail info, namespace: %s", namespace)
	cluster, err := m.GetCluster(namespace)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: getCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}
	if cluster != nil {
		res.payload(Payload{Clusters: []*manager.Cluster{cluster}})
	}
	if err = resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: getCluster, namespace: %s, error: %v", namespace, err)
	}
}

func (m Manager) createCluster(req *restful.Request, resp *restful.Response) {
	res := newResponse("createCluster")
	cluster := new(manager.Cluster)
	if err := req.ReadEntity(cluster); err != nil {
		res.message(err.Error()).statusCode(http.StatusBadRequest)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: createCluster, namespace: %s, error: %v", cluster.Name, err)
		}
		return
	}

	// Verify the cluster configuration
	if err := manager.CheckClusterConfig(cluster); err != nil {
		log.Error(err.Error())
		res.message(err.Error()).statusCode(http.StatusBadRequest)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: createCluster, namespace: %s, error: %v", cluster.Name, err)
		}
		return
	}

	log.Debugf("creating a cluster, namespace: %s", cluster.Name)
	if err := m.CreateInstance(cluster); err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: createCluster, namespace: %s, error: %v", cluster.Name, err)
		}
		return
	}
	res.message(fmt.Sprintf("tidb cluster will be created, namespace: %s", cluster.Name))
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: createCluster, namespace: %s, error: %v", cluster.Name, err)
	}
}

func (m Manager) updateCluster(req *restful.Request, resp *restful.Response) {
	res := newResponse("updateCluster")
	namespace := req.PathParameter("namespace")
	cluster := new(manager.Cluster)
	if err := req.ReadEntity(cluster); err != nil {
		res.message(err.Error()).statusCode(http.StatusBadRequest)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	// Verify the cluster configuration
	if err := manager.CheckClusterConfig(cluster); err != nil {
		log.Error(err.Error())
		res.message(err.Error()).statusCode(http.StatusBadRequest)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", cluster.Name, err)
		}
		return
	}

	ti, err := m.Cli.PingcapV1().TidbInstances(namespace).Get(namespace, metav1.GetOptions{})
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	if needResize(ti, cluster) {
		log.Debugf("resizing a cluster, namespace: %s", namespace)
		if err := m.ResizeInstance(namespace, *cluster); err != nil {
			res.message(err.Error()).statusCode(http.StatusInternalServerError)
			if err = resp.WriteEntity(res); err != nil {
				log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
			}
			return
		}

		res.message(fmt.Sprintf("tidb cluster will be resized, namespace: %s", namespace))
		if err := resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	// try to update the image of all components
	update, err := m.UpgradeInstance(namespace, *cluster)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	if update {
		res.message(fmt.Sprintf("tidb cluster will be upgrade, namespace: %s", namespace))
		if err := resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	res.message(fmt.Sprintf("tidb cluster has nothing to update, namespace: %s", namespace))
	if err = resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: updateCluster, namespace: %s, error: %v", namespace, err)
	}
}

func (m Manager) deleteCluster(req *restful.Request, resp *restful.Response) {
	res := newResponse("deleteCluster")
	namespace := req.PathParameter("namespace")

	ti, err := m.Cli.PingcapV1().TidbInstances(namespace).Get(namespace, metav1.GetOptions{})
	if err != nil {
		if apierrs.IsNotFound(err) {
			res.message(err.Error()).statusCode(http.StatusNotFound)
			if err = resp.WriteEntity(res); err != nil {
				log.Errorf("failed to response, method: deleteCluster, namespace: %s, error: %v", namespace, err)
			}
			return
		}

		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: deleteCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	if ti.Spec.Deleted {
		log.Infof("TidbInstance: %s's Spec.Deleted is already equal to true", namespace)
		if err := resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: deleteCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	log.Infof("setting TidbInstance: %s's Spec.Deleted to true", namespace)
	ti.Spec.Deleted = true
	_, err = m.Cli.PingcapV1().TidbInstances(namespace).Update(ti)
	if err != nil {
		log.Errorf("failed to update TidbInstance: %s, error: %v", namespace, err)
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: deleteCluster, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	res.message(fmt.Sprintf("TidbInstance: %s's Spec.Deleted is set to true", namespace))
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: deleteCluster, namespace: %s, error: %v", namespace, err)
	}
}

func (m Manager) restoreCluster(req *restful.Request, resp *restful.Response) {
	res := newResponse("restoreCluster")
	namespace := req.PathParameter("namespace")

	ti, err := m.Cli.PingcapV1().TidbInstances(namespace).Get(namespace, metav1.GetOptions{})
	if err != nil {
		if apierrs.IsNotFound(err) {
			res.message(err.Error()).statusCode(http.StatusNotFound)
			if err = resp.WriteEntity(res); err != nil {
				log.Errorf("failed to response, method: restoreCluster namespace: [%s], error: %v", namespace, err)
			}
			return
		}

		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: restoreCluster, namespace: [%s], error: %v", namespace, err)
		}
		return
	}

	if !ti.Spec.Deleted {
		log.Infof("TidbInstance: [%s] is not graceful deleted, doesn't need restoring", namespace)
		if err := resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: restoreCluster, namespace: [%s], error: %v", namespace, err)
		}
		return
	}

	log.Infof("setting TidbInstance: [%s]'s Spec.Deleted to false", namespace)
	ti.Spec.Deleted = false
	_, err = m.Cli.PingcapV1().TidbInstances(namespace).Update(ti)
	if err != nil {
		log.Errorf("failed to update TidbInstance: [%s], error: %v", namespace, err)
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: restoreCluster, namespace: [%s], error: %v", namespace, err)
		}
		return
	}
	res.message(fmt.Sprintf("TidbInstance: [%s]'s Spec.Deleted is set to false", namespace))

	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: restoreCluster, namespace: [%s], error: %v", namespace, err)
	}
}

func (m Manager) resetPassword(req *restful.Request, resp *restful.Response) {
	res := newResponse("resetPassword")
	namespace := req.PathParameter("namespace")
	cluster := new(manager.Cluster)
	res.payload(Payload{})
	if err := req.ReadEntity(cluster); err != nil {
		res.message(err.Error()).statusCode(http.StatusBadRequest)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: resetPassword, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	log.Debugf("resetting tidb's password, namespace: %s", namespace)
	jobObj, err := m.SetPassword(namespace, cluster.RootPassword, false)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: resetPassword, namespace: %s, error: %v", namespace, err)
		}
		return
	}
	res.Payload.Job = &manager.Job{
		Name:   jobObj.Name,
		Status: "Running",
	}
	res.message(fmt.Sprintf("password for tidb cluster %s will be reset", namespace))
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: resetPassword, namespace: %s, error: %v", namespace, err)
	}
}

func (m Manager) getJob(req *restful.Request, resp *restful.Response) {
	res := newResponse("queryJob")
	jobName := req.PathParameter("job_name")
	namespace := req.PathParameter("namespace")

	log.Debugf("getting a job status, namespace: %s, jobName: %s", namespace, jobName)
	status, err := m.GetJobStatus(namespace, jobName)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: queryJob, namespace: %s, job: %s, error: %v", namespace, jobName, err)
		}
		return
	}
	res.payload(Payload{Job: &manager.Job{Name: jobName, Status: status}})
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: queryJob, namespace: %s, job: %s, error: %v", namespace, jobName, err)
	}
}

func (m Manager) getNodes(req *restful.Request, resp *restful.Response) {
	res := newResponse("queryNodes")
	nodeList, err := m.GetNodes()
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Error(err)
		}
		return
	}
	nodes := make([]*manager.Node, 0)
	for _, node := range nodeList.Items {
		nodeInfo, err := m.DescribeNode(&node)
		if err != nil {
			res.message(err.Error()).statusCode(http.StatusInternalServerError)
			if err = resp.WriteEntity(res); err != nil {
				log.Error(err)
			}
			return
		}

		nodes = append(nodes, nodeInfo)
	}
	res.payload(Payload{Nodes: nodes})
	if err := resp.WriteEntity(res); err != nil {
		log.Error(err)
	}
}

func (m Manager) getVolumes(req *restful.Request, resp *restful.Response) {
	res := newResponse("queryVolumes")
	namespace := req.QueryParameter("namespace")
	nodeName := req.QueryParameter("node_name")
	canSafeDeleted := req.QueryParameter("can_safe_deleted") == "true"
	volumes, err := m.GetVolumes(namespace, nodeName, canSafeDeleted)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Error(err)
		}
		return
	}
	res.payload(Payload{Volumes: volumes})
	if err := resp.WriteEntity(res); err != nil {
		log.Error(err)
	}
}

func (m Manager) deleteVolume(req *restful.Request, resp *restful.Response) {
	res := newResponse("deleteVolume")
	volName := req.PathParameter("name")
	err := m.DeleteVolume(volName)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Error(err)
		}
		return
	}
	if err := resp.WriteEntity(res); err != nil {
		log.Error(err)
	}
}

func (m Manager) deletePod(req *restful.Request, resp *restful.Response) {
	res := newResponse("deletePod")
	podName := req.PathParameter("pod_name")
	namespace := req.PathParameter("namespace")

	log.Debugf("deleting a pod, namespace: %s, podName: %s", namespace, podName)
	err := m.DeletePod(namespace, podName)
	if err != nil {
		if apierrs.IsNotFound(err) {
			res.message(err.Error()).statusCode(http.StatusNotFound)
			if err = resp.WriteEntity(res); err != nil {
				log.Errorf("failed to response, method: deletePod, Pod: %s/%s, error: %v", namespace, podName, err)
			}
			return
		}
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: deletePod, namespace: %s, pod: %s, error: %v", namespace, podName, err)
		}
		return
	}
	res.Message = fmt.Sprintf("pod %s will be deleted", podName)
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: deletePod, namespace: %s, pod: %s, error: %v", namespace, podName, err)
	}
}

func (m Manager) createBackup(req *restful.Request, resp *restful.Response) {
	res := newResponse("createBackup")
	namespace := req.PathParameter("namespace")
	param := &manager.BackupParam{}

	if err := req.ReadEntity(param); err != nil {
		res.message(err.Error()).statusCode(http.StatusBadRequest)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: createBackup, namespace: %s, error: %v", namespace, err)
		}
		return
	}

	job, err := m.Backup(namespace, param)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: createBackup database %s/%s: %v", namespace, param.Database, err)
		}
		return
	}
	res.Message = fmt.Sprintf("database %s/%s will be backup", namespace, param.Database)
	res.Payload.Job = &manager.Job{
		Name:   job.Name,
		Status: "Running",
	}
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: createBackup, namespace: %s, error: %v", namespace, err)
	}
}

func (m Manager) getBackup(req *restful.Request, resp *restful.Response) {
	res := newResponse("getBackup")
	namespace := req.PathParameter("namespace")
	backupName := req.PathParameter("backup_name")
	status, err := m.GetBackup(namespace, backupName)
	if err != nil {
		res.message(err.Error()).statusCode(http.StatusInternalServerError)
		if err = resp.WriteEntity(res); err != nil {
			log.Errorf("failed to response, method: getBackup, namespace: %s, error: %v", namespace, err)
		}
		return
	}
	res.payload(Payload{BackupStatus: status})
	if err := resp.WriteEntity(res); err != nil {
		log.Errorf("failed to response, method: getBackup, namespace: %s, error: %v", namespace, err)
	}
}
