package plex

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

// newTestClient returns a Client pointed at the given test server.
func newTestClient(srv *httptest.Server) *Client {
	return NewClient(srv.URL, "test-token")
}

func TestPing_OK(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("X-Plex-Token") != "test-token" {
			t.Errorf("expected token in query, got %q", r.URL.Query().Get("X-Plex-Token"))
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"MediaContainer":{"friendlyName":"My Plex"}}`))
	}))
	defer srv.Close()

	if err := newTestClient(srv).Ping(); err != nil {
		t.Fatalf("Ping() unexpected error: %v", err)
	}
}

func TestPing_Unauthorized(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
	}))
	defer srv.Close()

	err := newTestClient(srv).Ping()
	if err == nil {
		t.Fatal("Ping() expected error on 401, got nil")
	}
	if !strings.Contains(err.Error(), "token invalid") {
		t.Errorf("expected 'token invalid' in error, got %q", err.Error())
	}
}

func TestPing_ServerError(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer srv.Close()

	err := newTestClient(srv).Ping()
	if err == nil {
		t.Fatal("Ping() expected error on 500, got nil")
	}
}

func TestMusicSections_FiltersByType(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{
			"MediaContainer": {
				"Directory": [
					{"key": "1", "type": "artist", "title": "Music"},
					{"key": "2", "type": "movie",  "title": "Movies"},
					{"key": "3", "type": "artist", "title": "Jazz"}
				]
			}
		}`))
	}))
	defer srv.Close()

	sections, err := newTestClient(srv).MusicSections()
	if err != nil {
		t.Fatalf("MusicSections() error: %v", err)
	}
	if len(sections) != 2 {
		t.Fatalf("expected 2 music sections, got %d", len(sections))
	}
	if sections[0].Key != "1" || sections[0].Title != "Music" {
		t.Errorf("unexpected section[0]: %+v", sections[0])
	}
	if sections[1].Key != "3" || sections[1].Title != "Jazz" {
		t.Errorf("unexpected section[1]: %+v", sections[1])
	}
}

func TestMusicSections_Empty(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"MediaContainer":{"Directory":[{"key":"1","type":"movie","title":"Movies"}]}}`))
	}))
	defer srv.Close()

	sections, err := newTestClient(srv).MusicSections()
	if err != nil {
		t.Fatalf("MusicSections() unexpected error: %v", err)
	}
	if len(sections) != 0 {
		t.Errorf("expected 0 music sections, got %d", len(sections))
	}
}

func TestMusicSections_LibraryFilter(t *testing.T) {
	serverJSON := `{"MediaContainer":{"Directory":[
		{"key":"1","type":"artist","title":"Music"},
		{"key":"2","type":"artist","title":"Jazz"},
		{"key":"3","type":"artist","title":"Classical"}
	]}}`

	tests := []struct {
		name       string
		libraries  []string
		wantLen    int
		wantTitles []string
		wantKeys   []string
	}{
		{
			name:       "filter to subset",
			libraries:  []string{"Jazz", "Classical"},
			wantLen:    2,
			wantTitles: []string{"Jazz", "Classical"},
		},
		{
			name:      "case-insensitive match",
			libraries: []string{"MUSIC"},
			wantLen:   1,
			wantKeys:  []string{"1"},
		},
		{
			name:      "no match returns empty",
			libraries: []string{"DoesNotExist"},
			wantLen:   0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.Header().Set("Content-Type", "application/json")
				w.Write([]byte(serverJSON))
			}))
			defer srv.Close()

			sections, err := NewClient(srv.URL, "test-token", tt.libraries...).MusicSections()
			if err != nil {
				t.Fatalf("MusicSections() error: %v", err)
			}
			if len(sections) != tt.wantLen {
				t.Fatalf("got %d sections, want %d: %v", len(sections), tt.wantLen, sections)
			}
			for i, title := range tt.wantTitles {
				if sections[i].Title != title {
					t.Errorf("sections[%d].Title = %q, want %q", i, sections[i].Title, title)
				}
			}
			for i, key := range tt.wantKeys {
				if sections[i].Key != key {
					t.Errorf("sections[%d].Key = %q, want %q", i, sections[i].Key, key)
				}
			}
		})
	}
}

func TestAlbums_RequestsType9(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("type") != "9" {
			t.Errorf("expected type=9 in request, got %q", r.URL.Query().Get("type"))
		}
		if !strings.HasSuffix(r.URL.Path, "/library/sections/3/all") {
			t.Errorf("unexpected path %q", r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{
			"MediaContainer": {
				"totalSize": 2,
				"Metadata": [
					{
						"ratingKey": "456",
						"title": "Kind of Blue",
						"parentTitle": "Miles Davis",
						"year": 1959,
						"leafCount": 5
					},
					{
						"ratingKey": "457",
						"title": "Bitches Brew",
						"parentTitle": "Miles Davis",
						"year": 1970,
						"leafCount": 4
					}
				]
			}
		}`))
	}))
	defer srv.Close()

	albums, err := newTestClient(srv).Albums("3")
	if err != nil {
		t.Fatalf("Albums() error: %v", err)
	}
	if len(albums) != 2 {
		t.Fatalf("expected 2 albums, got %d", len(albums))
	}
	a := albums[0]
	if a.RatingKey != "456" || a.Title != "Kind of Blue" || a.ArtistName != "Miles Davis" || a.Year != 1959 || a.TrackCount != 5 {
		t.Errorf("unexpected album[0]: %+v", a)
	}
}

func TestAlbums_Paginates(t *testing.T) {
	callCount := 0
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		callCount++
		start := r.URL.Query().Get("X-Plex-Container-Start")
		w.Header().Set("Content-Type", "application/json")
		switch start {
		case "0", "":
			w.Write([]byte(`{"MediaContainer":{"totalSize":2,"Metadata":[{"ratingKey":"1","title":"A","parentTitle":"Art","year":2000,"leafCount":1}]}}`))
		case "300":
			// Second page — but totalSize=2 means we should never reach here with pageSize=300.
			// This tests that we stop after the first page when totalSize <= pageSize.
			t.Errorf("unexpected second page request (offset=%s)", start)
			w.Write([]byte(`{"MediaContainer":{"totalSize":2,"Metadata":[]}}`))
		}
	}))
	defer srv.Close()

	albums, err := newTestClient(srv).Albums("1")
	if err != nil {
		t.Fatalf("Albums() error: %v", err)
	}
	if len(albums) != 1 {
		t.Errorf("expected 1 album, got %d", len(albums))
	}
	if callCount != 1 {
		t.Errorf("expected 1 API call, got %d", callCount)
	}
}

func TestTracks_MapsAllFields(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.HasSuffix(r.URL.Path, "/library/metadata/456/children") {
			t.Errorf("unexpected path %q", r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{
			"MediaContainer": {
				"Metadata": [
					{
						"ratingKey": "789",
						"title": "So What",
						"grandparentTitle": "Miles Davis",
						"parentTitle": "Kind of Blue",
						"year": 1959,
						"index": 1,
						"duration": 565000,
						"Media": [{"Part": [{"key": "/library/parts/100/111/So_What.flac"}]}]
					},
					{
						"ratingKey": "790",
						"title": "Freddie Freeloader",
						"grandparentTitle": "Miles Davis",
						"parentTitle": "Kind of Blue",
						"year": 1959,
						"index": 2,
						"duration": 586000,
						"Media": [{"Part": [{"key": "/library/parts/101/222/Freddie.flac"}]}]
					}
				]
			}
		}`))
	}))
	defer srv.Close()

	tracks, err := newTestClient(srv).Tracks("456")
	if err != nil {
		t.Fatalf("Tracks() error: %v", err)
	}
	if len(tracks) != 2 {
		t.Fatalf("expected 2 tracks, got %d", len(tracks))
	}
	tr := tracks[0]
	if tr.RatingKey != "789" {
		t.Errorf("RatingKey: got %q, want %q", tr.RatingKey, "789")
	}
	if tr.Title != "So What" {
		t.Errorf("Title: got %q, want %q", tr.Title, "So What")
	}
	if tr.ArtistName != "Miles Davis" {
		t.Errorf("ArtistName: got %q, want %q", tr.ArtistName, "Miles Davis")
	}
	if tr.AlbumName != "Kind of Blue" {
		t.Errorf("AlbumName: got %q, want %q", tr.AlbumName, "Kind of Blue")
	}
	if tr.Year != 1959 {
		t.Errorf("Year: got %d, want 1959", tr.Year)
	}
	if tr.TrackNumber != 1 {
		t.Errorf("TrackNumber: got %d, want 1", tr.TrackNumber)
	}
	if tr.Duration != 565000 {
		t.Errorf("Duration: got %d, want 565000", tr.Duration)
	}
	if tr.PartKey != "/library/parts/100/111/So_What.flac" {
		t.Errorf("PartKey: got %q, want /library/parts/100/111/So_What.flac", tr.PartKey)
	}
}

func TestTracks_MissingMedia(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{
			"MediaContainer": {
				"Metadata": [
					{"ratingKey": "1", "title": "Track Without Media", "Media": []}
				]
			}
		}`))
	}))
	defer srv.Close()

	tracks, err := newTestClient(srv).Tracks("42")
	if err != nil {
		t.Fatalf("Tracks() error: %v", err)
	}
	if len(tracks) != 1 {
		t.Fatalf("expected 1 track, got %d", len(tracks))
	}
	if tracks[0].PartKey != "" {
		t.Errorf("expected empty PartKey for track with no media, got %q", tracks[0].PartKey)
	}
}

func TestStreamURL_Format(t *testing.T) {
	c := NewClient("http://192.168.1.10:32400", "mytoken")
	got := c.StreamURL("/library/parts/100/111/file.flac")
	want := "http://192.168.1.10:32400/library/parts/100/111/file.flac?X-Plex-Token=mytoken"
	if got != want {
		t.Errorf("StreamURL:\n got  %q\n want %q", got, want)
	}
}

func TestStreamURL_TokenEncoded(t *testing.T) {
	c := NewClient("http://192.168.1.10:32400", "tok en+special")
	got := c.StreamURL("/library/parts/1/2/file.mp3")
	if !strings.Contains(got, "X-Plex-Token=tok+en%2Bspecial") {
		t.Errorf("StreamURL token not URL-encoded: %q", got)
	}
}

func TestSearch_SendsCorrectParams(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("query") != "Miles Davis" {
			t.Errorf("expected query=Miles Davis, got %q", r.URL.Query().Get("query"))
		}
		if r.URL.Query().Get("type") != "10" {
			t.Errorf("expected type=10, got %q", r.URL.Query().Get("type"))
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"MediaContainer":{"Metadata":[]}}`))
	}))
	defer srv.Close()

	tracks, err := newTestClient(srv).Search("Miles Davis")
	if err != nil {
		t.Fatalf("Search() error: %v", err)
	}
	if len(tracks) != 0 {
		t.Errorf("expected 0 tracks, got %d", len(tracks))
	}
}

func TestSearch_EmptyQueryNoRequest(t *testing.T) {
	called := false
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		w.WriteHeader(http.StatusOK)
	}))
	defer srv.Close()

	tracks, err := newTestClient(srv).Search("")
	if err != nil {
		t.Fatalf("Search(\"\") unexpected error: %v", err)
	}
	if tracks != nil {
		t.Errorf("expected nil tracks for empty query, got %v", tracks)
	}
	if called {
		t.Error("Search(\"\") should not make an HTTP request")
	}
}

func TestRequestHeaders(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Accept") != "application/json" {
			t.Errorf("expected Accept: application/json, got %q", r.Header.Get("Accept"))
		}
		if r.Header.Get("X-Plex-Product") != "cliamp" {
			t.Errorf("expected X-Plex-Product: cliamp, got %q", r.Header.Get("X-Plex-Product"))
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"MediaContainer":{}}`))
	}))
	defer srv.Close()

	_ = newTestClient(srv).Ping()
}
