package policy

import (
	"context"
	"errors"

	"github.com/FAU-CDI/wisski-distillery/internal/models"
	"gorm.io/gorm/clause"
)

var (
	ErrNoAccess = errors.New("no access")
	ErrInvalid  = errors.New("invalid parameters")
)

// Set sets a specific grant, overwriting any previous grant.
//
// User and Slug must not be empty.
// If DrupalUsername is empty, sets the username to be equal to the user.
func (policy *Policy) Set(ctx context.Context, grant models.Grant) error {
	if grant.DrupalUsername == "" {
		grant.DrupalUsername = grant.User
	}
	if grant.User == "" || grant.Slug == "" {
		return ErrInvalid
	}

	// check that the referenced user exists!
	{
		_, err := policy.Dependencies.Auth.User(ctx, grant.User)
		if err != nil {
			return err
		}
	}

	// get the table
	table, err := policy.table(ctx)
	if err != nil {
		return err
	}

	// and create or update the given user / slug combination
	return table.Clauses(
		clause.OnConflict{OnConstraint: "user_slug", UpdateAll: true},
	).Create(&grant).Error
}

// Remove removes access for the given username form the given instance.
// The user not having access is not an error.
func (policy *Policy) Remove(ctx context.Context, username string, slug string) error {
	// empty username or slug never have acccess
	if username == "" || slug == "" {
		return ErrInvalid
	}

	// get the table
	table, err := policy.table(ctx)
	if err != nil {
		return err
	}

	// delete the access from the database
	return table.Delete(&models.Grant{}, models.Grant{User: username, Slug: slug}).Error
}

// User returns all grants for the given user
func (policy *Policy) User(ctx context.Context, username string) (grants []models.Grant, err error) {
	if username == "" {
		return nil, ErrInvalid
	}

	// get the table
	table, err := policy.table(ctx)
	if err != nil {
		return nil, err
	}

	// find the grants
	err = table.Find(&grants, models.Grant{User: username}).Error
	if err != nil {
		return nil, err
	}
	return grants, nil
}

// Instance returns all the grants for the given instance
func (policy *Policy) Instance(ctx context.Context, slug string) (grants []models.Grant, err error) {
	if slug == "" {
		return nil, ErrInvalid
	}

	// get the table
	table, err := policy.table(ctx)
	if err != nil {
		return nil, err
	}

	// find the grants
	err = table.Find(&grants, models.Grant{Slug: slug}).Error
	if err != nil {
		return nil, err
	}
	return grants, nil
}

// Has checks if the given username has access to the given instance.
// If the user has access, returns the provided grant.
//
// If the user does not have access, returns ErrNoAccess.
// Other errors may be returned in other cases.
func (policy *Policy) Has(ctx context.Context, username string, slug string) (grant models.Grant, err error) {
	// empty username or slug never have acccess
	if username == "" || slug == "" {
		return grant, ErrInvalid
	}

	// get the table
	table, err := policy.table(ctx)
	if err != nil {
		return grant, err
	}

	// read the access from the database
	res := table.Find(&grant, models.Grant{User: username, Slug: slug})
	if err := res.Error; err != nil {
		return grant, err
	}

	// if there were no rows affected, then there was no access granted
	if res.RowsAffected == 0 {
		return grant, ErrNoAccess
	}

	// return the username and admin
	return grant, nil
}
