package matcher

import (
	"fmt"
	"reflect"
	"time"

	"github.com/onsi/gomega/format"
	"github.com/onsi/gomega/types"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

// Toleration is a mechanism that allows a false bool to be "tolerated"
// for some time, during which the falsy value will be rectified as a
// true.
//
// currently, this mechanism only being used in TolerateByDuration and
// PromQLExpr.
type Toleration struct {
	Duration         time.Duration
	FirstFailureTime *time.Time
}

func (t *Toleration) Tolerate(matched bool) bool {
	if t.Duration != 0 {
		now := time.Now()
		if matched {
			t.FirstFailureTime = nil
		} else {
			if t.FirstFailureTime == nil { // and is the first time failed
				t.FirstFailureTime = &now
				matched = true
			} else {
				failedFor := now.Sub(*t.FirstFailureTime)
				matched = failedFor <= t.Duration
			}
		}
	}

	return matched
}

type TolerateByDurationMatcher struct {
	Delegate   types.GomegaMatcher
	Toleration Toleration
	Results    []tolerateByDurationResult
}

type tolerateByDurationResult struct {
	result         bool
	tolerated      bool
	failureMessage string
	time           time.Time
}

// TolerateByDuration tolerates the immediate failure of the matcher (here we call it inner assertion
// or delegation), as long as it has not consecutively failed longer than the maximum toleration time
// specified in parameter duration.
//
// Note that TolerateByDuration could only be invoked by gomega.Should.
func TolerateByDuration(matcher types.GomegaMatcher, duration time.Duration) *TolerateByDurationMatcher {
	return &TolerateByDurationMatcher{
		Delegate:   matcher,
		Toleration: Toleration{Duration: duration},
	}
}

func (t *TolerateByDurationMatcher) Match(actual interface{}) (bool, error) {
	matched, err := t.Delegate.Match(actual)
	tolerated := matched
	if err != nil {
		return false, err
	}

	var failureMessage string
	if !matched {
		preamble := "TolerateByDuration inner assertion failed"
		tolerated = t.Toleration.Tolerate(matched)
		if tolerated {
			preamble += ", but tolerated."
		}
		failureMessage = t.Delegate.FailureMessage(actual)
		log.Debug(preamble,
			zap.String("delegate", reflect.TypeOf(t.Delegate).Elem().String()),
			zap.String("delegateFailureMessage", failureMessage),
			zap.Timep("firstNotMatchedTime", t.Toleration.FirstFailureTime))
	}

	t.Results = append(t.Results, tolerateByDurationResult{
		result:         matched,
		tolerated:      tolerated,
		failureMessage: failureMessage,
		time:           time.Now(),
	})

	return tolerated, nil
}

// FailureMessage checkout comments of DiagnoseInfo for an example of failure message.
func (t *TolerateByDurationMatcher) FailureMessage(actual interface{}) string {
	return format.Message(t.Delegate.FailureMessage(actual),
		fmt.Sprintf("to failed consecutively no longer than %s.\n\n%s", t.Toleration.Duration.String(), t.DiagnoseInfo()))
}

// NegatedFailureMessage checkout comments of DiagnoseInfo for an example of failure message.
func (t *TolerateByDurationMatcher) NegatedFailureMessage(actual interface{}) string {
	return format.Message(t.Delegate.FailureMessage(actual),
		fmt.Sprintf("to failed consecutively shorter than %s.\n\n%s", t.Toleration.Duration.String(), t.DiagnoseInfo()))
}

// DiagnoseInfo the log now runs at Debug level rather than Info level,
// and we add a separate Diagnose Info section to make the test
// significantly more concise and easier to read:
//
// Expected
//    <string>: Expected
//        <bool>: false
//    to be true
// to failed consecutively no longer than 500ms.
//
// ======== Diagnose Info ========
//  type of inner assertion: matchers.BeTrueMatcher
//  firstFailureTime: 2022-01-17 21:31:02.339797 +0800 CST m=+0.608250507
//  Inner assertion log:
//    0. [2022/01/17 21:31:02.339 +08:00] FAILED (tolerated):
//    . . . . Expected
//    . . . .     <bool>: false
//    . . . . to be true
//    1. [2022/01/17 21:31:02.539 +08:00] PASS   (tolerated)
//    2. [2022/01/17 21:31:02.539 +08:00] FAILED (tolerated):
//    . . . . Expected
//    . . . .     <bool>: false
//    . . . . to be true
//    3. [2022/01/17 21:31:02.740 +08:00] FAILED (tolerated):
//    . . . . Expected
//    . . . .     <bool>: false
//    . . . . to be true
//    4. [2022/01/17 21:31:02.940 +08:00] FAILED:
//    . . . . Expected
//    . . . .     <bool>: false
//    . . . . to be true
func (t *TolerateByDurationMatcher) DiagnoseInfo() string {
	return fmt.Sprintf("======== Diagnose Info ========\n  type of inner assertion: %s\n  firstFailureTime: %s\n  Inner assertion log: \n%s",
		reflect.TypeOf(t.Delegate).Elem().String(),
		t.Toleration.FirstFailureTime,
		format.IndentString(t.innerAssertionLog(), 1))
}

func (t *TolerateByDurationMatcher) innerAssertionLog() string {
	str := ""
	for i, value := range t.Results {
		result := "FAILED"
		if value.result {
			result = "PASS"
		}
		tolerate := ""
		if value.tolerated {
			tolerate = " (tolerated)"
		}

		failureMessage := ""
		oldIndent := format.Indent
		format.Indent = ". . "
		if !value.result {
			failureMessage = fmt.Sprintf(":\n%s", format.IndentString(value.failureMessage, 2))
		}
		format.Indent = oldIndent

		str += fmt.Sprintf("%d. [%s] %-6s%s%s\n", i, FormatTime(value.time), result, tolerate, failureMessage)
	}
	return str
}

func (t *TolerateByDurationMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
	if oracle, ok := t.Delegate.(types.OracleMatcher); ok {
		return oracle.MatchMayChangeInTheFuture(actual)
	}
	return true
}
