package dbmodel

import (
	"time"

	"github.com/go-pg/pg/v10"
	"github.com/pkg/errors"
)

// Represents a logged-in user session. The table layout is adopted from the
// official Postgres store for the SCS session manager (See
// https://github.com/alexedwards/scs/tree/master/postgresstor).
// The session is identified by a token,
// which is a random bytes generated by the session manager. The session data
// are stored in the serialized form.
// The session is considered expired if the current time is greater than the
// expiry time. The session manager is responsible for removing expired
// sessions from the database. It is done by the cleanup goroutine.
type Session struct {
	tableName struct{}  `pg:"sessions"` //nolint:unused
	Token     string    `pg:",pk,notnull,use_zero"`
	Data      []byte    `pg:",notnull,use_zero"`
	Expiry    time.Time `pg:",notnull,use_zero"`
}

// Returns an active session with the given token. If the session is not found
// or has expired, returns nil and no error.
func GetActiveSession(dbi pg.DBI, token string) (*Session, error) {
	session := &Session{}
	err := dbi.Model(session).
		Where("token = ?", token).
		Where("current_timestamp < expiry").
		Select()

	if errors.Is(err, pg.ErrNoRows) {
		return nil, nil
	}
	return session, errors.Wrapf(err, "problem getting the session with token %s", token)
}

// Adds a session to the database. If the session already exists, it will be
// updated.
func AddOrUpdateSession(dbi pg.DBI, session *Session) error {
	_, err := dbi.Model(session).OnConflict("(token) DO UPDATE").
		Set("data = EXCLUDED.data").
		Set("expiry = EXCLUDED.expiry").
		Insert()
	return errors.Wrapf(err, "problem adding session with token %s", session.Token)
}

// Deletes a session with a given token from the database.
func DeleteSession(dbi pg.DBI, token string) error {
	_, err := dbi.Model((*Session)(nil)).
		Where("token = ?", token).
		Delete()
	return errors.Wrapf(err, "problem deleting session with token %s", token)
}

// Returns all active sessions.
func GetAllActiveSessions(dbi pg.DBI) ([]*Session, error) {
	var sessions []*Session
	err := dbi.Model(&sessions).
		Where("current_timestamp < expiry").
		Select()
	return sessions, errors.Wrap(err, "problem getting all active sessions")
}

// Returns all sessions, including expired ones.
func GetAllSessions(dbi pg.DBI) ([]*Session, error) {
	var sessions []*Session
	err := dbi.Model(&sessions).Select()
	return sessions, errors.Wrap(err, "problem getting all sessions")
}

// Removes all expired sessions from the database.
func DeleteAllExpiredSessions(dbi pg.DBI) error {
	_, err := dbi.Model((*Session)(nil)).
		Where("expiry < current_timestamp").
		Delete()
	return errors.Wrap(err, "problem removing all expired sessions")
}
