package log_extractor

import (
	"github.com/pingcap-inc/alioth/pkg/util"
	logpattern_go_proto "github.com/pingcap-inc/alioth/proto"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

// LogPkgExtract designed to extract log printing level according to the log package used,
// e.g. error level using Errorf() method of the zap log package
type LogPkgExtract interface {
	Filter(pkgName, fnName, object string) (string, bool)
}

var filterHub = make(map[string]LogPkgExtract)

func RegisterLogPkgFilter(pkg string, filter LogPkgExtract) {
	if _, exist := filterHub[pkg]; exist {
		log.Fatal("log pattern filter already exists in hub", zap.String("log package", pkg))
	}
	filterHub[pkg] = filter
}

func init() {
	RegisterLogPkgFilter("log", &logPkg{})
	RegisterLogPkgFilter("zap", &zapLogPkg{})
}

// Filter used to determine whether the log pattern matched filter rule
type Filter struct {
	filterRule *logpattern_go_proto.LogPatternRule
}

func NewFilter(rule *logpattern_go_proto.LogPatternRule) *Filter {
	return &Filter{filterRule: rule}
}

// Filter used to log packaga/function name, and log format data to compute match result
func (f *Filter) Filter(pkgName, fnName, object string) (string, bool) {
	for _, filter := range filterHub {
		if level, matched := filter.Filter(pkgName, fnName, object); matched {
			if util.MatchLogPatternRule(f.filterRule, level) {
				return level, true
			}
		}
	}

	return "", false
}

type logPkg struct{}

func (l *logPkg) Filter(pkgName, fnName, object string) (string, bool) {
	level, matched := "", false

	if pkgName != "log" {
		return level, matched
	}

	if fnName == "ErrorFilterContextCanceled" || fnName == "Error" || fnName == "Errorf" {
		level, matched = "error", true
	} else if fnName == "Warnf" || fnName == "Warn" {
		level, matched = "warn", true
	} else if fnName == "Fatalf" || fnName == "Fatal" {
		level, matched = "fatal", true
	} else if fnName == "Printf" || fnName == "Print" || fnName == "Infof" || fnName == "Info" {
		level, matched = "info", true
	} else if fnName == "Debugf" || fnName == "Debug" {
		level, matched = "debug", true
	}

	return level, matched
}

type zapLogPkg struct{}

func (z *zapLogPkg) Filter(pkgName, fnName, object string) (string, bool) {
	level, matched := "", false

	if object != "*go.uber.org/zap.Logger" && object != "*go.uber.org/zap.SugaredLogger" {
		return level, matched
	}

	if fnName == "Errorf" || fnName == "Error" {
		level, matched = "error", true
	} else if fnName == "Warnf" || fnName == "Warn" {
		level, matched = "warn", true
	} else if fnName == "Fatalf" || fnName == "Fatal" {
		level, matched = "fatal", true
	} else if fnName == "Infof" || fnName == "Info" {
		level, matched = "info", true
	} else if fnName == "Debugf" || fnName == "Debug" {
		level, matched = "debug", true
	}

	return level, matched
}
