package syslog

import (
	"log"
	"net"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"testing"

	syslog "github.com/RackSec/srslog"
	"github.com/moby/moby/v2/daemon/logger"
)

func functionMatches(expectedFun any, actualFun any) bool {
	return reflect.ValueOf(expectedFun).Pointer() == reflect.ValueOf(actualFun).Pointer()
}

func TestParseLogFormat(t *testing.T) {
	formatter, framer, err := parseLogFormat("rfc5424", "udp")
	if err != nil || !functionMatches(rfc5424formatterWithAppNameAsTag, formatter) ||
		!functionMatches(syslog.DefaultFramer, framer) {
		t.Fatal("Failed to parse rfc5424 format", err, formatter, framer)
	}

	formatter, framer, err = parseLogFormat("rfc5424", "tcp+tls")
	if err != nil || !functionMatches(rfc5424formatterWithAppNameAsTag, formatter) ||
		!functionMatches(syslog.RFC5425MessageLengthFramer, framer) {
		t.Fatal("Failed to parse rfc5424 format", err, formatter, framer)
	}

	formatter, framer, err = parseLogFormat("rfc5424micro", "udp")
	if err != nil || !functionMatches(rfc5424microformatterWithAppNameAsTag, formatter) ||
		!functionMatches(syslog.DefaultFramer, framer) {
		t.Fatal("Failed to parse rfc5424 (microsecond) format", err, formatter, framer)
	}

	formatter, framer, err = parseLogFormat("rfc5424micro", "tcp+tls")
	if err != nil || !functionMatches(rfc5424microformatterWithAppNameAsTag, formatter) ||
		!functionMatches(syslog.RFC5425MessageLengthFramer, framer) {
		t.Fatal("Failed to parse rfc5424 (microsecond) format", err, formatter, framer)
	}

	formatter, framer, err = parseLogFormat("rfc3164", "")
	if err != nil || !functionMatches(syslog.RFC3164Formatter, formatter) ||
		!functionMatches(syslog.DefaultFramer, framer) {
		t.Fatal("Failed to parse rfc3164 format", err, formatter, framer)
	}

	formatter, framer, err = parseLogFormat("", "")
	if err != nil || !functionMatches(syslog.UnixFormatter, formatter) ||
		!functionMatches(syslog.DefaultFramer, framer) {
		t.Fatal("Failed to parse empty format", err, formatter, framer)
	}

	formatter, framer, err = parseLogFormat("invalid", "")
	if err == nil {
		t.Fatal("Failed to parse invalid format", err, formatter, framer)
	}
}

func TestValidateLogOptEmpty(t *testing.T) {
	emptyConfig := make(map[string]string)
	if err := ValidateLogOpt(emptyConfig); err != nil {
		t.Fatal("Failed to parse empty config", err)
	}
}

func TestValidateSyslogAddress(t *testing.T) {
	const sockPlaceholder = "/TEMPDIR/socket.sock"
	s, err := os.Create(filepath.Join(t.TempDir(), "socket.sock"))
	if err != nil {
		log.Fatal(err)
	}
	socketPath := s.Name()
	_ = s.Close()

	tests := []struct {
		address     string
		expectedErr string
		skipOn      string
	}{
		{
			address:     "this is not an uri",
			expectedErr: "unsupported scheme: ''",
		},
		{
			address:     "corrupted:42",
			expectedErr: "unsupported scheme: 'corrupted'",
		},
		{
			address: "unix://" + sockPlaceholder,
			skipOn:  "windows", // doesn't work with unix:// sockets
		},
		{
			address:     "unix:///does_not_exist",
			expectedErr: "no such file or directory",
			skipOn:      "windows", // error message differs
		},
		{
			address: "tcp://1.2.3.4",
		},
		{
			address: "udp://1.2.3.4",
		},
		{
			address:     "http://1.2.3.4",
			expectedErr: "unsupported scheme: 'http'",
		},
	}
	for _, tc := range tests {
		if tc.skipOn == runtime.GOOS {
			continue
		}
		t.Run(tc.address, func(t *testing.T) {
			address := strings.Replace(tc.address, sockPlaceholder, socketPath, 1)
			err := ValidateLogOpt(map[string]string{"syslog-address": address})
			if tc.expectedErr != "" {
				if err == nil {
					t.Fatal("expected an error, got nil")
				}
				if !strings.Contains(err.Error(), tc.expectedErr) {
					t.Fatalf("expected error to contain '%s', got: '%s'", tc.expectedErr, err)
				}
			} else if err != nil {
				t.Fatalf("unexpected error: '%s'", err)
			}
		})
	}
}

func TestParseAddressDefaultPort(t *testing.T) {
	_, address, err := parseAddress("tcp://1.2.3.4")
	if err != nil {
		t.Fatal(err)
	}

	_, port, _ := net.SplitHostPort(address)
	if port != defaultPort {
		t.Fatalf("Expected to default to port %s. It used port %s", defaultPort, port)
	}
}

func TestValidateSyslogFacility(t *testing.T) {
	err := ValidateLogOpt(map[string]string{
		"syslog-facility": "Invalid facility",
	})
	if err == nil {
		t.Fatal("Expected error if facility level is invalid")
	}
}

func TestValidateLogOptSyslogFormat(t *testing.T) {
	err := ValidateLogOpt(map[string]string{
		"syslog-format": "Invalid format",
	})
	if err == nil {
		t.Fatal("Expected error if format is invalid")
	}
}

func TestValidateLogOpt(t *testing.T) {
	err := ValidateLogOpt(map[string]string{
		"syslog-address":         "udp://1.2.3.4:1111",
		"syslog-facility":        "daemon",
		"syslog-tls-ca-cert":     "/etc/ca-certificates/custom/ca.pem",
		"syslog-tls-cert":        "/etc/ca-certificates/custom/cert.pem",
		"syslog-tls-key":         "/etc/ca-certificates/custom/key.pem",
		"syslog-tls-skip-verify": "true",
		"syslog-format":          "rfc3164",

		logger.AttrEnv:         "http://127.0.0.1",
		logger.AttrEnvRegex:    "abc",
		logger.AttrLabels:      "labelA",
		logger.AttrLabelsRegex: "def",
		logger.AttrLogTag:      "true",
	})
	if err != nil {
		t.Fatal(err)
	}

	err = ValidateLogOpt(map[string]string{
		"not-supported-option": "a",
	})
	if err == nil {
		t.Fatal("Expecting error on unsupported options")
	}
}
