package coverart

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync/atomic"
	"time"
)

// deezerBaseURL is a var (not const) so tests can override it to point
// at a test server.
var deezerBaseURL = "https://api.deezer.com"

const (
	// deezerMinPeriod throttles requests to 5/s — well under Deezer's
	// documented 50/5s per-IP limit. Conservative because the limit is
	// shared across the entire host, not per-API-key.
	deezerMinPeriod = 200 * time.Millisecond

	// deezerMaxRetries: number of retry attempts on 429 / 5xx.
	deezerMaxRetries = 3
)

// deezerProvider implements Provider, AlbumCoverProvider, and
// ArtistArtProvider against Deezer's public read API. No API key
// required. Safe for concurrent use; the underlying httpClient
// serialises calls so the rate limit applies uniformly.
type deezerProvider struct {
	enabled atomic.Bool
	client  *httpClient
}

func init() {
	Register(&deezerProvider{
		client: newHTTPClient(httpClientOptions{
			Name:        "deezer",
			HTTPClient:  &http.Client{Timeout: 30 * time.Second},
			MinInterval: deezerMinPeriod,
			MaxRetries:  deezerMaxRetries,
		}),
	})
}

func (p *deezerProvider) ID() string           { return "deezer" }
func (p *deezerProvider) DisplayName() string  { return "Deezer" }
func (p *deezerProvider) RequiresAPIKey() bool { return false }
func (p *deezerProvider) DefaultEnabled() bool { return true }

func (p *deezerProvider) Configure(s ProviderSettings) error {
	p.enabled.Store(s.Enabled)
	return nil
}

// FetchArtistArt searches Deezer's artist catalog by name.
//
// Returns ErrNotFound when:
//   - provider disabled
//   - ref.Name empty (Deezer is name-based, can't act on MBID alone)
//   - search returns no results
//   - top result's name doesn't case-insensitive-match ref.Name
//     (false-positive guard)
//   - response has no usable picture URL
//
// Deezer returns a single artist image (no separate fanart concept) —
// we use it as the thumb and leave fanart nil. The enricher's writer
// handles thumb-only correctly (writes thumb.jpg, skips fanart.jpg).
func (p *deezerProvider) FetchArtistArt(ctx context.Context, ref ArtistRef) (thumb, fanart []byte, err error) {
	if !p.enabled.Load() {
		return nil, nil, ErrNotFound
	}
	if ref.Name == "" {
		return nil, nil, ErrNotFound
	}

	q := url.Values{"q": {ref.Name}, "limit": {"1"}}
	var resp struct {
		Data []struct {
			Name       string `json:"name"`
			PictureXL  string `json:"picture_xl"`
			PictureBig string `json:"picture_big"`
		} `json:"data"`
	}
	if err := p.client.getJSON(ctx, deezerBaseURL+"/search/artist?"+q.Encode(), &resp); err != nil {
		return nil, nil, err
	}
	if len(resp.Data) == 0 {
		return nil, nil, ErrNotFound
	}
	hit := resp.Data[0]
	if !strings.EqualFold(hit.Name, ref.Name) {
		return nil, nil, ErrNotFound
	}
	imgURL := hit.PictureXL
	if imgURL == "" {
		imgURL = hit.PictureBig
	}
	if imgURL == "" {
		return nil, nil, ErrNotFound
	}
	img, ferr := p.client.getImage(ctx, imgURL)
	if ferr != nil {
		return nil, nil, ferr
	}
	return img, nil, nil
}

// FetchAlbumCover searches Deezer's album catalog using the advanced
// query syntax `artist:"<name>" album:"<title>"` to keep the result
// tight. Returns ErrNotFound on the same set of conditions as
// FetchArtistArt; additionally requires both ArtistName and AlbumTitle
// to be non-empty (Deezer search is unreliable with one missing).
func (p *deezerProvider) FetchAlbumCover(ctx context.Context, ref AlbumRef) ([]byte, error) {
	if !p.enabled.Load() {
		return nil, ErrNotFound
	}
	if ref.ArtistName == "" || ref.AlbumTitle == "" {
		return nil, ErrNotFound
	}

	qStr := fmt.Sprintf(`artist:"%s" album:"%s"`, ref.ArtistName, ref.AlbumTitle)
	q := url.Values{"q": {qStr}, "limit": {"1"}}
	var resp struct {
		Data []struct {
			Title    string `json:"title"`
			CoverXL  string `json:"cover_xl"`
			CoverBig string `json:"cover_big"`
			Artist   struct {
				Name string `json:"name"`
			} `json:"artist"`
		} `json:"data"`
	}
	if err := p.client.getJSON(ctx, deezerBaseURL+"/search/album?"+q.Encode(), &resp); err != nil {
		return nil, err
	}
	if len(resp.Data) == 0 {
		return nil, ErrNotFound
	}
	hit := resp.Data[0]
	if !strings.EqualFold(hit.Title, ref.AlbumTitle) || !strings.EqualFold(hit.Artist.Name, ref.ArtistName) {
		return nil, ErrNotFound
	}
	imgURL := hit.CoverXL
	if imgURL == "" {
		imgURL = hit.CoverBig
	}
	if imgURL == "" {
		return nil, ErrNotFound
	}
	return p.client.getImage(ctx, imgURL)
}

// Compile-time check: the new provider implements both interfaces.
var (
	_ AlbumCoverProvider = (*deezerProvider)(nil)
	_ ArtistArtProvider  = (*deezerProvider)(nil)
)
