//go:build windows

package agdcslog

import (
	"context"
	"strings"

	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/svc/eventlog"
)

// Event IDs are the application-specific identifiers for the events.  They
// provide a unique identification of a particular event.
//
// See
// https://learn.microsoft.com/en-us/windows/win32/eventlog/event-identifiers
// and also
// https://github.com/kardianos/service/blob/master/service_windows.go.
const (
	infoEventID    = 1
	warningEventID = 2
	errorEventID   = 3
	debugEventID   = 4
)

// eventlogWriter abstracts the Windows event log.
type eventlogWriter interface {
	// Info logs at the info level.  The same method is also used for debug
	// messages with a different event ID.
	Info(id uint32, msg string) (err error)

	// Warning logs a message at the warning level.
	Warning(id uint32, msg string) (err error)

	// Error logs a message at the error level.
	Error(id uint32, msg string) (err error)

	// Close closes the system log writer.
	Close() (err error)
}

// systemLogger is the implementation of the [SystemLogger] interface for
// Windows.
type systemLogger struct {
	writer eventlogWriter
}

// newSystemLogger returns a Windows-specific system logger.
//
// Note that the eventlog src is the same as the service name.  Otherwise, we
// will get "the description for event id cannot be found" warning in every log
// record.
func newSystemLogger(_ context.Context, src string) (l SystemLogger, err error) {
	const events = eventlog.Info | eventlog.Warning | eventlog.Error

	// Continue if we receive "registry key already exists" or if we get
	// ERROR_ACCESS_DENIED so that we can log without administrative permissions
	// for pre-existing eventlog sources.
	err = eventlog.InstallAsEventCreate(src, events)
	if err != nil {
		if !strings.Contains(err.Error(), "exists") && err != windows.ERROR_ACCESS_DENIED {
			// Don't wrap the error since it's informative enough as is.
			return nil, err
		}
	}

	writer, err := eventlog.Open(src)
	if err != nil {
		// Don't wrap the error since it's informative enough as is.
		return nil, err
	}

	return &systemLogger{
		writer: writer,
	}, nil
}

// type check
var _ SystemLogger = (*systemLogger)(nil)

// Debug implements the [SystemLogger] interface for *systemLogger.
func (l *systemLogger) Debug(msg string) (err error) {
	// Event Log doesn't have Debug log level, use Info.
	return l.writer.Info(debugEventID, msg)
}

// Info implements the [SystemLogger] interface for *systemLogger.
func (l *systemLogger) Info(msg string) (err error) {
	return l.writer.Info(infoEventID, msg)
}

// Warning implements the [SystemLogger] interface for *systemLogger.
func (l *systemLogger) Warning(msg string) (err error) {
	return l.writer.Warning(warningEventID, msg)
}

// Error implements the [SystemLogger] interface for *systemLogger.
func (l *systemLogger) Error(msg string) (err error) {
	return l.writer.Error(errorEventID, msg)
}

// Close implements the [SystemLogger] interface for *systemLogger.
func (l *systemLogger) Close() (err error) {
	return l.writer.Close()
}
