package chaos

import (
	"context"
	"errors"
	"fmt"
	"github.com/pingcap/endless/pkg/util/schedule"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap/test-infra/sdk/resource"
	"math/rand"
	"time"
)

const (
	ExperimentFullDiskKind = resource.ExperimentKind("full-disk")
)

func InjectFault(ctx context.Context, tc resource.TiDBCluster, nodeType resource.TiDBCompType, faultType resource.ExperimentKind) (resource.ChaosIdentifier, error) {
	var err error
	var opt resource.TiDBClusterSelectorOpts
	var chaosId resource.ChaosIdentifier
	switch nodeType {
	case resource.TiKV:
		opt = resource.TiDBClusterSelectorOpts{
			TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
				All: true,
			},
		}
	case resource.PD:
		opt = resource.TiDBClusterSelectorOpts{
			TiDBClusterPDSelectorSpec: &resource.TiDBClusterPDSelectorSpec{
				All: true,
			},
		}
	case resource.TiFlash:
		opt = resource.TiDBClusterSelectorOpts{
			TiDBClusterTiFlashSelectorSpec: &resource.TiDBClusterTiFlashSelectorSpec{
				All: true,
			},
		}
	default:
		return nil, errors.New("unknown component")
	}

	selector, err := tc.ChaosSelect(opt)
	if err != nil {
		return nil, err
	}
	op, err := NewFullDiskTiFLash(tc)
	if err != nil {
		return nil, err
	}
	switch faultType {
	case resource.ExperimentFailureKind:
		log.Info(fmt.Sprintf("begin to select %s to shutdown instance.", nodeType))
		chaosId, err = tc.ChaosClient().Create(resource.InjectChaosOptions{
			Selector: []resource.ChaosSelector{selector[rand.Intn(len(selector))]},
			ExperimentSpec: &resource.FailureExperimentSpec{
				Duration:  "2m",
				Scheduler: &resource.ExperimentScheduler{Cron: "@every 5m"},
			},
		})
	case resource.ExperimentNetworkPartitionKind:
		log.Info(fmt.Sprintf("begin to select %s to network Partition.", nodeType))
		chaosId, err = tc.ChaosClient().Create(resource.InjectChaosOptions{
			Selector: []resource.ChaosSelector{selector[0]},
			Target:   &[]resource.ChaosSelector{selector[1]},
			ExperimentSpec: &resource.NetworkPartitionSpec{
				Duration:  "30s",
				Scheduler: &resource.ExperimentScheduler{Cron: "@every 1m"},
			},
		})
	case resource.ExperimentNetworkDelayKind:
		log.Info(fmt.Sprintf("begin to select %s to network delay.", nodeType))
		chaosId, err = tc.ChaosClient().Create(resource.InjectChaosOptions{
			Selector: []resource.ChaosSelector{selector[rand.Intn(len(selector))]},
			ExperimentSpec: &resource.NetworkLatencySpec{
				Duration:    "30s",
				Scheduler:   &resource.ExperimentScheduler{Cron: "@every 1m"},
				Latency:     "10s",
				Correlation: "25",
				// Jitter:      "33",
			},
		})
	case resource.ExperimentNetworkLossKind:
		log.Info(fmt.Sprintf("begin to select %s to network Loss.", nodeType))
		chaosId, err = tc.ChaosClient().Create(resource.InjectChaosOptions{
			Selector: []resource.ChaosSelector{selector[rand.Intn(len(selector))]},
			ExperimentSpec: &resource.NetworkLossSpec{
				Duration:    "30s",
				Scheduler:   &resource.ExperimentScheduler{Cron: "@every 1m"},
				Loss:        "70",
				Correlation: "25",
			},
		})
	case ExperimentFullDiskKind:
		if nodeType != resource.TiFlash {
			return nil, errors.New("ExperimentFullDiskKind just support tiflash")
		}
		log.Info(fmt.Sprintf("begin to select %s to full disk.", nodeType))
		err = schedule.RunPeriodically(ctx, "@every 10m", 7*time.Minute, op, "randomFullTiFlashDisk")
	default:
		return nil, errors.New("unknown fault type")
	}
	if chaosId != nil {
		log.Info(chaosId.HumanReadableName())
	}
	return chaosId, err
}

func DeleteChaos(tc resource.TiDBCluster, cid resource.ChaosIdentifier) error {
	if cid != nil {
		log.Info("delete chaos", zap.String("chaos info", cid.HumanReadableName()))
		return tc.ChaosClient().Delete(cid)
	} else {
		log.Info("delete chaos")
	}
	return nil
}
