package services

// Live-DB tests for UserViewService. Skipped when TEST_DATABASE_URL is
// unset, mirroring the rest of the live-DB test suite.

import (
	"context"
	"errors"
	"os"
	"testing"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"

	"mgit.msbls.de/m/paliad/internal/db"
)

type userViewTestEnv struct {
	t       *testing.T
	pool    *sqlx.DB
	svc     *UserViewService
	userID  uuid.UUID
	cleanup func()
}

func setupUserViewTest(t *testing.T) *userViewTestEnv {
	t.Helper()
	url := os.Getenv("TEST_DATABASE_URL")
	if url == "" {
		t.Skip("TEST_DATABASE_URL not set — skipping live DB test")
	}
	if err := db.ApplyMigrations(url); err != nil {
		t.Fatalf("apply migrations: %v", err)
	}
	pool, err := sqlx.Connect("postgres", url)
	if err != nil {
		t.Fatalf("connect: %v", err)
	}
	ctx := context.Background()

	userID := uuid.New()
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO auth.users (id, email) VALUES ($1, $1::text || '@test.local')
		 ON CONFLICT (id) DO NOTHING`, userID); err != nil {
		t.Logf("skip auth.users seed: %v (continuing — auth schema may be locked down)", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.users (id, email, display_name, office, global_role)
		 VALUES ($1, $1::text || '@test.local', 'View Test User', 'munich', 'standard')
		 ON CONFLICT (id) DO NOTHING`, userID); err != nil {
		t.Fatalf("seed paliad.users: %v", err)
	}

	cleanup := func() {
		ctx := context.Background()
		pool.ExecContext(ctx, `DELETE FROM paliad.user_views WHERE user_id = $1`, userID)
		pool.ExecContext(ctx, `DELETE FROM paliad.users      WHERE id = $1`, userID)
		pool.ExecContext(ctx, `DELETE FROM auth.users        WHERE id = $1`, userID)
		pool.Close()
	}

	return &userViewTestEnv{
		t:       t,
		pool:    pool,
		svc:     NewUserViewService(pool),
		userID:  userID,
		cleanup: cleanup,
	}
}

func goodCreateInput(slug, name string) CreateUserViewInput {
	return CreateUserViewInput{
		Slug:       slug,
		Name:       name,
		FilterSpec: DefaultFilterSpec(),
		RenderSpec: DefaultRenderSpec(),
	}
}

func TestUserViewService_CreateAndList(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()

	ctx := context.Background()

	created, err := env.svc.Create(ctx, env.userID, goodCreateInput("freitag-stand", "Freitag-Stand"))
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if created.Slug != "freitag-stand" || created.Name != "Freitag-Stand" {
		t.Errorf("created shape: %+v", created)
	}
	if created.SortOrder != 0 {
		t.Errorf("first view sort_order = %d, want 0", created.SortOrder)
	}

	second, err := env.svc.Create(ctx, env.userID, goodCreateInput("siemens", "Siemens-Aktivität"))
	if err != nil {
		t.Fatalf("second create: %v", err)
	}
	if second.SortOrder != 1 {
		t.Errorf("second view sort_order = %d, want 1", second.SortOrder)
	}

	list, err := env.svc.ListForUser(ctx, env.userID)
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(list) != 2 {
		t.Fatalf("expected 2 views, got %d", len(list))
	}
	// sort_order ASC ordering
	if list[0].Slug != "freitag-stand" || list[1].Slug != "siemens" {
		t.Errorf("ordering: %v", []string{list[0].Slug, list[1].Slug})
	}
}

func TestUserViewService_GetBySlugAndID(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	created, err := env.svc.Create(ctx, env.userID, goodCreateInput("test-view", "Test View"))
	if err != nil {
		t.Fatalf("create: %v", err)
	}

	bySlug, err := env.svc.GetBySlug(ctx, env.userID, "test-view")
	if err != nil || bySlug == nil {
		t.Fatalf("GetBySlug: %v / nil", err)
	}
	if bySlug.ID != created.ID {
		t.Errorf("GetBySlug id mismatch")
	}

	byID, err := env.svc.GetByID(ctx, env.userID, created.ID)
	if err != nil || byID == nil {
		t.Fatalf("GetByID: %v / nil", err)
	}

	missing, err := env.svc.GetBySlug(ctx, env.userID, "does-not-exist")
	if err != nil {
		t.Fatalf("GetBySlug missing: %v", err)
	}
	if missing != nil {
		t.Error("missing slug should return nil")
	}
}

func TestUserViewService_SlugUniquenessPerUser(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	if _, err := env.svc.Create(ctx, env.userID, goodCreateInput("dup", "First")); err != nil {
		t.Fatalf("first create: %v", err)
	}
	_, err := env.svc.Create(ctx, env.userID, goodCreateInput("dup", "Second"))
	if !errors.Is(err, ErrUserViewSlugTaken) {
		t.Fatalf("duplicate slug must return ErrUserViewSlugTaken, got %v", err)
	}
}

func TestUserViewService_Update(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	created, err := env.svc.Create(ctx, env.userID, goodCreateInput("orig", "Original"))
	if err != nil {
		t.Fatalf("create: %v", err)
	}

	newName := "Updated"
	newShowCount := true
	updated, err := env.svc.Update(ctx, env.userID, created.ID, UpdateUserViewInput{
		Name:      &newName,
		ShowCount: &newShowCount,
	})
	if err != nil {
		t.Fatalf("update: %v", err)
	}
	if updated.Name != "Updated" {
		t.Errorf("name not updated: %s", updated.Name)
	}
	if !updated.ShowCount {
		t.Errorf("show_count not updated")
	}
	// Slug should be unchanged.
	if updated.Slug != "orig" {
		t.Errorf("slug should be unchanged, got %s", updated.Slug)
	}
}

func TestUserViewService_UpdateRejectsReservedSlug(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	created, err := env.svc.Create(ctx, env.userID, goodCreateInput("freely", "Freely"))
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	reserved := "dashboard"
	_, err = env.svc.Update(ctx, env.userID, created.ID, UpdateUserViewInput{Slug: &reserved})
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("update to reserved slug must reject, got %v", err)
	}
}

func TestUserViewService_Delete(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	created, err := env.svc.Create(ctx, env.userID, goodCreateInput("doomed", "Doomed"))
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	deleted, err := env.svc.Delete(ctx, env.userID, created.ID)
	if err != nil || !deleted {
		t.Fatalf("delete: %v, %v", deleted, err)
	}
	deletedAgain, err := env.svc.Delete(ctx, env.userID, created.ID)
	if err != nil {
		t.Fatalf("second delete: %v", err)
	}
	if deletedAgain {
		t.Error("second delete should report not-deleted")
	}
}

func TestUserViewService_TouchAndMostRecent(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	a, _ := env.svc.Create(ctx, env.userID, goodCreateInput("a-view", "A"))
	b, _ := env.svc.Create(ctx, env.userID, goodCreateInput("b-view", "B"))

	// Before any touch — MostRecent is nil.
	mr, err := env.svc.MostRecent(ctx, env.userID)
	if err != nil {
		t.Fatalf("most_recent: %v", err)
	}
	if mr != nil {
		t.Errorf("MostRecent should be nil before any touch")
	}

	if err := env.svc.Touch(ctx, env.userID, a.ID); err != nil {
		t.Fatalf("touch a: %v", err)
	}
	if err := env.svc.Touch(ctx, env.userID, b.ID); err != nil {
		t.Fatalf("touch b: %v", err)
	}
	mr, err = env.svc.MostRecent(ctx, env.userID)
	if err != nil || mr == nil {
		t.Fatalf("most_recent after touch: %v / nil", err)
	}
	if mr.ID != b.ID {
		t.Errorf("most-recent should be b (touched last), got %s", mr.Slug)
	}
}

func TestUserViewService_RejectsReservedSlugOnCreate(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	_, err := env.svc.Create(ctx, env.userID, goodCreateInput("inbox", "Inbox copy"))
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("reserved slug on create must reject, got %v", err)
	}
}

func TestUserViewService_RejectsBadSlug(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	_, err := env.svc.Create(ctx, env.userID, goodCreateInput("Has Spaces", "Bad"))
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("slug with spaces must reject, got %v", err)
	}
	_, err = env.svc.Create(ctx, env.userID, goodCreateInput("UPPER", "Bad"))
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("uppercase slug must reject, got %v", err)
	}
}

func TestUserViewService_RejectsEmptyName(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	_, err := env.svc.Create(ctx, env.userID, CreateUserViewInput{
		Slug:       "no-name",
		Name:       "",
		FilterSpec: DefaultFilterSpec(),
		RenderSpec: DefaultRenderSpec(),
	})
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("empty name must reject, got %v", err)
	}
}

func TestUserViewService_RejectsInvalidSpec(t *testing.T) {
	env := setupUserViewTest(t)
	defer env.cleanup()
	ctx := context.Background()

	bad := DefaultFilterSpec()
	bad.Sources = nil
	_, err := env.svc.Create(ctx, env.userID, CreateUserViewInput{
		Slug:       "bad-spec",
		Name:       "Bad",
		FilterSpec: bad,
		RenderSpec: DefaultRenderSpec(),
	})
	if !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("invalid spec must reject, got %v", err)
	}
}
