package services

import "testing"

func TestParseJournalLine(t *testing.T) {
	line := []byte(`{"__REALTIME_TIMESTAMP":"1750406104000000","PRIORITY":"6","MESSAGE":"Started OpenSSH server daemon.","_PID":"123"}`)
	e, ok := parseJournalLine(line)
	if !ok {
		t.Fatal("expected parse to succeed")
	}
	if e.Message != "Started OpenSSH server daemon." {
		t.Errorf("message = %q", e.Message)
	}
	if e.Priority != 6 {
		t.Errorf("priority = %d", e.Priority)
	}
	if e.Time != "2025-06-20T07:55:04Z" {
		t.Errorf("time = %q", e.Time)
	}
}

func TestParseJournalLineBinaryMessage(t *testing.T) {
	// Binary MESSAGE is encoded as a byte array; we yield an empty message, not an error.
	line := []byte(`{"__REALTIME_TIMESTAMP":"1750406104000000","PRIORITY":"3","MESSAGE":[104,105]}`)
	e, ok := parseJournalLine(line)
	if !ok || e.Message != "" || e.Priority != 3 {
		t.Errorf("got ok=%v entry=%+v", ok, e)
	}
}

func TestParseJournalLineMissingPriority(t *testing.T) {
	// PRIORITY is often absent; it must default to info (6), not emerg (0).
	line := []byte(`{"__REALTIME_TIMESTAMP":"1750406104000000","MESSAGE":"hi"}`)
	e, ok := parseJournalLine(line)
	if !ok || e.Priority != 6 {
		t.Errorf("got ok=%v priority=%d, want priority 6", ok, e.Priority)
	}
}

func TestParseJournalLineGarbage(t *testing.T) {
	if _, ok := parseJournalLine([]byte("not json")); ok {
		t.Error("garbage line should not parse")
	}
}

func TestResolveLogPath(t *testing.T) {
	allow := map[string][]string{
		"nginx.service": {"/var/log/nginx/access.log", "/var/log/nginx/error.log"},
	}

	// Allowlisted path resolves whether the caller uses the bare or .service
	// form, regardless of which form the config key used.
	for _, unit := range []string{"nginx.service", "nginx"} {
		if p, err := resolveLogPath(allow, unit, "/var/log/nginx/error.log"); err != nil || p != "/var/log/nginx/error.log" {
			t.Errorf("allowlisted path for %q: got %q, %v", unit, p, err)
		}
	}

	// Everything else is rejected: empty path, non-listed path (traversal),
	// listed path but wrong unit, and unit with no allowlist at all.
	bad := []struct{ unit, path string }{
		{"nginx.service", ""},
		{"nginx.service", "/etc/shadow"},
		{"nginx.service", "/var/log/nginx/access.log/../../../etc/shadow"},
		{"sshd.service", "/var/log/nginx/error.log"},
		{"unknown.service", "/var/log/nginx/error.log"},
	}
	for _, b := range bad {
		if _, err := resolveLogPath(allow, b.unit, b.path); err == nil {
			t.Errorf("resolveLogPath(%q, %q) = nil error, want rejection", b.unit, b.path)
		}
	}
}

func TestJournalUnit(t *testing.T) {
	cases := map[string]string{
		"docker.service": "docker",
		"docker":         "docker",
		"sshd.service":   "sshd",
		"foo.socket":     "foo.socket", // only .service is stripped
	}
	for in, want := range cases {
		if got := journalUnit(in); got != want {
			t.Errorf("journalUnit(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestClampLines(t *testing.T) {
	cases := map[int]int{0: defaultLogLines, -5: defaultLogLines, 50: 50, 999999: maxLogLines}
	for in, want := range cases {
		if got := clampLines(in); got != want {
			t.Errorf("clampLines(%d) = %d, want %d", in, got, want)
		}
	}
}
