package persistence

import (
	"context"
	"errors"

	. "github.com/Masterminds/squirrel"
	"github.com/deluan/rest"
	"github.com/navidrome/navidrome/model"
	"github.com/pocketbase/dbx"
)

type playerRepository struct {
	sqlRepository
	sqlRestful
}

func NewPlayerRepository(ctx context.Context, db dbx.Builder) model.PlayerRepository {
	r := &playerRepository{}
	r.ctx = ctx
	r.db = db
	r.tableName = "player"
	r.filterMappings = map[string]filterFunc{
		"name": containsFilter,
	}
	return r
}

func (r *playerRepository) Put(p *model.Player) error {
	_, err := r.put(p.ID, p)
	return err
}

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

func (r *playerRepository) FindMatch(userName, client, userAgent string) (*model.Player, error) {
	sel := r.newSelect().Columns("*").Where(And{
		Eq{"client": client},
		Eq{"user_agent": userAgent},
		Eq{"user_name": userName},
	})
	var res model.Player
	err := r.queryOne(sel, &res)
	return &res, err
}

func (r *playerRepository) newRestSelect(options ...model.QueryOptions) SelectBuilder {
	s := r.newSelect(options...)
	return s.Where(r.addRestriction())
}

func (r *playerRepository) addRestriction(sql ...Sqlizer) Sqlizer {
	s := And{}
	if len(sql) > 0 {
		s = append(s, sql[0])
	}
	u := loggedUser(r.ctx)
	if u.IsAdmin {
		return s
	}
	return append(s, Eq{"user_name": u.UserName})
}

func (r *playerRepository) Count(options ...rest.QueryOptions) (int64, error) {
	return r.count(r.newRestSelect(), r.parseRestOptions(options...))
}

func (r *playerRepository) Read(id string) (interface{}, error) {
	sel := r.newRestSelect().Columns("*").Where(Eq{"id": id})
	var res model.Player
	err := r.queryOne(sel, &res)
	return &res, err
}

func (r *playerRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
	sel := r.newRestSelect(r.parseRestOptions(options...)).Columns("*")
	res := model.Players{}
	err := r.queryAll(sel, &res)
	return res, err
}

func (r *playerRepository) EntityName() string {
	return "player"
}

func (r *playerRepository) NewInstance() interface{} {
	return &model.Player{}
}

func (r *playerRepository) isPermitted(p *model.Player) bool {
	u := loggedUser(r.ctx)
	return u.IsAdmin || p.UserName == u.UserName
}

func (r *playerRepository) Save(entity interface{}) (string, error) {
	t := entity.(*model.Player)
	if !r.isPermitted(t) {
		return "", rest.ErrPermissionDenied
	}
	id, err := r.put(t.ID, t)
	if errors.Is(err, model.ErrNotFound) {
		return "", rest.ErrNotFound
	}
	return id, err
}

func (r *playerRepository) Update(id string, entity interface{}, cols ...string) error {
	t := entity.(*model.Player)
	t.ID = id
	if !r.isPermitted(t) {
		return rest.ErrPermissionDenied
	}
	_, err := r.put(id, t, cols...)
	if errors.Is(err, model.ErrNotFound) {
		return rest.ErrNotFound
	}
	return err
}

func (r *playerRepository) Delete(id string) error {
	filter := r.addRestriction(And{Eq{"id": id}})
	err := r.delete(filter)
	if errors.Is(err, model.ErrNotFound) {
		return rest.ErrNotFound
	}
	return err
}

var _ model.PlayerRepository = (*playerRepository)(nil)
var _ rest.Repository = (*playerRepository)(nil)
var _ rest.Persistable = (*playerRepository)(nil)
