package account

import (
	"context"
	"fmt"

	"github.com/go-jet/jet/v2/postgres"
	"github.com/google/uuid"

	"scrabble/backend/internal/postgres/jet/backend/table"
)

// Per-account roles. A role is a named capability or restriction attached to an
// account, the reusable replacement for per-feature boolean flags. The set is
// expected to grow; roles are validated against KnownRoles in Go so adding one
// needs no migration. Granted/revoked from the admin console (/users and the
// feedback section).
const (
	// RoleFeedbackBanned forbids the account from submitting feedback (only that;
	// it is not a full account suspension). See internal/feedback.
	RoleFeedbackBanned = "feedback_banned"

	// RoleNoBanner suppresses the in-app advertising banner for the account
	// unconditionally, overriding the usual eligibility (a free account with an
	// empty hint wallet otherwise sees it). See internal/ads.
	RoleNoBanner = "no_banner"

	// RoleChatMuted forbids the account from writing in the moderated Telegram
	// discussion chat, without otherwise restricting the game (the chat-only
	// counterpart to a full account suspension). It is one input to the chat-access
	// gate; an active admin suspension mutes the player regardless, so this role only
	// matters for an account that is not suspended. Granting or revoking it re-pushes
	// the chat-gate command for a member currently in the chat.
	RoleChatMuted = "chat_muted"
)

// KnownRoles is the set of roles the console may grant or revoke; an operator
// cannot assign an unrecognised role.
var KnownRoles = []string{RoleFeedbackBanned, RoleNoBanner, RoleChatMuted}

// IsKnownRole reports whether role is a recognised account role.
func IsKnownRole(role string) bool {
	for _, r := range KnownRoles {
		if r == role {
			return true
		}
	}
	return false
}

// GrantRole gives the account the role, idempotently (a repeat grant is a no-op).
func (s *Store) GrantRole(ctx context.Context, accountID uuid.UUID, role string) error {
	stmt := table.AccountRoles.
		INSERT(table.AccountRoles.AccountID, table.AccountRoles.Role).
		VALUES(accountID, role).
		ON_CONFLICT(table.AccountRoles.AccountID, table.AccountRoles.Role).DO_NOTHING()
	if _, err := stmt.ExecContext(ctx, s.db); err != nil {
		return fmt.Errorf("account: grant role %q to %s: %w", role, accountID, err)
	}
	return nil
}

// RevokeRole removes the role from the account, idempotently.
func (s *Store) RevokeRole(ctx context.Context, accountID uuid.UUID, role string) error {
	stmt := table.AccountRoles.
		DELETE().
		WHERE(table.AccountRoles.AccountID.EQ(postgres.UUID(accountID)).
			AND(table.AccountRoles.Role.EQ(postgres.String(role))))
	if _, err := stmt.ExecContext(ctx, s.db); err != nil {
		return fmt.Errorf("account: revoke role %q from %s: %w", role, accountID, err)
	}
	return nil
}

// HasRole reports whether the account holds the role.
func (s *Store) HasRole(ctx context.Context, accountID uuid.UUID, role string) (bool, error) {
	var ok bool
	err := s.db.QueryRowContext(ctx,
		`SELECT EXISTS (SELECT 1 FROM backend.account_roles WHERE account_id = $1 AND role = $2)`,
		accountID, role).Scan(&ok)
	if err != nil {
		return false, fmt.Errorf("account: has-role %q %s: %w", role, accountID, err)
	}
	return ok, nil
}

// ListRoles returns the account's roles, oldest grant first.
func (s *Store) ListRoles(ctx context.Context, accountID uuid.UUID) ([]string, error) {
	rows, err := s.db.QueryContext(ctx,
		`SELECT role FROM backend.account_roles WHERE account_id = $1 ORDER BY granted_at ASC`,
		accountID)
	if err != nil {
		return nil, fmt.Errorf("account: list roles %s: %w", accountID, err)
	}
	defer rows.Close()
	var out []string
	for rows.Next() {
		var role string
		if err := rows.Scan(&role); err != nil {
			return nil, fmt.Errorf("account: scan role: %w", err)
		}
		out = append(out, role)
	}
	return out, rows.Err()
}
