// Copyright 2019 Yunion
//
// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package command

import (
	"fmt"
	"os"
	"os/exec"
	"time"

	"yunion.io/x/jsonutils"
	"yunion.io/x/log"

	webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole"
	"yunion.io/x/onecloud/pkg/mcclient"
	o "yunion.io/x/onecloud/pkg/webconsole/options"
)

type K8sEnv struct {
	Session    *mcclient.ClientSession
	Cluster    string
	Namespace  string
	Pod        string
	Container  string
	Kubeconfig string
	Data       jsonutils.JSONObject
}

type Kubectl struct {
	*BaseCommand
	kubeconfig string
	// For display info
	InstanceName string
	IPs          []string
}

func NewKubectlCommand(s *mcclient.ClientSession, kubeconfig, namespace string) *Kubectl {
	name := o.Options.KubectlPath
	if len(namespace) == 0 {
		namespace = "default"
	}
	cmd := NewBaseCommand(s, name, "--namespace", namespace)
	return &Kubectl{
		BaseCommand: cmd,
		kubeconfig:  kubeconfig,
	}
}

func (c *Kubectl) SetInstanceName(name string) {
	c.InstanceName = name
}

func (c *Kubectl) GetInstanceName() string {
	return c.InstanceName
}

func (c *Kubectl) SetIPs(ips []string) {
	c.IPs = ips
}

func (c *Kubectl) GetIPs() []string {
	return c.IPs
}

func (c *Kubectl) GetCommand() *exec.Cmd {
	cmd := c.BaseCommand.GetCommand()
	cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", c.kubeconfig))
	return cmd
}

func (c Kubectl) GetProtocol() string {
	return PROTOCOL_TTY
}

func (c *Kubectl) Cleanup() error {
	log.Debugf("Remove temp kubeconfig file: %s", c.kubeconfig)
	return os.Remove(c.kubeconfig)
}

type KubectlExec struct {
	*Kubectl
}

func (c *Kubectl) Exec() *KubectlExec {
	// Execute a command in a container
	cmd := &KubectlExec{
		Kubectl: c,
	}
	cmd.AppendArgs("exec")
	return cmd
}

func (c *KubectlExec) Stdin() *KubectlExec {
	// -i: Pass stdin to the container
	c.AppendArgs("-i")
	return c
}

func (c *KubectlExec) TTY() *KubectlExec {
	// -t: Stdin is a TTY
	c.AppendArgs("-t")
	return c
}

func (c *KubectlExec) Container(name string) *KubectlExec {
	if len(name) == 0 {
		return c
	}
	// -c: Container name. If ommitted, the first container in the pod will be chosen
	c.AppendArgs("-c", name)
	return c
}

func (c *KubectlExec) Pod(name string) *KubectlExec {
	// Pod name
	c.AppendArgs(name)
	return c
}

func (c *KubectlExec) Command(cmd string, args ...string) *KubectlExec {
	c.AppendArgs("--", cmd)
	c.AppendArgs(args...)
	return c
}

func NewPodBashCommand(env *K8sEnv) ICommand {
	shellRequest := webconsole_api.SK8sShellRequest{}
	err := env.Data.Unmarshal(&shellRequest)
	if err != nil {
		log.Errorf("env.Data.Unmarshal SK8sShellRequest: %s", err)
	}
	if shellRequest.Command == "" {
		shellRequest.Command = "sh"
	}
	args := make([]string, 0)
	if len(shellRequest.Env) > 0 {
		for k, v := range shellRequest.Env {
			args = append(args, fmt.Sprintf("%s=%s", k, v))
		}
		args = append(args, shellRequest.Command)
		shellRequest.Command = "env"
	}
	args = append(args, shellRequest.Args...)

	kExec := NewKubectlCommand(env.Session, env.Kubeconfig, env.Namespace).Exec().
		Stdin().
		TTY().
		Pod(env.Pod).
		Container(env.Container).
		Command(shellRequest.Command, args...)
	if shellRequest.DisplayInfo != nil {
		kExec.SetInstanceName(shellRequest.DisplayInfo.InstanceName)
		kExec.SetIPs(shellRequest.DisplayInfo.IPs)
	}
	return kExec
}

type KubectlLog struct {
	*Kubectl
}

func (c *Kubectl) Logs() *KubectlLog {
	// Print the logs for a container in a pod
	cmd := &KubectlLog{
		Kubectl: c,
	}
	cmd.AppendArgs("logs")
	return cmd
}

func (c *KubectlLog) Follow() *KubectlLog {
	// -f: Specify if the logs should be streamed
	c.AppendArgs("-f")
	return c
}

func (c *KubectlLog) Pod(name string) *KubectlLog {
	// Pod name
	c.AppendArgs(name)
	return c
}

func (c *KubectlLog) Container(name string) *KubectlLog {
	if name == "" {
		return c
	}
	// -c, --container='': Print the logs of this container
	c.AppendArgs("-c", name)
	return c
}

func (c *KubectlLog) Since(data jsonutils.JSONObject) *KubectlLog {
	durationStr, _ := data.GetString("since")
	if durationStr == "" {
		return c
	}
	// --since: Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used
	if _, err := time.ParseDuration(durationStr); err != nil {
		log.Errorf("Failed to parse log since opt: %v", err)
		return c
	}
	c.AppendArgs("--since", durationStr)
	return c
}

func NewPodLogCommand(env *K8sEnv) ICommand {
	return NewKubectlCommand(env.Session, env.Kubeconfig, env.Namespace).Logs().
		Follow().
		Pod(env.Pod).
		Since(env.Data).
		Container(env.Container)
}