package logger

import (
	"context"
	"io"
	"log/slog"
	"os"
	"slices"
	"time"

	"go.unistack.org/micro/v3/meter"
)

// Option func signature
type Option func(*Options)

// Options holds logger options
type Options struct {
	// TimeKey is the key used for the time of the log call
	TimeKey string
	// LevelKey is the key used for the level of the log call
	LevelKey string
	// ErroreKey is the key used for the error of the log call
	ErrorKey string
	// MessageKey is the key used for the message of the log call
	MessageKey string
	// SourceKey is the key used for the source file and line of the log call
	SourceKey string
	// StacktraceKey is the key used for the stacktrace
	StacktraceKey string
	// Name holds the logger name
	Name string

	// Out holds the output writer
	Out io.Writer
	// Context holds exernal options
	Context context.Context
	// Meter used to count logs for specific level
	Meter meter.Meter
	// TimeFunc used to obtain current time
	TimeFunc func() time.Time

	// Fields holds additional metadata
	Fields []interface{}
	// ContextAttrFuncs contains funcs that executed before log func on context
	ContextAttrFuncs []ContextAttrFunc

	// callerSkipCount number of frmaes to skip
	CallerSkipCount int
	// The logging level the logger should log
	Level Level
	// AddSource enabled writing source file and position in log
	AddSource bool
	// AddStacktrace controls writing of stacktaces on error
	AddStacktrace bool
	// DedupKeys deduplicate keys in log output
	DedupKeys bool
}

// NewOptions creates new options struct
func NewOptions(opts ...Option) Options {
	options := Options{
		Level:            DefaultLevel,
		Fields:           make([]interface{}, 0, 6),
		Out:              os.Stderr,
		Context:          context.Background(),
		ContextAttrFuncs: DefaultContextAttrFuncs,
		AddSource:        true,
		TimeFunc:         time.Now,
		Meter:            meter.DefaultMeter,
	}

	WithMicroKeys()(&options)

	for _, o := range opts {
		o(&options)
	}

	return options
}

// WithContextAttrFuncs appends default funcs for the context attrs filler
func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
	return func(o *Options) {
		o.ContextAttrFuncs = append(o.ContextAttrFuncs, fncs...)
	}
}

// WithDedupKeys dont log duplicate keys
func WithDedupKeys(b bool) Option {
	return func(o *Options) {
		o.DedupKeys = b
	}
}

// WithAddFields add fields for the logger
func WithAddFields(fields ...interface{}) Option {
	return func(o *Options) {
		if o.DedupKeys {
			for i := 0; i < len(o.Fields); i += 2 {
				for j := 0; j < len(fields); j += 2 {
					iv, iok := o.Fields[i].(string)
					jv, jok := fields[j].(string)
					if iok && jok && iv == jv {
						fields = slices.Delete(fields, j, j+2)
					}
				}
			}
			if len(fields) > 0 {
				o.Fields = append(o.Fields, fields...)
			}
		} else {
			o.Fields = append(o.Fields, fields...)
		}
	}
}

// WithFields set default fields for the logger
func WithFields(fields ...interface{}) Option {
	return func(o *Options) {
		o.Fields = fields
	}
}

// WithLevel set default level for the logger
func WithLevel(level Level) Option {
	return func(o *Options) {
		o.Level = level
	}
}

// WithOutput set default output writer for the logger
func WithOutput(out io.Writer) Option {
	return func(o *Options) {
		o.Out = out
	}
}

// WithAddStacktrace controls writing stacktrace on error
func WithAddStacktrace(v bool) Option {
	return func(o *Options) {
		o.AddStacktrace = v
	}
}

// WithAddSource controls writing source file and pos in log
func WithAddSource(v bool) Option {
	return func(o *Options) {
		o.AddSource = v
	}
}

// WithContext set context
func WithContext(ctx context.Context) Option {
	return func(o *Options) {
		o.Context = ctx
	}
}

// WithName sets the name
func WithName(n string) Option {
	return func(o *Options) {
		o.Name = n
	}
}

// WithMeter sets the meter
func WithMeter(m meter.Meter) Option {
	return func(o *Options) {
		o.Meter = m
	}
}

// WithTimeFunc sets the func to obtain current time
func WithTimeFunc(fn func() time.Time) Option {
	return func(o *Options) {
		o.TimeFunc = fn
	}
}

func WithZapKeys() Option {
	return func(o *Options) {
		o.TimeKey = "@timestamp"
		o.LevelKey = slog.LevelKey
		o.MessageKey = slog.MessageKey
		o.SourceKey = "caller"
		o.StacktraceKey = "stacktrace"
		o.ErrorKey = "error"
	}
}

func WithZerologKeys() Option {
	return func(o *Options) {
		o.TimeKey = slog.TimeKey
		o.LevelKey = slog.LevelKey
		o.MessageKey = "message"
		o.SourceKey = "caller"
		o.StacktraceKey = "stacktrace"
		o.ErrorKey = "error"
	}
}

func WithSlogKeys() Option {
	return func(o *Options) {
		o.TimeKey = slog.TimeKey
		o.LevelKey = slog.LevelKey
		o.MessageKey = slog.MessageKey
		o.SourceKey = slog.SourceKey
		o.StacktraceKey = "stacktrace"
		o.ErrorKey = "error"
	}
}

func WithMicroKeys() Option {
	return func(o *Options) {
		o.TimeKey = "timestamp"
		o.LevelKey = slog.LevelKey
		o.MessageKey = slog.MessageKey
		o.SourceKey = "caller"
		o.StacktraceKey = "stacktrace"
		o.ErrorKey = "error"
	}
}

// WithAddCallerSkipCount add skip count for copy logger
func WithAddCallerSkipCount(n int) Option {
	return func(o *Options) {
		if n > 0 {
			o.CallerSkipCount += n
		}
	}
}