package tags

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
)

// Artist-level tag fetching for out-of-library Discover candidates (#2376).
// Reuses the mbEntityServer / newMBProvider / newLastfmProvider helpers from
// the per-provider test files.

func TestMusicBrainzFetchArtistTags_UsesFullWeightNotTheFallbackDiscount(t *testing.T) {
	// THE distinction worth a test. artistTagWeightFactor (0.6) exists because
	// FetchTrackTags uses artist tags as a *proxy* for a track's tags. Here the
	// artist IS the subject, so weights must land unscaled — otherwise these
	// are not comparable with track_tags, which is the exact comparison slice 6
	// is built on.
	srv := mbEntityServer(``, `{"tags":[{"count":4,"name":"shoegaze"},{"count":2,"name":"dream pop"}]}`)
	defer srv.Close()
	old := mbBaseURL
	mbBaseURL = srv.URL
	defer func() { mbBaseURL = old }()

	tags, err := newMBProvider(true).FetchArtistTags(context.Background(),
		ArtistRef{MBID: "art-1", Name: "Some Band"})
	if err != nil {
		t.Fatalf("fetch: %v", err)
	}
	m := tagsByName(tags)
	if got := m["shoegaze"]; got != 1.0 {
		t.Errorf("shoegaze = %v, want 1.0 — not discounted by artistTagWeightFactor", got)
	}
	if got := m["dream pop"]; got != 0.5 {
		t.Errorf("dream pop = %v, want 0.5", got)
	}
}

func TestMusicBrainzFetchArtistTags_GatedOff(t *testing.T) {
	if _, err := newMBProvider(false).FetchArtistTags(context.Background(),
		ArtistRef{MBID: "art-1"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("disabled: err = %v, want ErrNotFound", err)
	}
	// No MBID → nothing MusicBrainz can look up. A name-based guess could
	// silently attach the wrong artist's tags, so it deliberately doesn't try.
	if _, err := newMBProvider(true).FetchArtistTags(context.Background(),
		ArtistRef{Name: "Some Band"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("no MBID: err = %v, want ErrNotFound", err)
	}
}

// fetchEntityTags reports an existing-but-untagged entity as (empty, nil) so
// FetchTrackTags can fall through to the artist level. FetchArtistTags has no
// next level, so it must convert that to the terminal ErrNotFound — otherwise
// the enricher would read "no error" as success and settle the candidate as
// enriched with zero tags.
func TestMusicBrainzFetchArtistTags_UntaggedArtistIsNotFound(t *testing.T) {
	srv := mbEntityServer(``, `{"tags":[]}`)
	defer srv.Close()
	old := mbBaseURL
	mbBaseURL = srv.URL
	defer func() { mbBaseURL = old }()

	_, err := newMBProvider(true).FetchArtistTags(context.Background(), ArtistRef{MBID: "art-1"})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound", err)
	}
}

func TestLastfmFetchArtistTags_CallsArtistGetTopTags(t *testing.T) {
	var gotMethod, gotArtist, gotMBID string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotMethod = r.URL.Query().Get("method")
		gotArtist = r.URL.Query().Get("artist")
		gotMBID = r.URL.Query().Get("mbid")
		_, _ = w.Write([]byte(`{"toptags":{"tag":[{"name":"post-punk","count":100},{"name":"moody","count":40}]}}`))
	}))
	defer srv.Close()
	old := lastfmBaseURL
	lastfmBaseURL = srv.URL + "/"
	defer func() { lastfmBaseURL = old }()

	p := &lastfmProvider{client: newHTTPClient(httpClientOptions{Name: "lastfm", MaxRetries: 1})}
	_ = p.Configure(ProviderSettings{Enabled: true, APIKey: "k"})

	tags, err := p.FetchArtistTags(context.Background(),
		ArtistRef{MBID: "art-1", Name: "Some Band"})
	if err != nil {
		t.Fatalf("fetch: %v", err)
	}
	if gotMethod != "artist.gettoptags" {
		t.Errorf("method = %q, want artist.gettoptags", gotMethod)
	}
	if gotArtist != "Some Band" {
		t.Errorf("artist = %q, want Some Band", gotArtist)
	}
	// MBID is sent as a disambiguating hint when we have one.
	if gotMBID != "art-1" {
		t.Errorf("mbid = %q, want art-1", gotMBID)
	}
	m := tagsByName(tags)
	if m["post-punk"] != 1.0 {
		t.Errorf("post-punk = %v, want 1.0", m["post-punk"])
	}
	if m["moody"] != 0.4 {
		t.Errorf("moody = %v, want 0.4", m["moody"])
	}
}

func TestLastfmFetchArtistTags_GatedOff(t *testing.T) {
	unkeyed := &lastfmProvider{client: newHTTPClient(httpClientOptions{Name: "lastfm", MaxRetries: 1})}
	_ = unkeyed.Configure(ProviderSettings{Enabled: true})
	if _, err := unkeyed.FetchArtistTags(context.Background(),
		ArtistRef{Name: "A"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("unkeyed: err = %v, want ErrNotFound", err)
	}

	keyed := &lastfmProvider{client: newHTTPClient(httpClientOptions{Name: "lastfm", MaxRetries: 1})}
	_ = keyed.Configure(ProviderSettings{Enabled: false, APIKey: "k"})
	if _, err := keyed.FetchArtistTags(context.Background(),
		ArtistRef{Name: "A"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("disabled: err = %v, want ErrNotFound", err)
	}

	// Name-based provider with no name → nothing to query. This is the case
	// that matters in practice: a candidate whose name coalesced to '' still
	// has an MBID, so MusicBrainz can serve it while Last.fm cannot.
	enabled := &lastfmProvider{client: newHTTPClient(httpClientOptions{Name: "lastfm", MaxRetries: 1})}
	_ = enabled.Configure(ProviderSettings{Enabled: true, APIKey: "k"})
	if _, err := enabled.FetchArtistTags(context.Background(),
		ArtistRef{MBID: "art-1"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("no name: err = %v, want ErrNotFound", err)
	}
}

func TestLastfmFetchArtistTags_TransientErrorCodeRetries(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(`{"error":29}`)) // rate limited
	}))
	defer srv.Close()
	old := lastfmBaseURL
	lastfmBaseURL = srv.URL + "/"
	defer func() { lastfmBaseURL = old }()

	p := &lastfmProvider{client: newHTTPClient(httpClientOptions{Name: "lastfm", MaxRetries: 1})}
	_ = p.Configure(ProviderSettings{Enabled: true, APIKey: "k"})
	_, err := p.FetchArtistTags(context.Background(), ArtistRef{Name: "A"})
	// Must be ErrTransient, not ErrNotFound: the enricher settles a candidate
	// to 'none' on ErrNotFound, which would permanently write off an artist we
	// were merely throttled on.
	if !errors.Is(err, ErrTransient) {
		t.Errorf("err = %v, want ErrTransient", err)
	}
}

func TestLastfmFetchArtistTags_UnknownArtistIsNotFound(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(`{"error":6}`)) // "not found" — terminal
	}))
	defer srv.Close()
	old := lastfmBaseURL
	lastfmBaseURL = srv.URL + "/"
	defer func() { lastfmBaseURL = old }()

	p := &lastfmProvider{client: newHTTPClient(httpClientOptions{Name: "lastfm", MaxRetries: 1})}
	_ = p.Configure(ProviderSettings{Enabled: true, APIKey: "k"})
	if _, err := p.FetchArtistTags(context.Background(),
		ArtistRef{Name: "Nobody"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound", err)
	}
}
