package services

// Live-DB tests for CardLayoutService. Skipped when TEST_DATABASE_URL
// is unset.

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 cardLayoutTestEnv struct {
	t       *testing.T
	pool    *sqlx.DB
	svc     *CardLayoutService
	userID  uuid.UUID
	cleanup func()
}

func setupCardLayoutTest(t *testing.T) *cardLayoutTestEnv {
	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", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.users (id, email, display_name, office, global_role)
		 VALUES ($1, $1::text || '@test.local', 'Card Layout Test', 'munich', 'standard')
		 ON CONFLICT (id) DO NOTHING`, userID); err != nil {
		t.Fatalf("seed paliad.users: %v", err)
	}

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

	return &cardLayoutTestEnv{
		t:       t,
		pool:    pool,
		svc:     NewCardLayoutService(pool),
		userID:  userID,
		cleanup: cleanup,
	}
}

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

	// First call seeds the default.
	def, err := env.svc.GetDefault(ctx, env.userID)
	if err != nil {
		t.Fatalf("GetDefault: %v", err)
	}
	if def.Name != "Standard" || !def.IsDefault {
		t.Errorf("seeded default: name=%q is_default=%v; want Standard, true", def.Name, def.IsDefault)
	}

	// Second call returns the same row, not a new seed.
	def2, err := env.svc.GetDefault(ctx, env.userID)
	if err != nil {
		t.Fatalf("GetDefault second: %v", err)
	}
	if def2.ID != def.ID {
		t.Errorf("second GetDefault returned %v; want same id %v", def2.ID, def.ID)
	}
}

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

	row, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{
		Name:      "Mein Erstes",
		Layout:    DefaultLayoutSpec(),
		IsDefault: false, // even with false, first row becomes default.
	})
	if err != nil {
		t.Fatalf("Create: %v", err)
	}
	if !row.IsDefault {
		t.Errorf("first layout is_default=false; want true (auto-flip)")
	}
}

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

	a, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "A", Layout: DefaultLayoutSpec()})
	if err != nil {
		t.Fatalf("Create A: %v", err)
	}
	if !a.IsDefault {
		t.Fatalf("A is_default=false; want true")
	}
	b, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "B", Layout: DefaultLayoutSpec()})
	if err != nil {
		t.Fatalf("Create B: %v", err)
	}
	if b.IsDefault {
		t.Fatalf("B is_default=true; want false (A is still default)")
	}

	// Flip B → default.
	bAfter, err := env.svc.SetDefault(ctx, env.userID, b.ID)
	if err != nil {
		t.Fatalf("SetDefault B: %v", err)
	}
	if !bAfter.IsDefault {
		t.Errorf("after SetDefault B.is_default=false")
	}

	// A should no longer be default.
	aAfter, err := env.svc.Get(ctx, env.userID, a.ID)
	if err != nil {
		t.Fatalf("Get A: %v", err)
	}
	if aAfter.IsDefault {
		t.Errorf("A still is_default=true after B took the flag")
	}
}

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

	row, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "OnlyOne", Layout: DefaultLayoutSpec()})
	if err != nil {
		t.Fatalf("Create: %v", err)
	}
	err = env.svc.Delete(ctx, env.userID, row.ID)
	if !errors.Is(err, ErrUserCardLayoutDefaultGate) {
		t.Errorf("Delete default = %v; want ErrUserCardLayoutDefaultGate", err)
	}
}

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

	_, _ = env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "Default", Layout: DefaultLayoutSpec()})
	b, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "Throwaway", Layout: DefaultLayoutSpec()})
	if err != nil {
		t.Fatalf("Create throwaway: %v", err)
	}
	if err := env.svc.Delete(ctx, env.userID, b.ID); err != nil {
		t.Fatalf("Delete: %v", err)
	}
	if _, err := env.svc.Get(ctx, env.userID, b.ID); !errors.Is(err, ErrUserCardLayoutNotFound) {
		t.Errorf("Get after delete = %v; want ErrUserCardLayoutNotFound", err)
	}
}

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

	if _, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "Same", Layout: DefaultLayoutSpec()}); err != nil {
		t.Fatalf("Create first: %v", err)
	}
	_, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "Same", Layout: DefaultLayoutSpec()})
	if !errors.Is(err, ErrUserCardLayoutNameTaken) {
		t.Errorf("duplicate name = %v; want ErrUserCardLayoutNameTaken", err)
	}
}

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

	row, err := env.svc.Create(ctx, env.userID, CreateCardLayoutInput{Name: "Editable", Layout: DefaultLayoutSpec()})
	if err != nil {
		t.Fatalf("Create: %v", err)
	}

	newName := "Renamed"
	newLayout := DefaultLayoutSpec()
	newLayout.Density = CardDensityCompact
	updated, err := env.svc.Update(ctx, env.userID, row.ID, UpdateCardLayoutInput{
		Name:   &newName,
		Layout: &newLayout,
	})
	if err != nil {
		t.Fatalf("Update: %v", err)
	}
	if updated.Name != "Renamed" {
		t.Errorf("name = %q; want Renamed", updated.Name)
	}

	parsed, err := ParseLayoutSpec(updated.LayoutJSON)
	if err != nil {
		t.Fatalf("ParseLayoutSpec on updated: %v", err)
	}
	if parsed.Density != CardDensityCompact {
		t.Errorf("density round-trip = %q; want compact", parsed.Density)
	}
}
