package logz

import (
	"fmt"
	"io"
	"log/slog"
	"path/filepath"
	"sync"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/fieldz"
	"k8s.io/apimachinery/pkg/util/sets"
)

type LogValuerFunc func() slog.Value

func (f LogValuerFunc) LogValue() slog.Value {
	return f()
}

func LazyValue(key string, val func() slog.Value) slog.Attr {
	return slog.Any(key, LogValuerFunc(val))
}

func Strings(key string, val []string) slog.Attr {
	return LazyValue(key, func() slog.Value {
		// don't expand val into a vararg. Use as a single argument to ensure spaces are added between elements.
		return slog.StringValue(fmt.Sprint(val))
	})
}

func StringSet(key string, val sets.Set[string]) slog.Attr {
	return LazyValue(key, func() slog.Value {
		// don't expand val into a vararg. Use as a single argument to ensure spaces are added between elements.
		return slog.StringValue(fmt.Sprint(val.UnsortedList()))
	})
}

func TrimSourceFilePath(attr slog.Attr) slog.Attr {
	switch attr.Key {
	case slog.SourceKey:
		s, ok := attr.Value.Any().(*slog.Source)
		if ok {
			s.File = filepath.Base(s.File)
		}
		return attr
	default:
		return attr
	}
}

// LockedWriter serializes Write calls.
type LockedWriter struct {
	io.Writer
	mu sync.Mutex
}

func (w *LockedWriter) Write(data []byte) (int, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.Writer.Write(data)
}

func ToSlogAttr(field fieldz.Field) slog.Attr {
	return slog.Any(field.Key, field.Value)
}

// ToSlogAttrs converts fieldz.Field to slog.Attr
// This function is useful to easily log all fieldz.Field.
// The fieldz.AgentKeyFieldName is ignored because
// our loggers are already equipped with agent ids and we
// do not want to duplicate the key in the log message.
// Returns []any and not []slog.Attr so that the result can be used as a vararg to the logger call.
func ToSlogAttrs(fields []fieldz.Field, extraAttrs ...slog.Attr) []any {
	slogAttrs := make([]any, 0, len(fields)+len(extraAttrs))
	for _, field := range fields {
		if field.Key == fieldz.AgentKeyFieldName {
			continue
		}
		if field.IsEmpty() {
			continue
		}
		slogAttrs = append(slogAttrs, ToSlogAttr(field))
	}
	for _, attr := range extraAttrs {
		slogAttrs = append(slogAttrs, attr)
	}
	return slogAttrs
}
