// Copyright 2018 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tester

import (
	"fmt"
	"time"

	"go.etcd.io/etcd/tests/v3/functional/rpcpb"

	"go.uber.org/zap"
)

// Stresser defines stressing client operations.
type Stresser interface {
	// Stress starts to stress the etcd cluster
	Stress() error
	// Pause stops the stresser from sending requests to etcd. Resume by calling Stress.
	Pause() map[string]int
	// Close releases all of the Stresser's resources.
	Close() map[string]int
	// ModifiedKeys reports the number of keys created and deleted by stresser
	ModifiedKeys() int64
}

// newStresser creates stresser from a comma separated list of stresser types.
func newStresser(clus *Cluster, m *rpcpb.Member) (stressers []Stresser) {
	// TODO: Too intensive stressing clients can panic etcd member with
	// 'out of memory' error. Put rate limits in server side.
	ks := &keyStresser{
		lg:                clus.lg,
		m:                 m,
		keySize:           int(clus.Tester.StressKeySize),
		keyLargeSize:      int(clus.Tester.StressKeySizeLarge),
		keySuffixRange:    int(clus.Tester.StressKeySuffixRange),
		keyTxnSuffixRange: int(clus.Tester.StressKeySuffixRangeTxn),
		keyTxnOps:         int(clus.Tester.StressKeyTxnOps),
		clientsN:          int(clus.Tester.StressClients),
		rateLimiter:       clus.rateLimiter,
	}
	ksExist := false

	for _, s := range clus.Tester.Stressers {
		clus.lg.Info(
			"creating stresser",
			zap.String("type", s.Type),
			zap.Float64("weight", s.Weight),
			zap.String("endpoint", m.EtcdClientEndpoint),
		)
		switch s.Type {
		case "KV_WRITE_SMALL":
			ksExist = true
			ks.weightKVWriteSmall = s.Weight
		case "KV_WRITE_LARGE":
			ksExist = true
			ks.weightKVWriteLarge = s.Weight
		case "KV_READ_ONE_KEY":
			ksExist = true
			ks.weightKVReadOneKey = s.Weight
		case "KV_READ_RANGE":
			ksExist = true
			ks.weightKVReadRange = s.Weight
		case "KV_DELETE_ONE_KEY":
			ksExist = true
			ks.weightKVDeleteOneKey = s.Weight
		case "KV_DELETE_RANGE":
			ksExist = true
			ks.weightKVDeleteRange = s.Weight
		case "KV_TXN_WRITE_DELETE":
			ksExist = true
			ks.weightKVTxnWriteDelete = s.Weight

		case "LEASE":
			stressers = append(stressers, &leaseStresser{
				stype:        rpcpb.StresserType_LEASE,
				lg:           clus.lg,
				m:            m,
				numLeases:    10, // TODO: configurable
				keysPerLease: 10, // TODO: configurable
				rateLimiter:  clus.rateLimiter,
			})

		case "ELECTION_RUNNER":
			reqRate := 100
			args := []string{
				"election",
				fmt.Sprintf("%v", time.Now().UnixNano()), // election name as current nano time
				"--dial-timeout=10s",
				"--endpoints", m.EtcdClientEndpoint,
				"--total-client-connections=10",
				"--rounds=0", // runs forever
				"--req-rate", fmt.Sprintf("%v", reqRate),
			}
			stressers = append(stressers, newRunnerStresser(
				rpcpb.StresserType_ELECTION_RUNNER,
				m.EtcdClientEndpoint,
				clus.lg,
				clus.Tester.RunnerExecPath,
				args,
				clus.rateLimiter,
				reqRate,
			))

		case "WATCH_RUNNER":
			reqRate := 100
			args := []string{
				"watcher",
				"--prefix", fmt.Sprintf("%v", time.Now().UnixNano()), // prefix all keys with nano time
				"--total-keys=1",
				"--total-prefixes=1",
				"--watch-per-prefix=1",
				"--endpoints", m.EtcdClientEndpoint,
				"--rounds=0", // runs forever
				"--req-rate", fmt.Sprintf("%v", reqRate),
			}
			stressers = append(stressers, newRunnerStresser(
				rpcpb.StresserType_WATCH_RUNNER,
				m.EtcdClientEndpoint,
				clus.lg,
				clus.Tester.RunnerExecPath,
				args,
				clus.rateLimiter,
				reqRate,
			))

		case "LOCK_RACER_RUNNER":
			reqRate := 100
			args := []string{
				"lock-racer",
				fmt.Sprintf("%v", time.Now().UnixNano()), // locker name as current nano time
				"--endpoints", m.EtcdClientEndpoint,
				"--total-client-connections=10",
				"--rounds=0", // runs forever
				"--req-rate", fmt.Sprintf("%v", reqRate),
			}
			stressers = append(stressers, newRunnerStresser(
				rpcpb.StresserType_LOCK_RACER_RUNNER,
				m.EtcdClientEndpoint,
				clus.lg,
				clus.Tester.RunnerExecPath,
				args,
				clus.rateLimiter,
				reqRate,
			))

		case "LEASE_RUNNER":
			args := []string{
				"lease-renewer",
				"--ttl=30",
				"--endpoints", m.EtcdClientEndpoint,
			}
			stressers = append(stressers, newRunnerStresser(
				rpcpb.StresserType_LEASE_RUNNER,
				m.EtcdClientEndpoint,
				clus.lg,
				clus.Tester.RunnerExecPath,
				args,
				clus.rateLimiter,
				0,
			))
		}
	}

	if ksExist {
		return append(stressers, ks)
	}
	return stressers
}
