package persistence

import (
	"context"
	"strings"
	"time"

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

type playlist struct {
	ID        string `orm:"column(id)"`
	Name      string
	Comment   string
	Duration  float32
	Owner     string
	Public    bool
	Tracks    string
	CreatedAt time.Time
	UpdatedAt time.Time
}

type playlistRepository struct {
	sqlRepository
}

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) CountAll() (int64, error) {
	return r.count(Select())
}

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

func (r *playlistRepository) Delete(id string) error {
	return r.delete(Eq{"id": id})
}

func (r *playlistRepository) Put(p *model.Playlist) error {
	if p.ID == "" {
		p.CreatedAt = time.Now()
	}
	p.UpdatedAt = time.Now()
	pls := r.fromModel(p)
	_, err := r.put(pls.ID, pls)
	return err
}

func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
	sel := r.newSelect().Columns("*").Where(Eq{"id": id})
	var res playlist
	err := r.queryOne(sel, &res)
	pls := r.toModel(&res)
	return &pls, err
}

func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playlists, error) {
	sel := r.newSelect(options...).Columns("*")
	var res []playlist
	err := r.queryAll(sel, &res)
	return r.toModels(res), err
}

func (r *playlistRepository) toModels(all []playlist) model.Playlists {
	result := make(model.Playlists, len(all))
	for i, p := range all {
		result[i] = r.toModel(&p)
	}
	return result
}

func (r *playlistRepository) toModel(p *playlist) model.Playlist {
	pls := model.Playlist{
		ID:        p.ID,
		Name:      p.Name,
		Comment:   p.Comment,
		Duration:  p.Duration,
		Owner:     p.Owner,
		Public:    p.Public,
		CreatedAt: p.CreatedAt,
		UpdatedAt: p.UpdatedAt,
	}
	if strings.TrimSpace(p.Tracks) != "" {
		tracks := strings.Split(p.Tracks, ",")
		for _, t := range tracks {
			pls.Tracks = append(pls.Tracks, model.MediaFile{ID: t})
		}
	}
	pls.Tracks = r.loadTracks(&pls)
	return pls
}

func (r *playlistRepository) fromModel(p *model.Playlist) playlist {
	pls := playlist{
		ID:        p.ID,
		Name:      p.Name,
		Comment:   p.Comment,
		Owner:     p.Owner,
		Public:    p.Public,
		CreatedAt: p.CreatedAt,
		UpdatedAt: p.UpdatedAt,
	}
	p.Tracks = r.loadTracks(p)
	var newTracks []string
	for _, t := range p.Tracks {
		newTracks = append(newTracks, t.ID)
		pls.Duration += t.Duration
	}
	pls.Tracks = strings.Join(newTracks, ",")
	return pls
}

// TODO: Introduce a relation table for Playlist <-> MediaFiles, and rewrite this method in pure SQL
func (r *playlistRepository) loadTracks(p *model.Playlist) model.MediaFiles {
	if len(p.Tracks) == 0 {
		return nil
	}

	// Collect all ids
	ids := make([]string, len(p.Tracks))
	for i, t := range p.Tracks {
		ids[i] = t.ID
	}

	// Break the list in chunks, up to 50 items, to avoid hitting SQLITE_MAX_FUNCTION_ARG limit
	const chunkSize = 50
	var chunks [][]string
	for i := 0; i < len(ids); i += chunkSize {
		end := i + chunkSize
		if end > len(ids) {
			end = len(ids)
		}

		chunks = append(chunks, ids[i:end])
	}

	// Query each chunk of media_file ids and store results in a map
	mfRepo := NewMediaFileRepository(r.ctx, r.ormer)
	trackMap := map[string]model.MediaFile{}
	for i := range chunks {
		idsFilter := Eq{"id": chunks[i]}
		tracks, err := mfRepo.GetAll(model.QueryOptions{Filters: idsFilter})
		if err != nil {
			log.Error(r.ctx, "Could not load playlist's tracks", "playlistName", p.Name, "playlistId", p.ID, err)
		}
		for _, t := range tracks {
			trackMap[t.ID] = t
		}
	}

	// Create a new list of tracks with the same order as the original
	newTracks := make(model.MediaFiles, len(p.Tracks))
	for i, t := range p.Tracks {
		newTracks[i] = trackMap[t.ID]
	}
	return newTracks
}

var _ model.PlaylistRepository = (*playlistRepository)(nil)
