package client

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

	"github.com/juju/errors"
	"github.com/pingcap/schrodinger/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
)

// ReplaceCat replace cats in experiment. Only online cat can do this
// In order to compatible , in other mode it will return nil
func (c *Client) ReplaceCat(cat *types.Cat) 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))
	}

	data, err := json.Marshal(cat)

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

	_, err = postAndExtract(fmt.Sprintf("%s/boxes/%d/experiments/%d/replaceCat", c.managerAddr, c.conf.Experiment.BoxID, c.conf.Experiment.ID), data)
	if err != nil {
		Errorf(errors.ErrorStack(err))
		return errors.Trace(err)
	}

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

// UpgradeCat upgrades cat. Only online environment support upgrade cat
// In order to compatible , in other mode it will return nil
func (c *Client) UpgradeCat(cat *types.Cat) 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))
	}

	data, err := json.Marshal(cat)

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

	_, err = postAndExtract(fmt.Sprintf("%s/cats/%d/upgrade?waitok=true", c.managerAddr, c.conf.Cat.ID), data)
	if err != nil {
		Errorf(errors.ErrorStack(err))
		return errors.Trace(err)
	}
	return nil
}

// BackupCat backups cat physically.
// In order to compatible , in other mode it will return nil
// The backup operation may take a while, but it will not be longer than 20 minutes.
// If the backupNames is empty, schrodinger will use a random name to backup.
func (c *Client) BackupCat(backupNames ...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))
	}

	backupName := ""
	if len(backupNames) > 0 {
		backupName = backupNames[0]
	}

	err := wait.Poll(30*time.Second, 60*time.Minute, func() (bool, error) {
		if _, err := postAndExtract(fmt.Sprintf("%s/cats/%d/backup/%s", c.managerAddr, c.conf.Cat.ID, backupName), nil); err != nil {
			if strings.Contains(err.Error(), "EOF") ||
				strings.Contains(err.Error(), "connection refused") {
				Warnf("connect to schrodinger manager failed, err: %v", err)
				return false, nil
			}
			return true, errors.Trace(err)
		}

		return true, nil
	})

	if err != nil {
		Errorf(errors.ErrorStack(err))
		return errors.Trace(err)
	}

	return errors.Trace(c.waitingCatStageIsRunning(30*time.Second, 60*time.Minute, types.CatStageBackuping))
}

// RestoreCat restores cat with the physical backup.
// In order to compatible , in other mode it will return nil
// The restore operation may take a while, but it will not be longer than 20 minutes.
// If the backupNames is empty, schrodinger will use the latest backup of the cat to restore it.
func (c *Client) RestoreCat(backupNames ...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))
	}

	backupName := ""
	if len(backupNames) > 0 {
		backupName = backupNames[0]
	}

	err := wait.Poll(30*time.Second, 60*time.Minute, func() (bool, error) {
		if _, err := postAndExtract(fmt.Sprintf("%s/cats/%d/restore/%s", c.managerAddr, c.conf.Cat.ID, backupName), nil); err != nil {
			if strings.Contains(err.Error(), "EOF") ||
				strings.Contains(err.Error(), "connection refused") {
				Warnf("connect to schrodinger manager failed, err: %v", err)
				return false, nil
			}
			return true, errors.Trace(err)
		}

		return true, nil
	})

	if err != nil {
		Errorf(errors.ErrorStack(err))
		return errors.Trace(err)
	}

	return errors.Trace(c.waitingCatStageIsRunning(30*time.Second, 60*time.Minute, types.CatStageRestoring))
}

// DestroyCat destroy cat directly
// In order to compatible , in other mode it will return nil
func (c *Client) DestroyCat() 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))
	}

	if c.conf.Cat == nil {
		return errors.New("the config of cat is nil")
	}

	if _, err := deleteAndExtract(fmt.Sprintf("%s/cats/%d", c.managerAddr, c.conf.Cat.ID)); err != nil {
		Errorf(errors.ErrorStack(err))
		return errors.Trace(err)
	}

	return nil
}

// IsBackupExists checks whether the specified backup exists or not.
func (c *Client) IsBackupExists(backupName string) (bool, error) {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return false, nil
	}

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

	data, err := getAndExtract(fmt.Sprintf("%s/cats/%d/backups/list", c.managerAddr, c.conf.Cat.ID))
	if err != nil {
		Errorf(errors.ErrorStack(err))
		return false, errors.Trace(err)
	}

	var list []*types.TiDBClusterBackup

	if err := json.Unmarshal(data, &list); err != nil {
		return false, errors.Trace(err)
	}

	for _, item := range list {
		if item.Name == backupName {
			return true, nil
		}
	}
	return false, nil
}

// CatConfig returns cat config
func (c *Client) CatConfig() types.TiDBClusterConfig {
	if !c.isOnline() {
		Warnf("only mode online is permitted. the mode is %s", c.mode)
		return types.TiDBClusterConfig{}
	}

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

	return c.conf.Cat.Config
}
