package log

import (
	"context"
	"fmt"
	"testing"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type contextKey struct{}

var loggerKey = contextKey{}

func WithLogger(ctx context.Context, lgr SimpleLogger) context.Context {
	return context.WithValue(ctx, loggerKey, lgr)
}

func FromContext(ctx context.Context) SimpleLogger {
	if ctx == nil {
		return nil
	}
	if lgr, ok := ctx.Value(loggerKey).(SimpleLogger); ok {
		return lgr
	}
	return nil
}

type SimpleLogger interface {
	Info(format string, a ...any)
	Warn(format string, a ...any)
	Error(format string, a ...any)
	Sync()
}

type driverLogger struct {
	lr *zap.Logger
}

func NewDriverLogger(caseName string) SimpleLogger {
	return &driverLogger{
		lr: NewLogger(caseName, "info").WithOptions(zap.AddCallerSkip(1)),
	}
}

func (l *driverLogger) Info(format string, a ...any) {
	l.lr.Info(fmt.Sprintf(format, a...))
}

func (l *driverLogger) Warn(format string, a ...any) {
	l.lr.Warn(fmt.Sprintf(format, a...))
}

func (l *driverLogger) Error(format string, a ...any) {
	l.lr.Error(fmt.Sprintf(format, a...))
}

func (l *driverLogger) Sync() {
	l.lr.Sync()
}

func NewLogger(name, levelString string) *zap.Logger {
	cfg := zap.NewProductionConfig()
	level, _ := zapcore.ParseLevel(levelString)
	cfg.Level = zap.NewAtomicLevelAt(level)
	cfg.Encoding = "console" // Use console (text) format instead of JSON
	cfg.EncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
	cfg.EncoderConfig.TimeKey = "" // timestamp no need
	cfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder

	lr, _ := cfg.Build()
	return lr.Named(name)
}

type caseLogger struct {
	t        *testing.T
	caseName string
}

func NewCaseLogger(t *testing.T, caseName string) SimpleLogger {
	return &caseLogger{
		t:        t,
		caseName: caseName,
	}
}

func (l *caseLogger) log(level, format string, a ...any) {
	b := []interface{}{level, time.Now().Format("2006-01-02 15:04:05"), l.caseName}
	b = append(b, a...)
	l.t.Logf("%s %s case=%s "+format, b...)
}

func (l *caseLogger) Info(format string, a ...any) {
	l.log("INFO", format, a...)
}

func (l *caseLogger) Warn(format string, a ...any) {
	l.log("WARN", format, a...)
}

func (l *caseLogger) Error(format string, a ...any) {
	l.log("ERROR", format, a...)
}

func (l *caseLogger) Sync() {}
