/*
 * Copyright 2024 CloudWeGo 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 retry

import (
	"context"
	"errors"
	"math"
	"sync/atomic"
	"testing"
	"time"

	"github.com/bytedance/sonic"

	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/kerrors"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
)

// test new MixedPolicy
func TestMixedRetryPolicy(t *testing.T) {
	mp := NewMixedPolicy(100)

	// case 1
	mp.WithMaxRetryTimes(3)
	jsonRet, err := sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var mp2 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &mp2)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.Equals(&mp2))
	test.Assert(t, mp2.FailurePolicy.StopPolicy.MaxRetryTimes == 3)

	// case 2
	mp.WithMaxRetryTimes(2)
	mp.WithRetrySameNode()
	mp.WithFixedBackOff(10)
	jsonRet, err = sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var mp3 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &mp3)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.Equals(&mp3), mp3)
	test.Assert(t, mp3.FailurePolicy.StopPolicy.MaxRetryTimes == 2)
	test.Assert(t, mp3.FailurePolicy.BackOffPolicy.BackOffType == FixedBackOffType)

	// case 3
	mp.WithRandomBackOff(10, 20)
	jsonRet, err = sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var mp4 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &mp4)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.Equals(&mp4), mp4)
	test.Assert(t, mp4.FailurePolicy.StopPolicy.MaxRetryTimes == 2)
	test.Assert(t, mp4.FailurePolicy.BackOffPolicy.BackOffType == RandomBackOffType)

	// case 4
	mp.WithRetryBreaker(0.2)
	mp.WithDDLStop()
	mp.WithMaxDurationMS(100)
	jsonRet, err = sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var mp5 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &mp5)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.Equals(&mp5), mp5)
	test.Assert(t, mp5.FailurePolicy.StopPolicy.DDLStop)
	test.Assert(t, mp5.FailurePolicy.StopPolicy.MaxDurationMS == 100)
	test.Assert(t, mp5.FailurePolicy.StopPolicy.CBPolicy.ErrorRate == 0.2)

	// case 5
	mp = &MixedPolicy{
		RetryDelayMS: 20,
		FailurePolicy: FailurePolicy{
			StopPolicy: StopPolicy{
				MaxRetryTimes:    2,
				DisableChainStop: false,
				CBPolicy: CBPolicy{
					ErrorRate: defaultCBErrRate,
				},
			},
			Extra: "{}",
		},
	}
	jsonRet, err = sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var mp6 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &mp6)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp6.BackOffPolicy == nil)
	test.Assert(t, mp.Equals(&mp6), mp6)
	test.Assert(t, mp6.FailurePolicy.StopPolicy.MaxRetryTimes == 2)
	test.Assert(t, !mp6.FailurePolicy.StopPolicy.DisableChainStop)
	test.Assert(t, mp6.FailurePolicy.StopPolicy.CBPolicy.ErrorRate == defaultCBErrRate)

	// case 6
	mp.DisableChainRetryStop()
	jsonRet, err = sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var mp7 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &mp7)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp7.BackOffPolicy == nil)
	test.Assert(t, mp.Equals(&mp7), mp7)
	test.Assert(t, mp7.FailurePolicy.StopPolicy.DisableChainStop)
	test.Assert(t, mp.String() == "{RetryDelayMS:20 StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true "+
		"DDLStop:false CBPolicy:{ErrorRate:0.1}} BackOffPolicy:<nil> RetrySameNode:false ShouldResultRetry:{ErrorRetry:false, RespRetry:false}}", mp)

	// case 7
	mp.WithSpecifiedResultRetry(&ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool {
		return false
	}})
	test.Assert(t, mp.String() == "{RetryDelayMS:20 StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true "+
		"DDLStop:false CBPolicy:{ErrorRate:0.1}} BackOffPolicy:<nil> RetrySameNode:false ShouldResultRetry:{ErrorRetry:true, RespRetry:false}}", mp)
	jsonRet, err = sonic.MarshalString(mp)
	test.Assert(t, err == nil, err)
	var fp9 MixedPolicy
	err = sonic.UnmarshalString(jsonRet, &fp9)
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.Equals(&fp9), fp9)
	test.Assert(t, fp9.ShouldResultRetry == nil)
}

func TestNewMixedPolicy(t *testing.T) {
	mp0 := NewMixedPolicy(100)
	mp1 := NewMixedPolicy(100)
	test.Assert(t, mp0.Equals(mp1))

	mp1 = NewMixedPolicy(20)
	test.Assert(t, !mp0.Equals(mp1))

	mp1 = mp0.DeepCopy()
	test.Assert(t, mp0.Equals(mp1))

	mp1 = mp0.DeepCopy()
	mp1.WithMaxRetryTimes(3)
	test.Assert(t, !mp0.Equals(mp1))

	mp1 = mp0.DeepCopy()
	mp1.WithFixedBackOff(10)
	test.Assert(t, !mp0.Equals(mp1))

	mp1 = mp0.DeepCopy()
	mp1.WithRetryBreaker(0.2)
	test.Assert(t, !mp0.Equals(mp1))

	mp1 = nil
	test.Assert(t, !mp0.Equals(mp1))

	mp0 = nil
	test.Assert(t, mp0.Equals(mp1))

	test.Panic(t, func() { NewMixedPolicy(0) })
}

// test MixedRetry call
func TestMixedRetry(t *testing.T) {
	ri := genRPCInfo()
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	var transErrCode int32 = 1001
	shouldResultRetry := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool {
		if ri.To().Method() == method {
			if te, ok := err.(*remote.TransError); ok && te.TypeID() == transErrCode {
				return true
			}
		}
		return false
	}}

	// case1: specified method retry with error
	t.Run("specified method retry with error", func(t *testing.T) {
		rc := NewRetryContainer()
		err := rc.Init(map[string]Policy{Wildcard: BuildMixedPolicy(NewMixedPolicy(100))}, shouldResultRetry)
		test.Assert(t, err == nil, err)
		ri1, ok, err := rc.WithRetryIfNeeded(ctx, nil, retryWithTransError(0, transErrCode), ri, nil, nil)
		test.Assert(t, err == nil, err)
		test.Assert(t, !ok)
		v, ok := ri1.To().Tag(remoteTagKey)
		test.Assert(t, ok)
		test.Assert(t, v == remoteTagValue)

		ri2, ok, err := rc.WithRetryIfNeeded(ctx, nil, retryWithTransError(0, 1002), ri, nil, nil)
		test.Assert(t, err != nil)
		test.Assert(t, !ok)
		_, ok = ri2.To().Tag(remoteTagKey)
		test.Assert(t, !ok)
	})

	// case2: specified method retry with error, but method not match
	t.Run("specified method retry with error, but method not match", func(t *testing.T) {
		rr := &ShouldResultRetry{ErrorRetryWithCtx: func(ctx context.Context, err error, ri rpcinfo.RPCInfo) bool {
			if ri.To().Method() != method {
				if te, ok := err.(*remote.TransError); ok && te.TypeID() == 1000 {
					return true
				}
			}
			return false
		}}
		rc := NewRetryContainer()
		err := rc.Init(map[string]Policy{method: BuildMixedPolicy(NewMixedPolicy(100))}, rr)
		test.Assert(t, err == nil, err)
		ri = genRPCInfo()
		ri, ok, err := rc.WithRetryIfNeeded(ctx, &Policy{}, retryWithTransError(0, transErrCode), ri, nil, nil)
		test.Assert(t, err != nil)
		test.Assert(t, err.(*remote.TransError).TypeID() == transErrCode)
		test.Assert(t, !ok)
		_, ok = ri.To().Tag(remoteTagKey)
		test.Assert(t, !ok)
	})

	// case3: all error retry
	t.Run("all error retry", func(t *testing.T) {
		rc := NewRetryContainer()
		p := BuildMixedPolicy(NewMixedPolicyWithResultRetry(100, AllErrorRetry()))
		ri = genRPCInfo()
		ri, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTransError(0, transErrCode), ri, nil, nil)
		test.Assert(t, err == nil, err)
		test.Assert(t, !ok)
		v, ok := ri.To().Tag(remoteTagKey)
		test.Assert(t, ok)
		test.Assert(t, v == remoteTagValue)
	})

	// case5: all error retry and trigger circuit breaker
	t.Run("case5", func(t *testing.T) {
		rc := NewRetryContainerWithPercentageLimit()
		p := BuildMixedPolicy(NewMixedPolicyWithResultRetry(100, AllErrorRetry()))
		ri = genRPCInfo()

		for i := 0; i < 10; i++ {
			// failure rate is 50%
			_, _, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTransError(0, transErrCode), ri, nil, nil)
			if i < 5 {
				// i < 5, total request < 10
				test.Assert(t, err == nil, err, i)
			} else {
				// the minSimple is 10, total request > 10 then trigger cb
				test.Assert(t, err != nil)
			}
		}
	})
}

// Assuming the first request returns at 300ms, the second request costs 150ms
// Configuration: Timeout=200ms、MaxRetryTimes=2 BackupDelay=100ms
// - Mixed Retry: Success, cost 250ms
// - Failure Retry: Success, cost 350ms
// - Backup Retry: Failure, cost 200ms
func TestMockCase1WithDiffRetry(t *testing.T) {
	ri := genRPCInfo(func() interface{} {
		return &mockResult{}
	})
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	retryWithTimeout := func(ri rpcinfo.RPCInfo, callTimes int32) RPCCallFunc {
		return func(ctx context.Context, r Retryer, request, response interface{}) (rpcinfo.RPCInfo, error) {
			ct := atomic.AddInt32(&callTimes, 1)
			resp := response.(*mockResult)
			resp.setCallTimes(ct)
			if ct == 1 {
				// first call retry timeout
				time.Sleep(200 * time.Millisecond)
				return ri, kerrors.ErrRPCTimeout.WithCause(errors.New("mock"))
			} else {
				time.Sleep(150 * time.Millisecond)
				return ri, nil
			}
		}
	}
	// mixed retry will success, latency is lowest
	t.Run("mixed retry", func(t *testing.T) {
		rc := NewRetryContainer()
		mp := NewMixedPolicy(100)
		mp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildMixedPolicy(mp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start) // 100+150 = 250
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getCallTimes() == 2, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-250.0) < 50.0, cost.Milliseconds())
	})

	// failure retry will success, but latency is more than mixed retry
	t.Run("failure retry", func(t *testing.T) {
		rc := NewRetryContainer()
		fp := NewFailurePolicy()
		fp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildFailurePolicy(fp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.callTimes == 2, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-350.0) < 50.0, cost.Milliseconds())
	})

	// backup request will failure
	t.Run("backup request", func(t *testing.T) {
		rc := NewRetryContainer()
		bp := NewBackupPolicy(100)
		bp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildBackupRequest(bp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err != nil, err)
		test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout))
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-200.0) < 50.0, cost.Milliseconds())
	})
}

// Assuming the first request returns at 300ms, the second request cost 150ms
// Configuration: Timeout=300ms、MaxRetryTimes=2 BackupDelay=100ms
// - Mixed Retry: Success, cost 250ms (>timeout, same with Backup Retry)
// - Failure Retry: Success, cost 350ms
// - Backup Retry: Failure, cost 200ms (same with Mixed Retry)
func TestMockCase2WithDiffRetry(t *testing.T) {
	ri := genRPCInfo(func() interface{} {
		return &mockResult{}
	})
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	retryWithTimeout := func(ri rpcinfo.RPCInfo, callTimes int32) RPCCallFunc {
		return func(ctx context.Context, r Retryer, request, response interface{}) (rpcinfo.RPCInfo, error) {
			ct := atomic.AddInt32(&callTimes, 1)
			resp := response.(*mockResult)
			resp.setCallTimes(ct)
			if ct == 1 {
				// first call retry timeout
				time.Sleep(300 * time.Millisecond)
				return ri, kerrors.ErrRPCTimeout.WithCause(errors.New("mock"))
			} else {
				time.Sleep(150 * time.Millisecond)
				return ri, nil
			}
		}
	}
	// mixed retry will success, latency is lowest
	t.Run("mixed retry", func(t *testing.T) {
		rc := NewRetryContainer()
		mp := NewMixedPolicy(100)
		mp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildMixedPolicy(mp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start) // 100+150 = 250
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getCallTimes() == 2, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-250.0) < 50.0, cost.Milliseconds())
	})

	// failure retry will success, but latency is more than mixed retry
	t.Run("failure retry", func(t *testing.T) {
		rc := NewRetryContainer()
		fp := NewFailurePolicy()
		fp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildFailurePolicy(fp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getCallTimes() == 2, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-450.0) < 50.0, cost.Milliseconds())
	})

	// backup request will failure
	t.Run("backup request", func(t *testing.T) {
		rc := NewRetryContainer()
		bp := NewBackupPolicy(100)
		bp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildBackupRequest(bp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getCallTimes() == 2, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-250.0) < 50.0, cost.Milliseconds())
	})
}

// Assuming all request timeout
// Configuration: Timeout=100ms、MaxRetryTimes=2 BackupDelay=100ms
// - Mixed Retry: Failure, cost 200ms
// - Failure Retry: Failure, cost 300ms
// - Backup Retry: Failure, cost 100ms (max cost is timeout)
func TestMockCase3WithDiffRetry(t *testing.T) {
	ri := genRPCInfo(func() interface{} {
		return &mockResult{}
	})
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	retryWithTimeout := func(ri rpcinfo.RPCInfo, callTimes int32) RPCCallFunc {
		return func(ctx context.Context, r Retryer, request, response interface{}) (rpcinfo.RPCInfo, error) {
			ct := atomic.AddInt32(&callTimes, 1)
			resp := response.(*mockResult)
			resp.setCallTimes(ct)
			time.Sleep(100 * time.Millisecond)
			return ri, kerrors.ErrRPCTimeout.WithCause(errors.New("mock"))
		}
	}
	// mixed retry will success, cost is least
	t.Run("mixed retry", func(t *testing.T) {
		rc := NewRetryContainer()
		mp := NewMixedPolicy(100)
		mp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildMixedPolicy(mp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start) // 100+(100,100) = 200
		test.Assert(t, err != nil, err)
		test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout))
		test.Assert(t, ret.getCallTimes() == 2 || ret.getCallTimes() == 3, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-200.0) < 50.0, cost.Milliseconds())
	})

	// failure retry will success, but cost is more than mixed retry
	t.Run("failure retry", func(t *testing.T) {
		rc := NewRetryContainer()
		fp := NewFailurePolicy()
		fp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildFailurePolicy(fp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err != nil, err)
		test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout))
		test.Assert(t, ret.getCallTimes() == 3, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-300.0) < 50.0, cost.Milliseconds())
	})

	// backup request will failure
	t.Run("backup request", func(t *testing.T) {
		rc := NewRetryContainer()
		bp := NewBackupPolicy(100)
		bp.WithMaxRetryTimes(2) // max call times is 3
		p := BuildBackupRequest(bp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithTimeout(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err != nil, err)
		test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout))
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-100.0) < 50.0, cost.Milliseconds())
	})
}

// Assuming BizStatus=11111/11112 needs to be retried,
//
//	the first reply is BizStatus=11111, it costs 250ms,
//	the second reply is BizStatus=11112, it costs 250ms,
//	the third reply is BizStatus=0, it costs 250ms,
//
// Configuration: MaxRetryTimes=3 BackupDelay=100ms
// - Mixed Retry: Success, cost 450ms, two backup retry, and one failure retry
// - Failure Retry: Success, cost 750ms
// - Backup Retry: Biz Error, cost 250ms
func TestMockCase4WithDiffRetry(t *testing.T) {
	bizStatusCode0, bizStatusCode1, bizStatusCode2 := 0, 11111, 11112
	ri := genRPCInfo(func() interface{} {
		return &mockResult{}
	})
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	retryWithResp := func(ri rpcinfo.RPCInfo, callTimes int32) RPCCallFunc {
		return func(ctx context.Context, r Retryer, request, response interface{}) (rpcinfo.RPCInfo, error) {
			ct := atomic.AddInt32(&callTimes, 1)
			resp := response.(*mockResult)
			resp.setCallTimes(ct)
			time.Sleep(250 * time.Millisecond)
			switch ct {
			case 1:
				resp.setResult(mockResp{code: bizStatusCode1})
				return ri, nil
			case 2:
				resp.setResult(mockResp{code: bizStatusCode2})
				return ri, nil
			case 3:
				resp.setResult(mockResp{code: bizStatusCode0})
				return ri, nil
			}
			return ri, errors.New("mock error")
		}
	}
	resultRetry := &ShouldResultRetry{RespRetryWithCtx: func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool {
		bizCode := resp.(*mockResult).getResult().(mockResp).code
		if bizCode == bizStatusCode1 || bizCode == bizStatusCode2 {
			return true
		}
		return false
	}}
	// mixed retry will success, cost is least
	t.Run("mixed retry", func(t *testing.T) {
		rc := NewRetryContainer()
		mp := NewMixedPolicy(100)
		mp.WithMaxRetryTimes(3) // max call times is 4
		mp.WithSpecifiedResultRetry(resultRetry)
		p := BuildMixedPolicy(mp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithResp(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getCallTimes() == 3, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-450.0) < 50.0, cost.Milliseconds())
	})

	// failure retry will success, but cost is more than mixed retry
	t.Run("failure retry", func(t *testing.T) {
		rc := NewRetryContainer()
		fp := NewFailurePolicy()
		fp.WithMaxRetryTimes(3) // max call times is 4
		fp.WithSpecifiedResultRetry(resultRetry)
		p := BuildFailurePolicy(fp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithResp(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getCallTimes() == 3, ret.callTimes)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-750.0) < 50.0, cost.Milliseconds())
	})

	// backup request will failure
	t.Run("backup request", func(t *testing.T) {
		rc := NewRetryContainer()
		bp := NewBackupPolicy(100)
		bp.WithMaxRetryTimes(2) // backup max retry times is 2
		p := BuildBackupRequest(bp)
		ri = genRPCInfo(func() interface{} {
			return &mockResult{}
		})
		ret := &mockResult{}
		start := time.Now()
		_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithResp(ri, 0), ri, nil, ret)
		cost := time.Since(start)
		test.Assert(t, err == nil, err)
		test.Assert(t, ret.getResult().(mockResp).code == bizStatusCode1)
		test.Assert(t, !ok)
		test.Assert(t, math.Abs(float64(cost.Milliseconds())-250.0) < 50.0, cost.Milliseconds())
	})
}

func BenchmarkMixedRetry(b *testing.B) {
	bizStatusCode0, bizStatusCode1, bizStatusCode2 := 0, 11111, 11112
	ri := genRPCInfo(func() interface{} { return &mockResult{} })
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	retryWithResp := func(ri rpcinfo.RPCInfo, callTimes int32, resp *mockResult) RPCCallFunc {
		return func(ctx context.Context, r Retryer, request, response interface{}) (rpcinfo.RPCInfo, error) {
			ct := atomic.AddInt32(&callTimes, 1)
			resp.setCallTimes(ct)
			switch ct {
			case 1:
				resp.setResult(mockResp{code: bizStatusCode1})
				return ri, nil
			case 2:
				resp.setResult(mockResp{code: bizStatusCode2})
				return ri, nil
			case 3:
				resp.setResult(mockResp{code: bizStatusCode0})
				return ri, nil
			}
			return ri, errors.New("mock error")
		}
	}
	resultRetry := &ShouldResultRetry{RespRetryWithCtx: func(ctx context.Context, resp interface{}, ri rpcinfo.RPCInfo) bool {
		bizCode := resp.(*mockResult).getResult().(mockResp).code
		if bizCode == bizStatusCode1 || bizCode == bizStatusCode2 {
			return true
		}
		return false
	}}
	rc := NewRetryContainer()
	mp := NewMixedPolicy(100)
	mp.WithMaxRetryTimes(3) // max call times is 4
	mp.WithSpecifiedResultRetry(resultRetry)

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			p := BuildMixedPolicy(mp)
			ri = genRPCInfo(func() interface{} { return &mockResult{} })
			ret := &mockResult{}
			_, ok, err := rc.WithRetryIfNeeded(ctx, &p, retryWithResp(ri, 0, ret), ri, nil, ret)
			test.Assert(b, err == nil, err)
			test.Assert(b, !ok)
		}
	})
}

func TestNewRetryer4MixedRetry(t *testing.T) {
	t.Run("normal mixed retry - success", func(t *testing.T) {
		jsonRet := `{"enable":true,"type":2,
"mixed_policy":{"retry_delay_ms":10,"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":"{\"not_retry_for_timeout\":false,\"rpc_retry_code\":{\"all_error_code\":false,\"error_codes\":[103,1204]},\"biz_retry_code\":{\"all_error_code\":false,\"error_codes\":[]}}","extra_struct":{"not_retry_for_timeout":false,"rpc_retry_code":{"all_error_code":false,"error_codes":[103,1204]},"biz_retry_code":{"all_error_code":false,"error_codes":[]}}}}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err == nil, err)
		test.Assert(t, r.(*mixedRetryer).enable)
	})

	t.Run("mixed retry disable - success but disable", func(t *testing.T) {
		jsonRet := `{"enable":false,"type":2,
"mixed_policy":{"retry_delay_ms":10,"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":"{\"not_retry_for_timeout\":false,\"rpc_retry_code\":{\"all_error_code\":false,\"error_codes\":[103,1204]},\"biz_retry_code\":{\"all_error_code\":false,\"error_codes\":[]}}","extra_struct":{"not_retry_for_timeout":false,"rpc_retry_code":{"all_error_code":false,"error_codes":[103,1204]},"biz_retry_code":{"all_error_code":false,"error_codes":[]}}}}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err == nil, err)
		test.Assert(t, !r.(*mixedRetryer).enable)
	})

	t.Run("type is mixedRetry but policy is empty - failed", func(t *testing.T) {
		jsonRet := `{"enable":true,"type":2, "mixed_policy":{}}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err != nil, err)
		test.Assert(t, r == nil)
	})

	t.Run("type is mixedRetry but no policy - failed", func(t *testing.T) {
		jsonRet := `{"enable":true,"type":2}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err != nil, err)
		test.Assert(t, r == nil)
	})

	t.Run("type is backupRequest but policy is mixedRetry - failed", func(t *testing.T) {
		jsonRet := `{"enable":true,"type":1,
"mixed_policy":{"retry_delay_ms":10,"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":"{\"not_retry_for_timeout\":false,\"rpc_retry_code\":{\"all_error_code\":false,\"error_codes\":[103,1204]},\"biz_retry_code\":{\"all_error_code\":false,\"error_codes\":[]}}","extra_struct":{"not_retry_for_timeout":false,"rpc_retry_code":{"all_error_code":false,"error_codes":[103,1204]},"biz_retry_code":{"all_error_code":false,"error_codes":[]}}}}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err != nil, err)
		test.Assert(t, r == nil)
	})

	t.Run("type is mixedRetry but policy is failure retry - failed", func(t *testing.T) {
		jsonRet := `{"enable":true,"type":2,
"failure_policy":{"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":"{\"not_retry_for_timeout\":false,\"rpc_retry_code\":{\"all_error_code\":false,\"error_codes\":[103,1204]},\"biz_retry_code\":{\"all_error_code\":false,\"error_codes\":[]}}","extra_struct":{"not_retry_for_timeout":false,"rpc_retry_code":{"all_error_code":false,"error_codes":[103,1204]},"biz_retry_code":{"all_error_code":false,"error_codes":[]}}}}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err != nil, err)
		test.Assert(t, r == nil)
	})

	t.Run("type is mixedRetry but has multi-policies - success", func(t *testing.T) {
		jsonRet := `{"enable":true,"type":2,
"mixed_policy":{"retry_delay_ms":10,"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":"{\"not_retry_for_timeout\":false,\"rpc_retry_code\":{\"all_error_code\":false,\"error_codes\":[103,1204]},\"biz_retry_code\":{\"all_error_code\":false,\"error_codes\":[]}}","extra_struct":{"not_retry_for_timeout":false,"rpc_retry_code":{"all_error_code":false,"error_codes":[103,1204]},"biz_retry_code":{"all_error_code":false,"error_codes":[]}}},
"failure_policy":{"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false,"extra":"{\"not_retry_for_timeout\":false,\"rpc_retry_code\":{\"all_error_code\":false,\"error_codes\":[103,1204]},\"biz_retry_code\":{\"all_error_code\":false,\"error_codes\":[]}}","extra_struct":{"not_retry_for_timeout":false,"rpc_retry_code":{"all_error_code":false,"error_codes":[103,1204]},"biz_retry_code":{"all_error_code":false,"error_codes":[]}}}
}`
		var p Policy
		err := sonici.UnmarshalFromString(jsonRet, &p)
		test.Assert(t, err == nil, err)

		r, err := NewRetryer(p, nil, nil)
		test.Assert(t, err == nil, err)
		test.Assert(t, r.(*mixedRetryer).enable)
	})
}
