package host

import (
	"fmt"
	"strings"
	"time"

	"github.com/pingcap/errors"
	"github.com/pingcap/test-infra/caselib/pkg/model/workload"
	"github.com/pingcap/test-infra/sdk/resource"
)

type TiUPDmClusterInfo struct {
	ClusterMeta TiUPDmClusterMeta `json:"cluster_meta,omitempty"`
	Instances   []TiUPDmInstance  `json:"instances,omitempty"`
}

type TiUPDmClusterMeta struct {
	ClusterType    string `json:"cluster_type"`
	ClusterName    string `json:"cluster_name"`
	ClusterVersion string `json:"cluster_version"`
	DeployUser     string `json:"deploy_user"`
	SSHType        string `json:"ssh_type"`
	TLSEnabled     bool   `json:"tls_enabled"`
	TLSCACert      string `json:"tls_ca_cert,omitempty"`
	TLSClientCert  string `json:"tls_client_cert,omitempty"`
	TLSClientKey   string `json:"tls_client_key,omitempty"`
	DashboardURL   string `json:"dashboard_url,omitempty"`
}

type TiUPDmInstance struct {
	ID        string `json:"id"`
	Role      string `json:"role"`
	Host      string `json:"host"`
	Ports     string `json:"ports"`
	OSArch    string `json:"os_arch"`
	Status    string `json:"status"`
	Since     string `json:"since"`
	DataDir   string `json:"data_dir"`
	DeployDir string `json:"deploy_dir"`

	ComponentName string
	Port          int
}

type TiUPDmCluster struct {
	diag *Diag

	ControlNodeHost *WorkloadHost
	TestBed         string

	DmClusterName string
	Topology      string
	CMDTimeout    time.Duration

	// ClusterInfo TiUPClusterInfo
}

func NewTiUPDmCluster(controlNode resource.WorkloadNode, tiupTopo string, dmClusterName string) *TiUPDmCluster {
	// TODO: It's really dum to let user pass in controlNodeHostname
	// but there isn't method to get hostname of a workloadNode
	workloadHost := NewWorkloadHost(controlNode)
	tiupDmCluster := &TiUPDmCluster{
		TestBed:         controlNode.Handle().Testbed,
		ControlNodeHost: workloadHost,
		DmClusterName:   dmClusterName,
		Topology:        tiupTopo,
		CMDTimeout:      10 * time.Minute,
	}
	return tiupDmCluster
}

// Deploy will deploy tiup cluster with haproxy.
func (t *TiUPDmCluster) Deploy(version string, flags ...string) error {
	topoFilepath, err := writeTmpFile(t.ControlNodeHost.workload, "tiup.yaml", t.Topology)
	if err != nil {
		return err
	}
	return t.run(fmt.Sprintf("tiup dm deploy %s %s %s", t.DmClusterName, version, topoFilepath), flags)
}

func (t *TiUPDmCluster) Start(flags ...string) error {
	err := t.run(fmt.Sprintf("tiup dm start %s", t.DmClusterName), flags)
	if err != nil {
		return err
	}
	return nil
}

func (t *TiUPDmCluster) Stop(flags ...string) error {
	return t.run(fmt.Sprintf("tiup dm stop %s", t.DmClusterName), flags)
}

func (t *TiUPDmCluster) Restart(flags ...string) error {
	err := t.run(fmt.Sprintf("tiup dm restart %s", t.DmClusterName), flags)
	if err != nil {
		return err
	}
	return nil
}

func (t *TiUPDmCluster) Display(flags ...string) error {
	err := t.run(fmt.Sprintf("tiup dm display %s", t.DmClusterName), flags)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (t *TiUPDmCluster) run(cmd string, flags []string) error {
	flags = append(flags, "-y") // Skip all confirmations
	cmd += " "
	cmd += strings.Join(flags, " ")
	return t.ControlNodeHost.Run(workload.NewCMD(cmd, t.CMDTimeout))
}

func (t *TiUPDmCluster) ScaleOut(flags ...string) error {
	topoFilepath, err := writeTmpFile(t.ControlNodeHost.workload, "scale.yaml", t.Topology)
	if err != nil {
		return err
	}
	return t.run(fmt.Sprintf("tiup dm scale-out %s %s", t.DmClusterName, topoFilepath), flags)
}

func (t *TiUPDmCluster) ScaleIn(flags ...string) error {
	return t.run(fmt.Sprintf("tiup dm scale-in %s -N %s", t.DmClusterName, t.Topology), flags)
}

func (t *TiUPDmCluster) Upgrade(version string, flags ...string) error {
	return t.run(fmt.Sprintf("tiup dm upgrade %s  %s", t.DmClusterName, version), flags)
}
