package artwork

import (
	"context"
	"crypto/md5"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Masterminds/squirrel"
	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/consts"
	"github.com/navidrome/navidrome/core"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/utils"
)

type artistReader struct {
	cacheKey
	a            *artwork
	em           core.ExternalMetadata
	artist       model.Artist
	artistFolder string
	files        string
}

func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, em core.ExternalMetadata) (*artistReader, error) {
	ar, err := artwork.ds.Artist(ctx).Get(artID.ID)
	if err != nil {
		return nil, err
	}
	als, err := artwork.ds.Album(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_artist_id": artID.ID}})
	if err != nil {
		return nil, err
	}
	a := &artistReader{
		a:      artwork,
		em:     em,
		artist: *ar,
	}
	// TODO Find a way to factor in the ExternalUpdateInfoAt in the cache key. Problem is that it can
	// change _after_ retrieving from external sources, making the key invalid
	//a.cacheKey.lastUpdate = ar.ExternalInfoUpdatedAt
	var files []string
	var paths []string
	for _, al := range als {
		files = append(files, al.ImageFiles)
		paths = append(paths, splitList(al.Paths)...)
		if a.cacheKey.lastUpdate.Before(al.UpdatedAt) {
			a.cacheKey.lastUpdate = al.UpdatedAt
		}
	}
	a.files = strings.Join(files, consts.Zwsp)
	a.artistFolder = utils.LongestCommonPrefix(paths)
	if !strings.HasSuffix(a.artistFolder, string(filepath.Separator)) {
		a.artistFolder, _ = filepath.Split(a.artistFolder)
	}
	a.cacheKey.artID = artID
	return a, nil
}

func (a *artistReader) Key() string {
	hash := md5.Sum([]byte(conf.Server.Agents + conf.Server.Spotify.ID))
	return fmt.Sprintf(
		"%s.%t.%x",
		a.cacheKey.Key(),
		conf.Server.EnableExternalServices,
		hash,
	)
}

func (a *artistReader) LastUpdated() time.Time {
	return a.lastUpdate
}

func (a *artistReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
	var ff = a.fromArtistArtPriority(ctx, conf.Server.ArtistArtPriority)
	return selectImageReader(ctx, a.artID, ff...)
}

func (a *artistReader) fromArtistArtPriority(ctx context.Context, priority string) []sourceFunc {
	var ff []sourceFunc
	for _, pattern := range strings.Split(strings.ToLower(priority), ",") {
		pattern = strings.TrimSpace(pattern)
		switch {
		case pattern == "external":
			ff = append(ff, fromArtistExternalSource(ctx, a.artist, a.em))
		case strings.HasPrefix(pattern, "album/"):
			ff = append(ff, fromExternalFile(ctx, a.files, strings.TrimPrefix(pattern, "album/")))
		default:
			ff = append(ff, fromArtistFolder(ctx, a.artistFolder, pattern))
		}
	}
	return ff
}

func fromArtistFolder(ctx context.Context, artistFolder string, pattern string) sourceFunc {
	return func() (io.ReadCloser, string, error) {
		fsys := os.DirFS(artistFolder)
		matches, err := fs.Glob(fsys, pattern)
		if err != nil {
			log.Warn(ctx, "Error matching artist image pattern", "pattern", pattern, "folder", artistFolder)
			return nil, "", err
		}
		if len(matches) == 0 {
			return nil, "", fmt.Errorf(`no matches for '%s' in '%s'`, pattern, artistFolder)
		}
		for _, m := range matches {
			filePath := filepath.Join(artistFolder, m)
			if !model.IsImageFile(m) {
				continue
			}
			f, err := os.Open(filePath)
			if err != nil {
				log.Warn(ctx, "Could not open cover art file", "file", filePath, err)
				return nil, "", err
			}
			return f, filePath, nil
		}
		return nil, "", nil
	}
}
