package plugins

import (
	"context"
	"time"

	"github.com/navidrome/navidrome/core/scrobbler"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/request"
	"github.com/navidrome/navidrome/plugins/api"
	"github.com/tetratelabs/wazero"
)

func newWasmScrobblerPlugin(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
	loader, err := api.NewScrobblerPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
	if err != nil {
		log.Error("Error creating scrobbler service plugin", "plugin", pluginID, "path", wasmPath, err)
		return nil
	}
	return &wasmScrobblerPlugin{
		wasmBasePlugin: newWasmBasePlugin[api.Scrobbler, *api.ScrobblerPlugin](
			wasmPath,
			pluginID,
			CapabilityScrobbler,
			m.metrics,
			loader,
			func(ctx context.Context, l *api.ScrobblerPlugin, path string) (api.Scrobbler, error) {
				return l.Load(ctx, path)
			},
		),
	}
}

type wasmScrobblerPlugin struct {
	*wasmBasePlugin[api.Scrobbler, *api.ScrobblerPlugin]
}

func (w *wasmScrobblerPlugin) IsAuthorized(ctx context.Context, userId string) bool {
	username, _ := request.UsernameFrom(ctx)
	if username == "" {
		u, ok := request.UserFrom(ctx)
		if ok {
			username = u.UserName
		}
	}

	result, err := callMethod(ctx, w, "IsAuthorized", func(inst api.Scrobbler) (bool, error) {
		resp, err := inst.IsAuthorized(ctx, &api.ScrobblerIsAuthorizedRequest{
			UserId:   userId,
			Username: username,
		})
		if err != nil {
			return false, err
		}
		if resp.Error != "" {
			return false, nil
		}
		return resp.Authorized, nil
	})
	return err == nil && result
}

func (w *wasmScrobblerPlugin) NowPlaying(ctx context.Context, userId string, track *model.MediaFile, position int) error {
	username, _ := request.UsernameFrom(ctx)
	if username == "" {
		u, ok := request.UserFrom(ctx)
		if ok {
			username = u.UserName
		}
	}

	artists := make([]*api.Artist, 0, len(track.Participants[model.RoleArtist]))
	for _, a := range track.Participants[model.RoleArtist] {
		artists = append(artists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
	}
	albumArtists := make([]*api.Artist, 0, len(track.Participants[model.RoleAlbumArtist]))
	for _, a := range track.Participants[model.RoleAlbumArtist] {
		albumArtists = append(albumArtists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
	}
	trackInfo := &api.TrackInfo{
		Id:           track.ID,
		Mbid:         track.MbzRecordingID,
		Name:         track.Title,
		Album:        track.Album,
		AlbumMbid:    track.MbzAlbumID,
		Artists:      artists,
		AlbumArtists: albumArtists,
		Length:       int32(track.Duration),
		Position:     int32(position),
	}
	_, err := callMethod(ctx, w, "NowPlaying", func(inst api.Scrobbler) (struct{}, error) {
		resp, err := inst.NowPlaying(ctx, &api.ScrobblerNowPlayingRequest{
			UserId:    userId,
			Username:  username,
			Track:     trackInfo,
			Timestamp: time.Now().Unix(),
		})
		if err != nil {
			return struct{}{}, err
		}
		if resp.Error != "" {
			return struct{}{}, nil
		}
		return struct{}{}, nil
	})
	return err
}

func (w *wasmScrobblerPlugin) Scrobble(ctx context.Context, userId string, s scrobbler.Scrobble) error {
	username, _ := request.UsernameFrom(ctx)
	if username == "" {
		u, ok := request.UserFrom(ctx)
		if ok {
			username = u.UserName
		}
	}

	track := &s.MediaFile
	artists := make([]*api.Artist, 0, len(track.Participants[model.RoleArtist]))
	for _, a := range track.Participants[model.RoleArtist] {
		artists = append(artists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
	}
	albumArtists := make([]*api.Artist, 0, len(track.Participants[model.RoleAlbumArtist]))
	for _, a := range track.Participants[model.RoleAlbumArtist] {
		albumArtists = append(albumArtists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
	}
	trackInfo := &api.TrackInfo{
		Id:           track.ID,
		Mbid:         track.MbzRecordingID,
		Name:         track.Title,
		Album:        track.Album,
		AlbumMbid:    track.MbzAlbumID,
		Artists:      artists,
		AlbumArtists: albumArtists,
		Length:       int32(track.Duration),
	}
	_, err := callMethod(ctx, w, "Scrobble", func(inst api.Scrobbler) (struct{}, error) {
		resp, err := inst.Scrobble(ctx, &api.ScrobblerScrobbleRequest{
			UserId:    userId,
			Username:  username,
			Track:     trackInfo,
			Timestamp: s.TimeStamp.Unix(),
		})
		if err != nil {
			return struct{}{}, err
		}
		if resp.Error != "" {
			return struct{}{}, nil
		}
		return struct{}{}, nil
	})
	return err
}
