package entity

import (
	"fmt"
	"time"

	"github.com/photoprism/photoprism/internal/event"
	"github.com/photoprism/photoprism/pkg/clean"
	"github.com/photoprism/photoprism/pkg/rnd"
)

const PermDefault uint = 0

const (
	PermNone uint = 1 << iota
	PermView
	PermReact
	PermComment
	PermUpload
	PermEdit
	PermShare
	PermAll
)

// SharePrefix for RefID.
const (
	SharePrefix = "share"
)

// UserShares represents shared content.
type UserShares []UserShare

// UIDs returns shared UIDs.
func (m UserShares) UIDs() UIDs {
	result := make(UIDs, len(m))

	for i, share := range m {
		result[i] = share.ShareUID
	}

	return result
}

// Empty checks if there are no shares.
func (m UserShares) Empty() bool {
	return m == nil || len(m) == 0
}

// Contains checks the uid is shared.
func (m UserShares) Contains(uid string) bool {
	if len(m) == 0 {
		return false
	}

	for _, share := range m {
		if share.ShareUID == uid {
			return true
		}
	}

	return false
}

// UserShare represents content shared with a user.
type UserShare struct {
	UserUID   string     `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"-" yaml:"UserUID"`
	ShareUID  string     `gorm:"type:VARBINARY(42);primary_key;index;" json:"ShareUID" yaml:"ShareUID"`
	LinkUID   string     `gorm:"type:VARBINARY(42);" json:"LinkUID,omitempty" yaml:"LinkUID,omitempty"`
	ExpiresAt *time.Time `sql:"index" json:"ExpiresAt,omitempty" yaml:"ExpiresAt,omitempty"`
	Comment   string     `gorm:"size:512;" json:"Comment,omitempty" yaml:"Comment,omitempty"`
	Perm      uint       `json:"Perm,omitempty" yaml:"Perm,omitempty"`
	RefID     string     `gorm:"type:VARBINARY(16);" json:"-" yaml:"-"`
	CreatedAt time.Time  `json:"CreatedAt" yaml:"-"`
	UpdatedAt time.Time  `json:"UpdatedAt" yaml:"-"`
}

// TableName returns the entity table name.
func (UserShare) TableName() string {
	return "auth_users_shares"
}

// NewUserShare creates a new entity model.
func NewUserShare(userUID, shareUid string, perm uint, expires *time.Time) *UserShare {
	result := &UserShare{
		UserUID:   userUID,
		ShareUID:  shareUid,
		Perm:      perm,
		RefID:     rnd.RefID(SharePrefix),
		CreatedAt: TimeStamp(),
		UpdatedAt: TimeStamp(),
		ExpiresAt: expires,
	}

	return result
}

// FindUserShare fetches the matching record or returns null if it was not found.
func FindUserShare(find UserShare) *UserShare {
	if !find.HasID() {
		return nil
	}

	m := &UserShare{}

	// Find matching record.
	if UnscopedDb().First(m, "user_uid = ? AND share_uid = ?", find.UserUID, find.ShareUID).RecordNotFound() {
		return nil
	}

	return m
}

// FindUserShares finds all shares to which the user has access.
func FindUserShares(userUid string) UserShares {
	found := UserShares{}

	if rnd.InvalidUID(userUid, UserUID) {
		return found
	}

	// Find matching record.
	if err := UnscopedDb().Find(&found, "user_uid = ? AND (expires_at IS NULL OR expires_at > ?)", userUid, TimeStamp()).Error; err != nil {
		event.AuditWarn([]string{"user %s", "find shares", "%s"}, clean.Log(userUid), err)
		return nil
	}

	return found
}

// HasID tests if the entity has a valid uid.
func (m *UserShare) HasID() bool {
	return rnd.IsUID(m.UserUID, UserUID) && rnd.IsUID(m.ShareUID, 0)
}

// Create inserts a new record into the database.
func (m *UserShare) Create() error {
	return Db().Create(m).Error
}

// Save updates the record in the database or inserts a new record if it does not already exist.
func (m *UserShare) Save() error {
	return Db().Save(m).Error
}

// Updates changes multiple record values.
func (m *UserShare) Updates(values interface{}) error {
	return UnscopedDb().Model(m).Updates(values).Error
}

// UpdateLink updates the share data using the Link provided.
func (m *UserShare) UpdateLink(link Link) error {
	if m.ShareUID != link.ShareUID {
		return fmt.Errorf("shared uid does not match")
	}

	m.LinkUID = link.LinkUID
	m.Comment = link.Comment
	m.Perm = link.Perm
	m.UpdatedAt = TimeStamp()
	m.ExpiresAt = link.ExpiresAt()

	values := Values{
		"link_uid":   m.LinkUID,
		"expires_at": m.ExpiresAt,
		"comment":    m.Comment,
		"perm":       m.Perm,
		"updated_at": m.UpdatedAt}

	return m.Updates(values)
}
