package log

import (
	"context"

	"github.com/getsentry/sentry-go"
	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/logid"
	"github.com/tidbcloud/pkgv2/zapext/zapsentry"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type (
	// Logger Key for store logger in ctx
	loggerKey struct{}
)

func GetGinLogger(ctx *gin.Context) *zap.Logger {
	return GetBaseLogger(ctx.Request.Context())
}

func GetLogger(ctx context.Context) *zap.Logger {
	logger := ctx.Value(loggerKey{})
	if logger != nil {
		return logger.(*zap.Logger)
	}
	return GetBaseLogger(WithTraceID(ctx))
}

func WithLogger(ctx context.Context, logger *zap.Logger) context.Context {
	return context.WithValue(ctx, loggerKey{}, logger)
}

func GetSugaredLogger(ctx context.Context) *zap.SugaredLogger {
	return GetBaseLogger(ctx).Sugar()
}

func WithLogID(ctx context.Context) context.Context {
	logId := logid.GetLogid(ctx)
	if logId == "" {
		logId = logid.GenLogid()
		return logid.WithLogid(ctx, logId)
	}
	return ctx
}

func GetLogID(ctx context.Context) string {
	ctx = WithTraceID(ctx)
	logId := logid.GetLogid(ctx)
	return logId
}

func WithTraceID(ctx context.Context) context.Context {
	return WithLogID(ctx)
}

func GetBaseLogger(ctx context.Context) *zap.Logger {
	newCtx := WithLogID(ctx)
	legacyLogID := logid.GetLogid(newCtx)
	return log.L().Named("metrics-integration").
		With(zap.String("legacy_log_id", legacyLogID)).
		With(tilog.TraceIDFromContext(ctx)...)
}

type InitLoggerOption struct {
	PrintLogLevel string

	EnableSentry           bool
	ReportToSentryLogLevel string
}

func InitLogger(option *InitLoggerOption) error {
	opts, err := buildLoggerOptions(option)
	if err != nil {
		return err
	}

	cfg := &log.Config{
		Level: option.PrintLogLevel,
	}
	gl, props, err := log.InitLogger(cfg, opts...)
	if err != nil {
		return err
	}
	log.ReplaceGlobals(gl, props)
	return nil
}

func buildLoggerOptions(option *InitLoggerOption) ([]zap.Option, error) {
	var sentryOpt zap.Option
	if !option.EnableSentry {
		return []zap.Option{zap.AddStacktrace(zapcore.ErrorLevel)}, nil
	}

	var sentryLogLevel zapcore.Level
	if err := sentryLogLevel.UnmarshalText([]byte(option.ReportToSentryLogLevel)); err != nil {
		return nil, bizerr.WrapError(err, "invalid sentry.LogLevel")
	}
	sentryOpt = zap.WrapCore(func(core zapcore.Core) zapcore.Core {
		return zapcore.NewTee(core, zapsentry.NewCore(sentryLogLevel, sentry.CurrentHub().Client()))
	})
	return []zap.Option{sentryOpt, zap.AddStacktrace(zapcore.ErrorLevel)}, nil
}
