package migration

import (
	"fmt"
	"log"
	"time"

	"github.com/cactus/go-statsd-client/statsd"

	"code.justin.tv/chat/timing"
	"code.justin.tv/d8a/migration/comparison"
)

type CallComparer interface {
	RecordPrimary(methodName string, duration time.Duration)
	SecondaryCall(methodName string, secondaryName string, duration time.Duration, params []interface{}, retVals []interface{}, execute func() ([]interface{}, error))
}

func CreateCallComparer(statsdHostPort string, logLevel comparison.LogLevel, preprocessor comparison.ComparePreprocessor, environment string, repo string, interfaceName string) CallComparer {
	stats, err := statsd.NewBufferedClient(statsdHostPort,
		fmt.Sprintf("d8a-migration.%s.%s.%s", environment, extractRepoFromSvcname(repo), interfaceName),
		time.Second, 0)
	if err != nil {
		log.Fatalln(err)
	}

	return &directCallComparer{
		stats:        stats,
		logLevel:     logLevel,
		preprocessor: preprocessor,
	}
}

type directCallComparer struct {
	stats        statsd.Statter
	logLevel     comparison.LogLevel
	preprocessor comparison.ComparePreprocessor
}

func (self *directCallComparer) RecordPrimary(methodName string, duration time.Duration) {
	self.stats.TimingDuration(fmt.Sprintf("primary.%s.success", methodName), duration, StatsdSampleRate)
}

func (self *directCallComparer) SecondaryCall(methodName string, secondaryName string, duration time.Duration, params []interface{}, retVals []interface{}, execute func() ([]interface{}, error)) {
	timer := timing.Xact{
		Stats:            self.stats,
		StatsdSampleRate: StatsdSampleRate,
	}
	timer.AddName(fmt.Sprintf("secondary.%s.%s", secondaryName, methodName))
	timer.Start()
	defer timer.End("err")

	secondaryResult, err := execute()
	if err != nil {
		log.Println(err)
		return
	}

	timer.End("success")

	oldResults, err := comparison.ComparablesFromStruct(retVals)
	if err != nil {
		log.Println(err)
		self.logStat("error", secondaryName, methodName)
		return
	}

	newResults, err := comparison.ComparablesFromStruct(secondaryResult)
	if err != nil {
		log.Println(err)
		self.logStat("error", secondaryName, methodName)
		return
	}

	sameResults, err := comparison.DeepCompare(methodName, self.preprocessor, oldResults, newResults)
	if err != nil {
		log.Println(err)
		self.logStat("error", secondaryName, methodName)
		return
	}

	if sameResults {
		self.logStat("match", secondaryName, methodName)
	} else {
		comparison.LogMismatch(methodName, params, retVals, secondaryResult, self.logLevel)
		self.logStat("mismatch", secondaryName, methodName)
	}
}

func (self *directCallComparer) logStat(metric string, secondaryName string, methodName string) {
	self.stats.Inc(fmt.Sprintf("secondary.%s.%s.%s", secondaryName, methodName, metric), 1, StatsdSampleRate)
}
