package resource

import (
	"fmt"
)

type ExperimentKind string

const (
	ExperimentFailureKind          = ExperimentKind("failure")
	ExperimentNetworkPartitionKind = ExperimentKind("network-partition")
	ExperimentNetworkDelayKind     = ExperimentKind("network-delay")
	ExperimentNetworkLossKind      = ExperimentKind("network-loss")
	ExperimentIODelayKind          = ExperimentKind("io-delay")
	ExperimentTimeChaosKind        = ExperimentKind("time-chaos")
	ExperimentStressChaosKind      = ExperimentKind("stress-chaos")
	ExperimentSchedulesKind        = ExperimentKind("schedules")
)

type ChaosSelector interface {
	// HumanReadableName is the human-readable name of the chaos target
	HumanReadableName() string
}

// ExperimentSpec defines the chaos experiment spec
type ExperimentSpec interface {
	fmt.GoStringer
	Kind() ExperimentKind
}

// InjectChaosOptions defines the chaos source and target(optional) with a ExperimentSpec
type InjectChaosOptions struct {
	Selector []ChaosSelector
	Target   *[]ChaosSelector
	ExperimentSpec
}

var (
	_ ExperimentSpec = &FailureExperimentSpec{}
	_ ExperimentSpec = &NetworkPartitionSpec{}
	_ ExperimentSpec = &NetworkLatencySpec{}
	_ ExperimentSpec = &NetworkLossSpec{}
	_ ExperimentSpec = &IODelaySpec{}
	_ ExperimentSpec = &TimeChaosSpec{}
	_ ExperimentSpec = &StressChaosSpec{}
	_ ExperimentSpec = &SchedulerExperimentSpecWrapper{}
)

type ExperimentScheduler struct {
	// Cron defines a cron job rule.
	//
	// Some rule examples:
	// "0 30 * * * *" means to "Every hour on the half hour"
	// "@hourly"      means to "Every hour"
	// "@every 1h30m" means to "Every hour thirty"
	//
	// More rule info: https://godoc.org/github.com/robfig/cron
	Cron string
}

func (f *ExperimentScheduler) GoString() string {
	return fmt.Sprintf("ExperimentScheduler{Cron: %q}", f.Cron)
}

// SchedulerExperimentSpecWrapper wraps a ExperimentSpec with ExperimentSchedulesKind kind
type SchedulerExperimentSpecWrapper struct {
	ExperimentSpec
}

func (s SchedulerExperimentSpecWrapper) Kind() ExperimentKind {
	return ExperimentSchedulesKind
}

// FailureExperimentSpec injects the downtime error
type FailureExperimentSpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string
	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler
}

func (f *FailureExperimentSpec) GoString() string {
	return fmt.Sprintf("FailureExperimentSpec{Duration: %q, Scheduler: %#v}", f.Duration, f.Scheduler)
}

func (f *FailureExperimentSpec) Kind() ExperimentKind {
	return ExperimentFailureKind
}

// NetworkPartitionSpec injects the network partition(or split brain) error
// 	A network partition refers to network decomposition into relatively independent subnets for their separate optimization as well as network split due to the failure of network devices. In both cases the partition-tolerant behavior of subnets is expected. This means that even after the network is partitioned into multiple sub-systems, it still works correctly.
type NetworkPartitionSpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string

	// Direction indicate the network partition can be directional.
	// Valid Direction are `from`, `to`, `both`.
	Direction string

	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler
}

func (n *NetworkPartitionSpec) GoString() string {
	return fmt.Sprintf("NetworkPartitionSpec{Duration: %q, Direction: %s, Scheduler: %#v}", n.Duration, n.Direction, n.Scheduler)
}

func (n *NetworkPartitionSpec) Kind() ExperimentKind {
	return ExperimentNetworkPartitionKind
}

// NetworkLatencySpec specifies the network latency.
//  inject latency: Latency ± Jitter with Correlation% correlation
// For example if Latency = 90ms, Jitter = 20ms, then the network latency is 90ms ± 20ms.
//
// Correlation refers to correlation,
// because network conditions change smoothly and the delays of adjacent messages over a short period of time should be approximate rather than completely random.
//
// This value is a percentage and degenerates to the fixed delay case if it is 100.
//
// Or to the random delay case if it is 0
type NetworkLatencySpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string
	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler

	Latency     string
	Correlation string
	Jitter      string
}

func (n *NetworkLatencySpec) GoString() string {
	return fmt.Sprintf("NetworkLatencySpec{Duration: %q, Scheduler: %#v, Latency: %q, Jitter: %q, Correlation: %q}", n.Duration, n.Scheduler, n.Latency, n.Jitter, n.Correlation)
}

func (n *NetworkLatencySpec) Kind() ExperimentKind {
	return ExperimentNetworkDelayKind
}

type ChaosIdentifier interface {
	HumanReadableName() string
}

// ChaosClient is the client to create or delete a chaos experiment.
type ChaosClient interface {
	Create(opts InjectChaosOptions) (id ChaosIdentifier, err error)
	Delete(id ChaosIdentifier) error
}

// NetworkLossSpec specifies the network packets to drop randomly.
type NetworkLossSpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string
	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler

	// Loss defines the percentage of packet loss
	Loss string

	// NetworkChaos variation is not purely random, so to emulate that there is a correlation value as well.
	Correlation string
}

func (n *NetworkLossSpec) GoString() string {
	return fmt.Sprintf("NetworkLossSpec{Duration: %q, Scheduler: %#v, Loss: %q, Correlation: %q}", n.Duration, n.Scheduler, n.Loss, n.Correlation)
}

func (n *NetworkLossSpec) Kind() ExperimentKind {
	return ExperimentNetworkLossKind
}

// IODelaySpec specifies the IO Delay.
type IODelaySpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string
	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler

	// The mount path of the target volume
	//
	// for tikv, it's /var/lib/tikv
	VolumePath string
	// Defines the path of files for injecting IOChaos actions. It should be a glob for the files which you want to inject fault or delay. It is base on glob pattern and should be in the volumePath directory.
	//
	// for tikv, it's /var/lib/tikv/data/**/*
	Path string

	// Specifies the latency of the fault injection. The duration might be a string with a signed sequence of decimal numbers, each with an optional fraction and a unit suffix. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", and "h".
	Delay string
	// Defines the probability of injecting errors in percentage, max 100
	Percent int64
}

func (i IODelaySpec) GoString() string {
	return fmt.Sprintf("IODelaySpec{Duration: %q, Scheduler: %#v, Delay: %q, Path: %q, Percent: %d}", i.Duration, i.Scheduler, i.Delay, i.Path, i.Percent)
}

func (i IODelaySpec) Kind() ExperimentKind {
	return ExperimentIODelayKind
}

// TimeChaosSpec specifies the time skew
type TimeChaosSpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string
	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler

	// TimeOffset specifies the time offset.
	// It is a duration string with specified unit, such as 300ms, -1.5h. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	TimeOffset string
}

func (t TimeChaosSpec) GoString() string {
	return fmt.Sprintf("TimeChaosSpec{Duration: %q, Scheduler: %#v, TimeOffset: %q}", t.Duration, t.Scheduler, t.TimeOffset)
}

func (t TimeChaosSpec) Kind() ExperimentKind {
	return ExperimentTimeChaosKind
}

// StressChaosSpec specifies the **********
type StressChaosSpec struct {
	// A duration string is a possibly signed sequence of
	// decimal numbers, each with optional fraction and a unit suffix,
	// such as "300ms", "-1.5h" or "2h45m".
	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
	//
	// empty value means last until it's deleted
	Duration string
	// Scheduler and Duration should be omitted or defined at the same time
	Scheduler *ExperimentScheduler

	// Stressors defines plenty of stressors supported to stress system components out.
	// You can use one or more of them to make up various kinds of stresses. At least
	// one of the stressors should be specified.
	// +optional
	Stressors *Stressors `json:"stressors,omitempty"`
}

func (t StressChaosSpec) GoString() string {
	return fmt.Sprintf("StressChaosSpec{Duration: %q, Scheduler: %#v, stressors: %#v}", t.Duration, t.Scheduler, t.Stressors)
}

func (t StressChaosSpec) Kind() ExperimentKind {
	return ExperimentStressChaosKind
}

// Stressors defines plenty of stressors supported to stress system components out.
// You can use one or more of them to make up various kinds of stresses
type Stressors struct {
	// MemoryStressor stresses virtual memory out
	// +optional
	MemoryStressor *MemoryStressor `json:"memory,omitempty"`
	// CPUStressor stresses CPU out
	// +optional
	CPUStressor *CPUStressor `json:"cpu,omitempty"`
}

// Stressor defines common configurations of a stressor
type Stressor struct {
	// Workers specifies N workers to apply the stressor.
	// Maximum 8192 workers can run by stress-ng
	// +kubebuilder:validation:Maximum=8192
	Workers int64 `json:"workers"`
}

// MemoryStressor defines how to stress memory out
type MemoryStressor struct {
	Stressor `json:",inline"`

	// Size specifies N bytes consumed per vm worker, default is the total available memory.
	// One can specify the size as % of total available memory or in units of B, KB/KiB,
	// MB/MiB, GB/GiB, TB/TiB.
	// +optional
	Size string `json:"size,omitempty" webhook:"Bytes"`
}

// CPUStressor defines how to stress CPU out
type CPUStressor struct {
	Stressor `json:",inline"`
	// Load specifies P percent loading per CPU worker. 0 is effectively a sleep (no load) and 100
	// is full loading.
	// +optional
	Load int64 `json:"load,omitempty"`
}
