package steps

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/pingcap/test-infra/caselib/pkg/host"
	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/resource"
)

type kvFailure struct {
	target     *apiresource.ResourceHandle
	failNodeID int
	duration   time.Duration
}

func NewKVFailure(target *apiresource.ResourceHandle, duration time.Duration) *kvFailure {
	return &kvFailure{
		target:   target,
		duration: duration,
	}
}

func NewKVFailureWithID(target *apiresource.ResourceHandle, failNodeID int, duration time.Duration) *kvFailure {
	return &kvFailure{
		target:     target,
		failNodeID: failNodeID,
		duration:   duration,
	}
}

func (s *kvFailure) Execute(ctx context.Context) error {
	all := true
	if strconv.Itoa(s.failNodeID) != "" {
		all = false
	}

	return NewChaosTask(
		s.target,
		resource.TiDBClusterSelectorOpts{
			TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
				Peer: &s.failNodeID,
				All:  all,
			},
		},
		&resource.FailureExperimentSpec{},
		s.duration).Execute(ctx)
}

func (s *kvFailure) String() string {
	return fmt.Sprintf("kvFailure: duration=%+v, failNode=%+v", s.duration, s.failNodeID)
}

type allTiKVNetworkLoss struct {
	target      *apiresource.ResourceHandle
	duration    time.Duration
	loss        string
	correlation string
}

func NewAllTiKVNetworkLoss(target *apiresource.ResourceHandle, duration time.Duration) Step {
	return &allTiKVNetworkLoss{
		target:      target,
		duration:    duration,
		loss:        "100",
		correlation: "25",
	}
}

func (s *allTiKVNetworkLoss) String() string {
	return fmt.Sprintf("injectNetworkChaos: duration=%+v, loss=%+v, correlation=%+v", s.duration, s.loss, s.correlation)
}

func (s *allTiKVNetworkLoss) Execute(ctx context.Context) error {
	return newNetworkLossChaosTask(
		s.target,
		resource.TiDBClusterSelectorOpts{
			TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
				All: true,
			},
		},
		s.duration).Execute(ctx)
}

type allKVStress struct {
	target    *apiresource.ResourceHandle
	duration  time.Duration
	stressors *resource.Stressors
}

func (s *allKVStress) String() string {
	return fmt.Sprintf("kvStress: target=%+v, duration=%+v, stressores=%+v, ", *s.target, s.duration, *s.stressors)
}

func (s *allKVStress) Execute(ctx context.Context) error {
	return NewChaosTask(s.target, resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
		All: true,
	}}, &resource.StressChaosSpec{Stressors: s.stressors}, s.duration).Execute(ctx)
}

func NewAllKVStress(target *apiresource.ResourceHandle, duration time.Duration, stressors *resource.Stressors) Step {
	return &allKVStress{target, duration, stressors}
}

func (b *Builder) AllKVStress(target *apiresource.ResourceHandle, duration time.Duration, stressors *resource.Stressors) *Builder {
	b.steps = append(b.steps, &allKVStress{target, duration, stressors})
	return b
}

type allKVMemStress struct {
	target   *apiresource.ResourceHandle
	percent  int
	duration time.Duration
}

func (s *allKVMemStress) Execute(ctx context.Context) error {
	resovled, err := core.ResolveResourceHandle(s.target)
	if err != nil {
		return err
	}
	tc := resovled.(resource.TiDBCluster)
	h := &host.TiKVHost{TiDBCluster: tc}
	memory, err := h.GetTotalMemory()
	if err != nil {
		return err
	}
	loadMemory := memory * s.percent / 100

	return NewAllKVStress(s.target, s.duration,
		&resource.Stressors{MemoryStressor: &resource.MemoryStressor{Stressor: resource.Stressor{16},
			Size: strconv.Itoa(loadMemory) + "B"}}).Execute(ctx)
}

func (s *allKVMemStress) String() string {
	return fmt.Sprintf("allKVMemStress: target=%+v, duration=%+v, percent=%d, ", *s.target, s.duration, s.percent)
}

func NewAllKVMemStress(target *apiresource.ResourceHandle, percent int, duration time.Duration) Step {
	return &allKVMemStress{target: target, percent: percent, duration: duration}
}

func (b *Builder) AllKVMemStress(target *apiresource.ResourceHandle, percent int, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewAllKVMemStress(target, percent, duration))
	return b
}

type allKVCPUStress struct {
	target   *apiresource.ResourceHandle
	percent  int
	duration time.Duration
}

func (s *allKVCPUStress) Execute(ctx context.Context) error {

	return NewAllKVStress(s.target, s.duration,
		&resource.Stressors{CPUStressor: &resource.CPUStressor{Stressor: resource.Stressor{16},
			Load: int64(s.percent)}}).Execute(ctx)
}

func (s *allKVCPUStress) String() string {
	return fmt.Sprintf("allKVCPUStress: target=%+v, duration=%+v, percent=%d, ", *s.target, s.duration, s.percent)
}

func NewAllKVCPUStress(target *apiresource.ResourceHandle, percent int, duration time.Duration) Step {
	return &allKVCPUStress{target: target, percent: percent, duration: duration}
}

func (b *Builder) AllKVCPUStress(target *apiresource.ResourceHandle, percent int, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewAllKVCPUStress(target, percent, duration))
	return b
}

type KVStress struct {
	target    *apiresource.ResourceHandle
	nodeID    int
	duration  time.Duration
	stressors *resource.Stressors
}

func (s *KVStress) String() string {
	return fmt.Sprintf("kvStress: target=%+v, nodeid=%d, duration=%+v, stressores=%+v, ", *s.target, s.nodeID, s.duration, *s.stressors)
}

func (s *KVStress) Execute(ctx context.Context) error {
	return NewChaosTask(s.target, resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
		Peer: &s.nodeID,
	}}, &resource.StressChaosSpec{Stressors: s.stressors}, s.duration).Execute(ctx)
}

func NewKVStress(target *apiresource.ResourceHandle, nodeID int, duration time.Duration, stressors *resource.Stressors) Step {
	return &KVStress{target, nodeID, duration, stressors}
}

func (b *Builder) KVStress(target *apiresource.ResourceHandle, nodeID int, duration time.Duration, stressors *resource.Stressors) *Builder {
	b.steps = append(b.steps, NewKVStress(target, nodeID, duration, stressors))
	return b
}

type allKVIODelay struct {
	target   *apiresource.ResourceHandle
	delay    string
	percent  int64
	duration time.Duration
}

func (s *allKVIODelay) Execute(ctx context.Context) error {
	return NewChaosTask(s.target, resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
		All: true,
	}}, &resource.IODelaySpec{VolumePath: "/var/lib/tikv", Path: "/var/lib/tikv/data/**/*", Delay: s.delay, Percent: s.percent}, s.duration).
		Execute(ctx)
}

func (s *allKVIODelay) String() string {
	return fmt.Sprintf("kvIODelay: target=%+v, duration=%+v, delay=%s,percent=%d", *s.target, s.duration, s.delay, s.percent)
}

func NewAllKVIODelay(target *apiresource.ResourceHandle, delay string, percent int64, duration time.Duration) Step {
	return &allKVIODelay{target, delay, percent, duration}
}

func (b *Builder) AllKVIODelay(target *apiresource.ResourceHandle, delay string, percent int64, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewAllKVIODelay(target, delay, percent, duration))
	return b
}

type allKVTimeChaos struct {
	target   *apiresource.ResourceHandle
	offset   string
	duration time.Duration
}

func (s *allKVTimeChaos) Execute(ctx context.Context) error {
	return NewChaosTask(s.target, resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
		All: true,
	}}, &resource.TimeChaosSpec{TimeOffset: s.offset}, s.duration).
		Execute(ctx)
}

func (s *allKVTimeChaos) String() string {
	return fmt.Sprintf("kvTimeChaos: target=%+v, duration=%+v, timeOffset=%s", *s.target, s.duration, s.offset)
}

func NewAllKVTimeChaos(target *apiresource.ResourceHandle, offset string, duration time.Duration) Step {
	return &allKVTimeChaos{target, offset, duration}
}

func (b *Builder) AllKVTimeChaos(target *apiresource.ResourceHandle, offset string, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewAllKVTimeChaos(target, offset, duration))
	return b
}

type allKV2PDNetworkPartition struct {
	target   *apiresource.ResourceHandle
	duration time.Duration
}

func (s *allKV2PDNetworkPartition) Execute(ctx context.Context) error {
	chaosStep := &networkPartitionChaosTask{source: s.target, target: s.target, duration: s.duration,
		sourceOption: resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
			All: true,
		}},
		targetOption: resource.TiDBClusterSelectorOpts{TiDBClusterPDSelectorSpec: &resource.TiDBClusterPDSelectorSpec{Peer: new(int)}},
		spec:         &resource.NetworkPartitionSpec{Direction: "to"}}
	return chaosStep.Execute(ctx)
}

func (s *allKV2PDNetworkPartition) String() string {
	return fmt.Sprintf("KV2PDNetworkPartition: target=%+v, duration=%+v", *s.target, s.duration)
}

func NewAllKV2PDNetworkPartition(target *apiresource.ResourceHandle, duration time.Duration) Step {
	return &allKV2PDNetworkPartition{target, duration}
}

func (b *Builder) AllKV2PDNetworkPartition(target *apiresource.ResourceHandle, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewAllKV2PDNetworkPartition(target, duration))
	return b
}

type networkPartitionBwTiKV struct {
	target           *apiresource.ResourceHandle
	failNodeID       int
	targetFailNodeID int
	duration         time.Duration
}

func (n networkPartitionBwTiKV) String() string {
	return fmt.Sprintf("Network partition between TiKV %d and %d", n.failNodeID, n.targetFailNodeID)
}

func (n networkPartitionBwTiKV) Execute(ctx context.Context) error {
	chaosStep := &networkPartitionChaosTask{source: n.target, target: n.target, duration: n.duration,
		sourceOption: resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
			Peer: &n.failNodeID,
		}},
		targetOption: resource.TiDBClusterSelectorOpts{TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
			Peer: &n.targetFailNodeID,
		}},
		spec: &resource.NetworkPartitionSpec{Direction: "both"}}
	return chaosStep.Execute(ctx)
}

func NewNetworkPartitionBwTiKV(target *apiresource.ResourceHandle, failNodeID, targetFailNodeID int, duration time.Duration) Step {
	return &networkPartitionBwTiKV{target: target, failNodeID: failNodeID, targetFailNodeID: targetFailNodeID, duration: duration}
}

func (b *Builder) NetworkPartitionBwTiKV(target *apiresource.ResourceHandle, failNodeID, targetFailNodeID int, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewNetworkPartitionBwTiKV(target, failNodeID, targetFailNodeID, duration))
	return b
}
