package repo

import (
	"context"
	"time"

	"github.com/google/uuid"
	"github.com/hay-kot/homebox/backend/internal/data/ent"
	"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
	"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
	"github.com/hay-kot/homebox/backend/pkgs/hasher"
	"github.com/hay-kot/homebox/backend/pkgs/set"
)

type TokenRepository struct {
	db *ent.Client
}

type (
	UserAuthTokenCreate struct {
		TokenHash []byte    `json:"token"`
		UserID    uuid.UUID `json:"userId"`
		ExpiresAt time.Time `json:"expiresAt"`
	}

	UserAuthToken struct {
		UserAuthTokenCreate
		CreatedAt time.Time `json:"createdAt"`
	}
)

func (u UserAuthToken) IsExpired() bool {
	return u.ExpiresAt.Before(time.Now())
}

// GetUserFromToken get's a user from a token
func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (UserOut, error) {
	user, err := r.db.AuthTokens.Query().
		Where(authtokens.Token(token)).
		Where(authtokens.ExpiresAtGTE(time.Now())).
		WithUser().
		QueryUser().
		WithGroup().
		Only(ctx)
	if err != nil {
		return UserOut{}, err
	}

	return mapUserOut(user), nil
}

func (r *TokenRepository) GetRoles(ctx context.Context, token string) (*set.Set[string], error) {
	tokenHash := hasher.HashToken(token)

	roles, err := r.db.AuthRoles.
		Query().
		Where(authroles.HasTokenWith(
			authtokens.Token(tokenHash),
		)).
		All(ctx)
	if err != nil {
		return nil, err
	}

	roleSet := set.Make[string](len(roles))

	for _, role := range roles {
		roleSet.Insert(role.Role.String())
	}

	return &roleSet, nil
}

// Creates a token for a user
func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthTokenCreate, roles ...authroles.Role) (UserAuthToken, error) {
	dbToken, err := r.db.AuthTokens.Create().
		SetToken(createToken.TokenHash).
		SetUserID(createToken.UserID).
		SetExpiresAt(createToken.ExpiresAt).
		Save(ctx)
	if err != nil {
		return UserAuthToken{}, err
	}

	for _, role := range roles {
		_, err := r.db.AuthRoles.Create().
			SetRole(role).
			SetToken(dbToken).
			Save(ctx)
		if err != nil {
			return UserAuthToken{}, err
		}
	}

	return UserAuthToken{
		UserAuthTokenCreate: UserAuthTokenCreate{
			TokenHash: dbToken.Token,
			UserID:    createToken.UserID,
			ExpiresAt: dbToken.ExpiresAt,
		},
		CreatedAt: dbToken.CreatedAt,
	}, nil
}

// DeleteToken remove a single token from the database - equivalent to revoke or logout
func (r *TokenRepository) DeleteToken(ctx context.Context, token []byte) error {
	_, err := r.db.AuthTokens.Delete().Where(authtokens.Token(token)).Exec(ctx)
	return err
}

// PurgeExpiredTokens removes all expired tokens from the database
func (r *TokenRepository) PurgeExpiredTokens(ctx context.Context) (int, error) {
	tokensDeleted, err := r.db.AuthTokens.Delete().Where(authtokens.ExpiresAtLTE(time.Now())).Exec(ctx)
	if err != nil {
		return 0, err
	}

	return tokensDeleted, nil
}

func (r *TokenRepository) DeleteAll(ctx context.Context) (int, error) {
	amount, err := r.db.AuthTokens.Delete().Exec(ctx)
	return amount, err
}