package coverart

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"testing"
	"time"
)

// newDeezerTestProvider returns a fresh enabled provider configured
// against a test server URL.
func newDeezerTestProvider(t *testing.T, baseURL string) *deezerProvider {
	t.Helper()
	old := deezerBaseURL
	deezerBaseURL = baseURL
	t.Cleanup(func() { deezerBaseURL = old })

	p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
	p.enabled.Store(true)
	return p
}

func TestDeezer_ID(t *testing.T) {
	p := &deezerProvider{}
	if p.ID() != "deezer" {
		t.Errorf("ID = %q, want deezer", p.ID())
	}
	if p.RequiresAPIKey() {
		t.Error("RequiresAPIKey = true, want false")
	}
	if !p.DefaultEnabled() {
		t.Error("DefaultEnabled = false, want true")
	}
}

func TestDeezer_FetchArtistArt_Success(t *testing.T) {
	mux := http.NewServeMux()
	srv := httptest.NewServer(mux)
	defer srv.Close()

	mux.HandleFunc("/search/artist", func(w http.ResponseWriter, r *http.Request) {
		imgURL := "http://" + r.Host + "/img/cp.jpg"
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[{"name":"Caravan Palace","picture_xl":"` + imgURL + `"}]}`))
	})
	mux.HandleFunc("/img/", func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "image/jpeg")
		_, _ = w.Write([]byte("img-bytes"))
	})

	p := newDeezerTestProvider(t, srv.URL)
	thumb, fanart, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "Caravan Palace"})
	if err != nil {
		t.Fatalf("err = %v, want nil", err)
	}
	if string(thumb) != "img-bytes" {
		t.Errorf("thumb = %q, want img-bytes", thumb)
	}
	if fanart != nil {
		t.Errorf("fanart = %v, want nil (Deezer has no separate fanart)", fanart)
	}
}

func TestDeezer_FetchArtistArt_EmptyResults(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[],"total":0}`))
	}))
	defer srv.Close()

	p := newDeezerTestProvider(t, srv.URL)
	_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "Daisy Grenade"})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound", err)
	}
}

func TestDeezer_FetchArtistArt_NameMismatch(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[{"name":"Some Other Artist","picture_xl":"http://example/x.jpg"}]}`))
	}))
	defer srv.Close()

	p := newDeezerTestProvider(t, srv.URL)
	_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "Real Artist"})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound (name mismatch should not match)", err)
	}
}

func TestDeezer_FetchArtistArt_EmptyName(t *testing.T) {
	p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
	p.enabled.Store(true)
	_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound on empty Name", err)
	}
}

func TestDeezer_FetchArtistArt_Disabled(t *testing.T) {
	p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
	// enabled defaults to false
	_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "X"})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound when disabled", err)
	}
}

func TestDeezer_FetchAlbumCover_Success(t *testing.T) {
	mux := http.NewServeMux()
	srv := httptest.NewServer(mux)
	defer srv.Close()

	mux.HandleFunc("/search/album", func(w http.ResponseWriter, r *http.Request) {
		imgURL := "http://" + r.Host + "/img/r.jpg"
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[{"title":"The Record","cover_xl":"` + imgURL + `","artist":{"name":"The Band"}}]}`))
	})
	mux.HandleFunc("/img/", func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "image/jpeg")
		_, _ = w.Write([]byte("album-bytes"))
	})

	p := newDeezerTestProvider(t, srv.URL)
	body, err := p.FetchAlbumCover(context.Background(), AlbumRef{ArtistName: "The Band", AlbumTitle: "The Record"})
	if err != nil {
		t.Fatalf("err = %v, want nil", err)
	}
	if string(body) != "album-bytes" {
		t.Errorf("body = %q, want album-bytes", body)
	}
}

func TestDeezer_FetchAlbumCover_TitleMismatch(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[{"title":"Wrong Album","cover_xl":"http://example/x.jpg","artist":{"name":"The Band"}}]}`))
	}))
	defer srv.Close()

	p := newDeezerTestProvider(t, srv.URL)
	_, err := p.FetchAlbumCover(context.Background(), AlbumRef{ArtistName: "The Band", AlbumTitle: "The Record"})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("err = %v, want ErrNotFound (title mismatch)", err)
	}
}

func TestDeezer_FetchAlbumCover_MissingFields(t *testing.T) {
	p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
	p.enabled.Store(true)

	cases := []AlbumRef{
		{},
		{ArtistName: "X"}, // no title
		{AlbumTitle: "Y"}, // no artist
	}
	for _, ref := range cases {
		_, err := p.FetchAlbumCover(context.Background(), ref)
		if !errors.Is(err, ErrNotFound) {
			t.Errorf("ref=%+v err=%v, want ErrNotFound", ref, err)
		}
	}
}

func TestDeezer_RateLimit_Serializes(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[]}`))
	}))
	defer srv.Close()

	old := deezerBaseURL
	deezerBaseURL = srv.URL
	t.Cleanup(func() { deezerBaseURL = old })

	// Build a provider WITH the production MinInterval so this test can
	// verify the rate-limit actually serializes. Other tests use
	// newDeezerTestProvider which leaves MinInterval=0 for speed.
	p := &deezerProvider{client: newHTTPClient(httpClientOptions{
		Name:        "deezer",
		HTTPClient:  &http.Client{Timeout: 5 * time.Second},
		MinInterval: deezerMinPeriod,
		BaseBackoff: time.Millisecond,
	})}
	p.enabled.Store(true)

	start := time.Now()
	_, _, _ = p.FetchArtistArt(context.Background(), ArtistRef{Name: "A"})
	_, _, _ = p.FetchArtistArt(context.Background(), ArtistRef{Name: "B"})
	elapsed := time.Since(start)

	if elapsed < deezerMinPeriod {
		t.Errorf("two back-to-back calls completed in %v, want at least %v (rate-limit didn't serialize)",
			elapsed, deezerMinPeriod)
	}
}

func TestDeezer_FetchArtistArt_ServerError(t *testing.T) {
	var attempts atomic.Int32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		attempts.Add(1)
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer srv.Close()

	p := newDeezerTestProvider(t, srv.URL)
	_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "X"})
	if !errors.Is(err, ErrTransient) {
		t.Errorf("err = %v, want ErrTransient on 500", err)
	}
	// Should have retried up to deezerMaxRetries+1 times total.
	if attempts.Load() < 2 {
		t.Errorf("attempts = %d, want >= 2 (retry should have run)", attempts.Load())
	}
}

func TestDeezer_FetchArtistArt_429TriggersRetry(t *testing.T) {
	var attempts atomic.Int32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		n := attempts.Add(1)
		if n == 1 {
			http.Error(w, "rate limit", http.StatusTooManyRequests)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":[]}`)) // second attempt: empty → ErrNotFound
	}))
	defer srv.Close()

	p := newDeezerTestProvider(t, srv.URL)
	_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "X"})
	if attempts.Load() < 2 {
		t.Errorf("attempts = %d, want >= 2 (retry should have run)", attempts.Load())
	}
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("final err = %v, want ErrNotFound (empty results after retry)", err)
	}
}
