package api

import (
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	"github.com/ponzischeme89/memby/server/internal/store"
)

func TestRequestLogLevel(t *testing.T) {
	tests := []struct {
		path   string
		status int
		want   slog.Level
	}{
		{"/healthz", http.StatusOK, slog.LevelDebug},
		{"/v1/status", http.StatusOK, slog.LevelDebug},
		{"/v1/images/123/primary", http.StatusOK, slog.LevelDebug},
		{"/admin/api/status", http.StatusOK, slog.LevelDebug},
		{"/admin/api/status", http.StatusUnauthorized, slog.LevelWarn},
		{"/v1/home", http.StatusOK, slog.LevelInfo},
		{"/v1/images/123/primary", http.StatusNotFound, slog.LevelWarn},
		{"/v1/home", http.StatusServiceUnavailable, slog.LevelError},
	}
	for _, test := range tests {
		if got := requestLogLevel(test.path, test.status); got != test.want {
			t.Errorf("requestLogLevel(%q, %d) = %v, want %v", test.path, test.status, got, test.want)
		}
	}
}

func TestComponentNamesThePartOfTheAppARouteBelongsTo(t *testing.T) {
	tests := map[string]string{
		"/healthz":                      "health",
		"/v1/status":                    "status",
		"/v1/home":                      "home",
		"/v1/preferences":               "settings",
		"/v1/screensaver":               "screensaver",
		"/v1/auth/login":                "auth",
		"/v1/auth/devices/tv-1":         "devices",
		"/v1/search":                    "search",
		"/v1/genres/Comedy/items":       "search",
		"/v1/library/items":             "search",
		"/v1/items/42":                  "details",
		"/v1/items/42/related":          "details",
		"/v1/items/42/playback":         "playback",
		"/v1/items/42/next":             "playback",
		"/v1/items/42/subtitles/search": "playback",
		"/v1/items/42/trickplay":        "playback",
		"/v1/items/42/trickplay/12.jpg": "playback",
		"/v1/playback/started":          "playback",
		"/v1/images/42/primary":         "artwork",
		"/v1/recommendations":           "recommendations",
		"/v1/for-you":                   "recommendations",
		"/v1/my-shows":                  "my-shows",
		"/admin/api/status":             "admin",
		"/hooks/radarr":                 "webhooks",
		"/install":                      "installer",
		"/updates/latest.apk":           "updates",
		"/something-nobody-has-written": "api",
	}
	for path, want := range tests {
		if got := componentFor(path); got != want {
			t.Errorf("componentFor(%q) = %q, want %q", path, got, want)
		}
	}
}

func TestIdentifyNamesTheViewerAndTelevision(t *testing.T) {
	request := httptest.NewRequest(http.MethodGet, "/v1/home", nil)
	request.Header.Set("X-Memby-Version", "0.1.60")
	request, identity := withRequestIdentity(request)

	identify(request.Context(), store.Session{
		Username: "matt", DeviceName: "Living room", ClientProtocol: "1",
	})

	if identity.component != "home" || identity.user != "matt" || identity.device != "Living room" {
		t.Fatalf("identity was not filled in: %+v", identity)
	}
	// The header the TV actually sent must survive a session that has no version yet.
	if identity.client != "0.1.60" || identity.protocol != "1" {
		t.Fatalf("client identity was lost: %+v", identity)
	}
}

// A device with no name at all must still be identifiable, or an old APK's traffic
// becomes anonymous exactly when someone is trying to work out which television it is.
func TestIdentifyFallsBackToTheDeviceID(t *testing.T) {
	request, identity := withRequestIdentity(httptest.NewRequest(http.MethodGet, "/v1/home", nil))
	identify(request.Context(), store.Session{DeviceID: "tv-1"})
	if identity.device != "tv-1" {
		t.Fatalf("device = %q", identity.device)
	}
}

func TestPlaybackTitlesNamesReportsAndStaysBounded(t *testing.T) {
	var titles playbackTitles
	titles.remember("42", "Dune")
	if got := titles.name("42"); got != "Dune" {
		t.Fatalf("remembered title = %q", got)
	}
	// An unknown item reports as itself rather than as an empty field.
	if got := titles.name("99"); got != "99" {
		t.Fatalf("unknown title = %q", got)
	}
	for i := range rememberedTitles + 10 {
		titles.remember(strconv.Itoa(1000+i), "Title")
	}
	if len(titles.titles) > rememberedTitles {
		t.Fatalf("title memory grew to %d entries", len(titles.titles))
	}
}

func TestWatchedPercentReadsAsProgress(t *testing.T) {
	if got := watchedPercent(30_000, 60_000); got != "50%" {
		t.Errorf("half watched = %q", got)
	}
	// Emby's reported position can overshoot a runtime by a frame or two.
	if got := watchedPercent(61_000, 60_000); got != "100%" {
		t.Errorf("overshoot = %q", got)
	}
	if got := watchedPercent(30_000, 0); got != "unknown" {
		t.Errorf("unknown runtime = %q", got)
	}
}

func TestClientLogValueIsNeverBlank(t *testing.T) {
	if got := clientLogValue(""); got != "unknown" {
		t.Fatalf("blank identity logged as %q", got)
	}
	if got := clientLogValue("0.1.60"); got != "0.1.60" {
		t.Fatalf("reported identity changed to %q", got)
	}
}
