package client

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/juju/errors"
	agClient "github.com/pingcap/schrodinger/agent/client"
	"github.com/pingcap/schrodinger/pkg/types"
	"k8s.io/api/core/v1"
)

// CreateMysql creates mysql
func (c *Client) CreateMysql(version string, id int) (*types.APPConfig, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil, nil
	}
	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	app := &types.APPConfig{
		Type:      types.MysqlType,
		Name:      fmt.Sprintf("%s-%d", genMysqlName(version), id),
		Version:   version,
		Namespace: c.conf.Experiment.Namespace,
		Port:      3306,
	}

	data, err := json.Marshal(app)

	if err != nil {
		return app, errors.Trace(err)
	}

	data, err = postAndExtract(fmt.Sprintf("%s/app", c.managerAddr), data)
	if err != nil {
		Errorf(errors.ErrorStack(err))
		return app, errors.Trace(err)
	}

	err = json.Unmarshal(data, app)
	if err != nil {
		return app, errors.Trace(err)
	}
	return app, nil
}

// DeleteMysql deletes mysql
func (c *Client) DeleteMysql(version string) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}
	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	namespace := c.conf.Experiment.Namespace
	name := genMysqlName(version)
	_, err := deleteAndExtract(fmt.Sprintf("%s/app/%s/%s", c.managerAddr, namespace, name))
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// CreateTool creates tool
func (c *Client) CreateTool(url, binName, name, args, image string, envs []v1.EnvVar, actions ...types.Action) (*types.APPConfig, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil, nil
	}
	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	app := &types.APPConfig{
		Type:         types.ToolsType,
		Name:         name,
		Namespace:    c.conf.Experiment.Namespace,
		BinaryName:   binName,
		URL:          url,
		Args:         args,
		Envs:         envs,
		Actions:      actions,
		Image:        image,
		TestID:       c.conf.Self.ID,
		ExperimentID: c.conf.Experiment.ID,
		BoxID:        c.conf.Experiment.BoxID,
	}

	data, err := json.Marshal(app)
	if err != nil {
		return app, errors.Trace(err)
	}

	data, err = postAndExtract(fmt.Sprintf("%s/app", c.managerAddr), data)
	if err != nil {
		Errorf(errors.ErrorStack(err))
		return app, errors.Trace(err)
	}

	err = json.Unmarshal(data, app)
	if err != nil {
		return app, errors.Trace(err)
	}
	return app, nil
}

// CreateToolV2 creates tool using appConfig
func (c *Client) CreateToolV2(app *types.APPConfig) (*types.APPConfig, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil, nil
	}
	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	if app.Type == "" {
		app.Type = types.ToolsType
	}
	app.Namespace = c.conf.Experiment.Namespace

	data, err := json.Marshal(app)
	if err != nil {
		return app, errors.Trace(err)
	}

	data, err = postAndExtract(fmt.Sprintf("%s/app", c.managerAddr), data)
	if err != nil {
		Errorf(errors.ErrorStack(err))
		return app, errors.Trace(err)
	}

	err = json.Unmarshal(data, app)
	if err != nil {
		return app, errors.Trace(err)
	}
	return app, nil
}

// DeleteTool deletes tools
func (c *Client) DeleteTool(name string) error {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	namespace := c.conf.Experiment.Namespace
	_, err := deleteAndExtract(fmt.Sprintf("%s/app/%s/%s", c.managerAddr, namespace, name))
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// UploadCfgWithInfo upload config with info which can be used in CreateTool
// payload can be the content of file, savePath can be the path
func (c *Client) UploadCfgWithInfo(payload []byte, savePath string) ([]types.Action, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return nil, nil
	}

	if err := c.refreshConfig(); err != nil {
		Fatalf("init client config error %v", errors.ErrorStack(err))
	}

	object := &types.Object{
		Payload: payload,
	}

	data, err := json.Marshal(object)
	if err != nil {
		return nil, errors.Trace(err)
	}

	data, err = postAndExtract(fmt.Sprintf("%s/objects", c.managerAddr), data)
	if err != nil {
		return nil, errors.Trace(err)
	}

	err = json.Unmarshal(data, object)
	if err != nil {
		return nil, errors.Trace(err)
	}

	meta := &types.ConfigData{
		Addr:     fmt.Sprintf("%s/objects/%d", c.managerAddr, object.ID),
		SavePath: savePath,
	}

	data, err = json.Marshal(meta)
	if err != nil {
		return nil, errors.Trace(err)
	}

	action := types.Action{
		Type: types.ActionSaveCfg,
		Data: data,
	}
	return []types.Action{action}, nil
}

// GetAPPStartTime returns the start times of the tool
func (c *Client) GetAPPStartTime(conf *types.APPConfig) (time.Time, error) {
	cli := agClient.NewClient(fmt.Sprintf("%s:%d", conf.Host, c.conf.Manager.AgentPort))

	stat, err := cli.ProcessDetail(conf.Name)
	if err != nil {
		Errorf("get %s detail failed, error: %v", conf.Name, err)
		return time.Time{}, errors.Trace(err)
	}

	return stat.StartTime, nil
}
