package store

import (
	"context"
	"fmt"

	"github.com/jackc/pgx/v5"
)

// Which colour schemes an operator has decided a viewer may choose from.
//
// The store deliberately knows nothing about what a theme *is*: the catalogue, the palettes
// and the rule about seasons all live in internal/api next to the client contract, the same
// division user_preferences draws. What is here is only the set of ids, and the one property
// that has to be true at this level — that an empty result means "unrestricted", never "no
// themes at all". See the table comment in schema.sql.

// UserThemes is the ids this viewer may pick between, or an empty slice for anybody the
// operator has never restricted — which is everybody, until they do.
func (s *Store) UserThemes(ctx context.Context, userID string) ([]string, error) {
	rows, err := s.pool.Query(ctx,
		`SELECT theme_id FROM user_themes WHERE emby_user_id = $1 ORDER BY theme_id`, userID)
	if err != nil {
		return nil, fmt.Errorf("store: read user themes: %w", err)
	}
	defer rows.Close()
	themes := []string{}
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, fmt.Errorf("store: scan user theme: %w", err)
		}
		themes = append(themes, id)
	}
	return themes, rows.Err()
}

// AllUserThemes is the admin console's read: one query for the whole accounts page rather
// than one per person, since that page already fans out over every account it lists.
func (s *Store) AllUserThemes(ctx context.Context) (map[string][]string, error) {
	rows, err := s.pool.Query(ctx,
		`SELECT emby_user_id, theme_id FROM user_themes ORDER BY emby_user_id, theme_id`)
	if err != nil {
		return nil, fmt.Errorf("store: list user themes: %w", err)
	}
	defer rows.Close()
	all := map[string][]string{}
	for rows.Next() {
		var userID, themeID string
		if err := rows.Scan(&userID, &themeID); err != nil {
			return nil, fmt.Errorf("store: scan user themes: %w", err)
		}
		all[userID] = append(all[userID], themeID)
	}
	return all, rows.Err()
}

// SetUserThemes replaces this viewer's allowlist wholesale.
//
// Replace rather than merge because the console sends the whole set of ticked boxes, and a
// merge would make unticking one impossible. It is one transaction so a viewer is never
// momentarily allowed nothing — a television resolving its theme in that window would be
// told the default and would repaint itself for no reason.
//
// An empty list deletes the rows rather than storing anything, which is what keeps
// "unrestricted" a single representation. api.normalizeThemeAllowlist is what turns "every
// box ticked" into that empty list before it arrives here.
func (s *Store) SetUserThemes(ctx context.Context, userID string, themes []string) error {
	return pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
		if _, err := tx.Exec(ctx,
			`DELETE FROM user_themes WHERE emby_user_id = $1`, userID); err != nil {
			return fmt.Errorf("store: clear user themes: %w", err)
		}
		for _, id := range themes {
			if _, err := tx.Exec(ctx, `
				INSERT INTO user_themes (emby_user_id, theme_id) VALUES ($1, $2)
				ON CONFLICT DO NOTHING`, userID, id); err != nil {
				return fmt.Errorf("store: write user theme: %w", err)
			}
		}
		return nil
	})
}
