package persistence

import (
	"context"
	"time"

	. "github.com/Masterminds/squirrel"
	"github.com/astaxie/beego/orm"
	"github.com/deluan/navidrome/log"
	"github.com/deluan/navidrome/model"
	"github.com/deluan/rest"
)

type playlistRepository struct {
	sqlRepository
	sqlRestful
}

func NewPlaylistRepository(ctx context.Context, o orm.Ormer) model.PlaylistRepository {
	r := &playlistRepository{}
	r.ctx = ctx
	r.ormer = o
	r.tableName = "playlist"
	return r
}

func (r *playlistRepository) userFilter() Sqlizer {
	user := loggedUser(r.ctx)
	if user.IsAdmin {
		return And{}
	}
	return Or{
		Eq{"public": true},
		Eq{"owner": user.UserName},
	}
}

func (r *playlistRepository) CountAll(options ...model.QueryOptions) (int64, error) {
	sql := Select().Where(r.userFilter())
	return r.count(sql, options...)
}

func (r *playlistRepository) Exists(id string) (bool, error) {
	return r.exists(Select().Where(And{Eq{"id": id}, r.userFilter()}))
}

func (r *playlistRepository) Delete(id string) error {
	usr := loggedUser(r.ctx)
	if !usr.IsAdmin {
		pls, err := r.Get(id)
		if err != nil {
			return err
		}
		if pls.Owner != usr.UserName {
			return rest.ErrPermissionDenied
		}
	}
	return r.delete(And{Eq{"id": id}, r.userFilter()})
}

func (r *playlistRepository) Put(p *model.Playlist) error {
	if p.ID == "" {
		p.CreatedAt = time.Now()
	} else {
		ok, err := r.Exists(p.ID)
		if err != nil {
			return err
		}
		if !ok {
			return model.ErrNotAuthorized
		}
	}
	p.UpdatedAt = time.Now()

	// Save tracks for later and set it to nil, to avoid trying to save it to the DB
	tracks := p.Tracks
	p.Tracks = nil

	id, err := r.put(p.ID, p)
	if err != nil {
		return err
	}
	p.ID = id

	// Only update tracks if they are specified
	if tracks != nil {
		err = r.updateTracks(id, tracks)
		if err != nil {
			return err
		}
	}
	return r.loadTracks(p)
}

func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
	sel := r.newSelect().Columns("*").Where(And{Eq{"id": id}, r.userFilter()})
	var pls model.Playlist
	err := r.queryOne(sel, &pls)
	if err != nil {
		return nil, err
	}
	err = r.loadTracks(&pls)
	return &pls, err
}

func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playlists, error) {
	sel := r.newSelect(options...).Columns("*").Where(r.userFilter())
	res := model.Playlists{}
	err := r.queryAll(sel, &res)
	return res, err
}

func (r *playlistRepository) updateTracks(id string, tracks model.MediaFiles) error {
	ids := make([]string, len(tracks))
	for i := range tracks {
		ids[i] = tracks[i].ID
	}
	return r.Tracks(id).Update(ids)
}

func (r *playlistRepository) loadTracks(pls *model.Playlist) error {
	tracksQuery := Select().From("playlist_tracks").
		LeftJoin("annotation on ("+
			"annotation.item_id = media_file_id"+
			" AND annotation.item_type = 'media_file'"+
			" AND annotation.user_id = '"+userId(r.ctx)+"')").
		Columns("starred", "starred_at", "play_count", "play_date", "rating", "f.*").
		Join("media_file f on f.id = media_file_id").
		Where(Eq{"playlist_id": pls.ID}).OrderBy("playlist_tracks.id")
	err := r.queryAll(tracksQuery, &pls.Tracks)
	if err != nil {
		log.Error("Error loading playlist tracks", "playlist", pls.Name, "id", pls.ID)
	}
	return err
}

func (r *playlistRepository) Count(options ...rest.QueryOptions) (int64, error) {
	return r.CountAll(r.parseRestOptions(options...))
}

func (r *playlistRepository) Read(id string) (interface{}, error) {
	return r.Get(id)
}

func (r *playlistRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
	return r.GetAll(r.parseRestOptions(options...))
}

func (r *playlistRepository) EntityName() string {
	return "playlist"
}

func (r *playlistRepository) NewInstance() interface{} {
	return &model.Playlist{}
}

func (r *playlistRepository) Save(entity interface{}) (string, error) {
	pls := entity.(*model.Playlist)
	pls.Owner = loggedUser(r.ctx).UserName
	err := r.Put(pls)
	if err != nil {
		return "", err
	}
	return pls.ID, err
}

func (r *playlistRepository) Update(entity interface{}, cols ...string) error {
	pls := entity.(*model.Playlist)
	usr := loggedUser(r.ctx)
	if !usr.IsAdmin && pls.Owner != usr.UserName {
		return rest.ErrPermissionDenied
	}
	err := r.Put(pls)
	if err == model.ErrNotFound {
		return rest.ErrNotFound
	}
	return err
}

func (r *playlistRepository) removeOrphans() error {
	sel := Select("playlist_tracks.playlist_id as id", "p.name").From("playlist_tracks").
		Join("playlist p on playlist_tracks.playlist_id = p.id").
		LeftJoin("media_file mf on playlist_tracks.media_file_id = mf.id").
		Where(Eq{"mf.id": nil}).
		GroupBy("playlist_tracks.playlist_id")

	var pls []struct{ Id, Name string }
	err := r.queryAll(sel, &pls)
	if err != nil {
		return err
	}

	for _, pl := range pls {
		log.Debug(r.ctx, "Cleaning-up orphan tracks from playlist", "id", pl.Id, "name", pl.Name)
		del := Delete("playlist_tracks").Where(And{
			ConcatExpr("media_file_id not in (select id from media_file)"),
			Eq{"playlist_id": pl.Id},
		})
		n, err := r.executeSQL(del)
		if n == 0 || err != nil {
			return err
		}
		log.Debug(r.ctx, "Deleted tracks, now reordering", "id", pl.Id, "name", pl.Name, "deleted", n)

		// To reorganize the playlist, just add an empty list of new tracks
		trks := r.Tracks(pl.Id)
		if err := trks.Add(nil); err != nil {
			return err
		}
	}
	return nil
}

var _ model.PlaylistRepository = (*playlistRepository)(nil)
var _ rest.Repository = (*playlistRepository)(nil)
var _ rest.Persistable = (*playlistRepository)(nil)
