package deezer

import (
	"context"
	"errors"
	"net/http"
	"strings"

	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/consts"
	"github.com/navidrome/navidrome/core/agents"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/utils/cache"
	"github.com/navidrome/navidrome/utils/slice"
)

const deezerAgentName = "deezer"
const deezerApiPictureXlSize = 1000
const deezerApiPictureBigSize = 500
const deezerApiPictureMediumSize = 250
const deezerApiPictureSmallSize = 56
const deezerArtistSearchLimit = 50

type deezerAgent struct {
	dataStore model.DataStore
	client    *client
}

func deezerConstructor(dataStore model.DataStore) agents.Interface {
	agent := &deezerAgent{dataStore: dataStore}
	httpClient := &http.Client{
		Timeout: consts.DefaultHttpClientTimeOut,
	}
	cachedHttpClient := cache.NewHTTPClient(httpClient, consts.DefaultHttpClientTimeOut)
	agent.client = newClient(cachedHttpClient, conf.Server.Deezer.Language)
	return agent
}

func (s *deezerAgent) AgentName() string {
	return deezerAgentName
}

func (s *deezerAgent) GetArtistImages(ctx context.Context, _, name, _ string) ([]agents.ExternalImage, error) {
	artist, err := s.searchArtist(ctx, name)
	if err != nil {
		if errors.Is(err, agents.ErrNotFound) {
			log.Warn(ctx, "Artist not found in deezer", "artist", name)
		} else {
			log.Error(ctx, "Error calling deezer", "artist", name, err)
		}
		return nil, err
	}

	var res []agents.ExternalImage
	possibleImages := []struct {
		URL  string
		Size int
	}{
		{artist.PictureXl, deezerApiPictureXlSize},
		{artist.PictureBig, deezerApiPictureBigSize},
		{artist.PictureMedium, deezerApiPictureMediumSize},
		{artist.PictureSmall, deezerApiPictureSmallSize},
	}
	for _, imgData := range possibleImages {
		if imgData.URL != "" {
			res = append(res, agents.ExternalImage{
				URL:  imgData.URL,
				Size: imgData.Size,
			})
		}
	}
	return res, nil
}

func (s *deezerAgent) searchArtist(ctx context.Context, name string) (*Artist, error) {
	artists, err := s.client.searchArtists(ctx, name, deezerArtistSearchLimit)
	if errors.Is(err, ErrNotFound) || len(artists) == 0 {
		return nil, agents.ErrNotFound
	}
	if err != nil {
		return nil, err
	}

	// If the first one has the same name, that's the one
	if !strings.EqualFold(artists[0].Name, name) {
		return nil, agents.ErrNotFound
	}
	return &artists[0], err
}

func (s *deezerAgent) GetSimilarArtists(ctx context.Context, _, name, _ string, limit int) ([]agents.Artist, error) {
	artist, err := s.searchArtist(ctx, name)
	if err != nil {
		return nil, err
	}

	related, err := s.client.getRelatedArtists(ctx, artist.ID)
	if err != nil {
		return nil, err
	}

	res := slice.Map(related, func(r Artist) agents.Artist {
		return agents.Artist{
			Name: r.Name,
		}
	})
	if len(res) > limit {
		res = res[:limit]
	}
	return res, nil
}

func (s *deezerAgent) GetArtistTopSongs(ctx context.Context, _, artistName, _ string, count int) ([]agents.Song, error) {
	artist, err := s.searchArtist(ctx, artistName)
	if err != nil {
		return nil, err
	}

	tracks, err := s.client.getTopTracks(ctx, artist.ID, count)
	if err != nil {
		return nil, err
	}

	res := slice.Map(tracks, func(r Track) agents.Song {
		return agents.Song{
			Name: r.Title,
		}
	})
	return res, nil
}

func (s *deezerAgent) GetArtistBiography(ctx context.Context, _, name, _ string) (string, error) {
	artist, err := s.searchArtist(ctx, name)
	if err != nil {
		return "", err
	}

	return s.client.getArtistBio(ctx, artist.ID)
}

func init() {
	conf.AddHook(func() {
		if conf.Server.Deezer.Enabled {
			agents.Register(deezerAgentName, deezerConstructor)
		}
	})
}
