package pods

import (
	"encoding/json"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/docker/docker/pkg/term"
	"gopkg.in/igm/sockjs-go.v2/sockjs"
	"k8s-console/utils"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/remotecommand"
	"k8s.io/kubernetes/pkg/util/interrupt"
	"net/http"
	"regexp"
	"strings"
)

var debugLogger = logs.GetLogger("debug")

type TerminalSockJs struct {
	conn      sockjs.Session
	sizeChan  chan *remotecommand.TerminalSize
	context   string
	namespace string
	pod       string
	container string
}

//读取
func (ts TerminalSockJs) Read(p []byte) (int, error) {
	var reply string
	var msg map[string]uint16
	reply, err := ts.conn.Recv()
	if err != nil {
		errorLogger.Println(err)
		return 0, err
	}
	if err := json.Unmarshal([]byte(reply), &msg); err != nil {
		return copy(p, reply), nil
	} else {
		ts.sizeChan <- &remotecommand.TerminalSize{
			msg["cols"],
			msg["rows"],
		}
		return 0, nil
	}
}

//发送
func (ts TerminalSockJs) Write(p []byte) (int, error) {
	str := string(p)
	if strings.Contains(str, `/bin/sh: no such file or directory": unknown`) {
		str = utils.GetI18nLang("en", "terminalSockJsWrite_notSupportAccessToContainer") + " \r\n"
	}
	err := ts.conn.Send(str)
	if err != nil {
		errorLogger.Println(err)
	}
	return len(p), err
}

// 实现tty size queue
func (ts *TerminalSockJs) Next() *remotecommand.TerminalSize {
	size := <-ts.sizeChan
	debugLogger.Printf("terminal size to width: %d height: %d", size.Width, size.Height)
	return size
}

//解析config文件
func buildConfigFromContextFlags(context, kubeconfigPath string) (*rest.Config, error) {
	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
		&clientcmd.ConfigOverrides{
			CurrentContext: context,
		}).ClientConfig()
}

// 处理输入输出与sockjs 交互
func Handler(ts *TerminalSockJs, cmd string) error {
	//解析config
	config, err := buildConfigFromContextFlags(ts.context, beego.AppConfig.String("configPath"))
	if err != nil {
		errorLogger.Printf("解析config文件失败: %s", err.Error())
		return err
	}
	groupVersion := schema.GroupVersion{
		Group:   "",
		Version: "v1",
	}
	config.GroupVersion = &groupVersion
	config.APIPath = "/api"
	config.ContentType = runtime.ContentTypeJSON
	config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		errorLogger.Printf("创建restClient失败: %s", err.Error())
		return err
	}
	fn := func() error {
		req := restClient.Post().
			Resource("pods").
			Name(ts.pod).
			Namespace(ts.namespace).
			SubResource("exec").
			Param("container", ts.container).
			Param("stdin", "true").
			Param("stdout", "true").
			Param("stderr", "true").
			Param("command", cmd).Param("tty", "true")
		req.VersionedParams(
			&v1.PodExecOptions{
				Container: ts.container,
				Command:   []string{},
				Stdin:     true,
				Stdout:    true,
				Stderr:    true,
				TTY:       true,
			},
			scheme.ParameterCodec,
		)
		executor, err := remotecommand.NewSPDYExecutor(config, http.MethodPost, req.URL())
		if err != nil {
			errorLogger.Printf("连接服务器失败:%s", err.Error())
			return err
		}
		return executor.Stream(remotecommand.StreamOptions{
			Stdin:             ts,
			Stdout:            ts,
			Stderr:            ts,
			Tty:               true,
			TerminalSizeQueue: ts,
		})
	}
	inFd, _ := term.GetFdInfo(ts.conn)
	state, err := term.SaveState(inFd)
	return interrupt.Chain(nil, func() {
		e := term.RestoreTerminal(inFd, state)
		errorLogger.Println(e)
	}).Run(fn)
}

// 自定义http.handler 创建websocket
func (ts TerminalSockJs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	nameSpace := r.FormValue("nameSpace")
	podName := r.FormValue("podName")
	container := r.FormValue("container")
	infoLogger.Printf("nameSpace: %s podName: %s container: %s", nameSpace, podName, container)

	sockJsHandler := func(session sockjs.Session) {
		t := &TerminalSockJs{session, make(chan *remotecommand.TerminalSize),
			"", nameSpace, podName, container}

		//校验license是否失效
		//verifyLicenseValidRes := controllers.VerifyLicenseValid("zh")
		//logs.GetLogger("info").Println(verifyLicenseValidRes)
		//if verifyLicenseValidRes.Status == 300 {
		//	logs.GetLogger("error").Printf("校验license是否失效: %s", verifyLicenseValidRes.Message)
		//	t.conn.Send(verifyLicenseValidRes.Message + "\r\n")
		//	t.conn.Close(1024, "Close")
		//	return
		//}

		if err := Handler(t, "/bin/sh"); err != nil {
			errMsg := err.Error()
			errorLogger.Println(errMsg)
			if match, e := regexp.MatchString(`pods "`+podName+`" not found`, errMsg); match && e == nil {
				t.conn.Send(errMsg + "\r\n")
				t.conn.Close(1024, "Close")
				return
			}
		}
	}
	sockjs.NewHandler("/terminalWebSocket", sockjs.DefaultOptions, sockJsHandler).ServeHTTP(w, r)
}
