package services

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

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

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

// user_service_test covers the onboarding Create path end-to-end against a
// real Postgres. Mirrors the akte_service_test setup pattern; skips when
// TEST_DATABASE_URL is unset.

func setupUserTest(t *testing.T) (*UserService, *sqlx.DB, func()) {
	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)
	}
	users := NewUserService(pool)
	return users, pool, func() { pool.Close() }
}

func seedAuthUser(t *testing.T, pool *sqlx.DB, id uuid.UUID, email string) {
	t.Helper()
	if _, err := pool.ExecContext(context.Background(),
		`INSERT INTO auth.users (id, email) VALUES ($1, $2)
		 ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email`, id, email); err != nil {
		t.Fatalf("seed auth.users: %v", err)
	}
}

func cleanupUsers(t *testing.T, pool *sqlx.DB, ids ...uuid.UUID) {
	t.Helper()
	ctx := context.Background()
	for _, id := range ids {
		pool.ExecContext(ctx, `DELETE FROM paliad.users WHERE id = $1`, id)
		pool.ExecContext(ctx, `DELETE FROM auth.users WHERE id = $1`, id)
	}
}

func TestUserService_Create_Valid(t *testing.T) {
	users, pool, done := setupUserTest(t)
	defer done()

	// Ensure the table is empty so the bootstrap path is deterministic. The
	// first inserter becomes global_admin; this test just exercises the rest
	// of the Create shape and isn't asserting on global_role.
	pool.ExecContext(context.Background(), `DELETE FROM paliad.users`)

	id := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000001")
	seedAuthUser(t, pool, id, "first@hlc.com")
	defer cleanupUsers(t, pool, id)

	u, err := users.Create(context.Background(), id, "first@hlc.com", CreateUserInput{
		DisplayName: "  First User  ",
		Office:      "munich",
		JobTitle:    "Trainee",
	})
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if u == nil || u.ID != id {
		t.Fatalf("unexpected user: %+v", u)
	}
	if u.DisplayName != "First User" {
		t.Errorf("display_name not trimmed: %q", u.DisplayName)
	}
	if u.Office != "munich" || u.JobTitle == nil || *u.JobTitle != "Trainee" || u.Email != "first@hlc.com" {
		t.Errorf("field mismatch: %+v", u)
	}
}

func TestUserService_Create_InvalidInput(t *testing.T) {
	users, pool, done := setupUserTest(t)
	defer done()

	id := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000002")
	seedAuthUser(t, pool, id, "x@hlc.com")
	defer cleanupUsers(t, pool, id)

	cases := []struct {
		name  string
		input CreateUserInput
	}{
		{"missing display_name", CreateUserInput{Office: "munich", JobTitle: "associate"}},
		{"invalid office", CreateUserInput{DisplayName: "X", Office: "tokyo", JobTitle: "associate"}},
		{"missing job_title", CreateUserInput{DisplayName: "X", Office: "munich", JobTitle: "  "}},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if _, err := users.Create(context.Background(), id, "x@hlc.com", c.input); err == nil {
				t.Fatalf("expected error")
			}
		})
	}
}

func TestUserService_Create_DuplicateReturns409(t *testing.T) {
	users, pool, done := setupUserTest(t)
	defer done()

	id := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000003")
	seedAuthUser(t, pool, id, "dup@hlc.com")
	defer cleanupUsers(t, pool, id)

	ctx := context.Background()
	in := CreateUserInput{DisplayName: "Dup", Office: "munich", JobTitle: "associate"}
	if _, err := users.Create(ctx, id, "dup@hlc.com", in); err != nil {
		t.Fatalf("first create: %v", err)
	}
	_, err := users.Create(ctx, id, "dup@hlc.com", in)
	if !errors.Is(err, ErrUserAlreadyOnboarded) {
		t.Fatalf("expected ErrUserAlreadyOnboarded, got %v", err)
	}
}

// TestUserService_Create_BootstrapPromotesFirstUser asserts the bootstrap
// rule: when paliad.users is empty, the first inserter is silently promoted
// to global_admin. Subsequent inserters default to standard.
func TestUserService_Create_BootstrapPromotesFirstUser(t *testing.T) {
	users, pool, done := setupUserTest(t)
	defer done()
	ctx := context.Background()

	pool.ExecContext(ctx, `DELETE FROM paliad.users`)

	first := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000011")
	second := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000012")
	seedAuthUser(t, pool, first, "first@hlc.com")
	seedAuthUser(t, pool, second, "second@hlc.com")
	defer cleanupUsers(t, pool, first, second)

	u1, err := users.Create(ctx, first, "first@hlc.com", CreateUserInput{
		DisplayName: "First", Office: "munich", JobTitle: "Partner",
	})
	if err != nil {
		t.Fatalf("bootstrap user: %v", err)
	}
	if u1.GlobalRole != "global_admin" {
		t.Fatalf("first user should be global_admin, got %q", u1.GlobalRole)
	}

	u2, err := users.Create(ctx, second, "second@hlc.com", CreateUserInput{
		DisplayName: "Second", Office: "munich", JobTitle: "Associate",
	})
	if err != nil {
		t.Fatalf("second user: %v", err)
	}
	if u2.GlobalRole != "standard" {
		t.Fatalf("second user should be standard, got %q", u2.GlobalRole)
	}
}

// TestUserService_AdminUpdateUser_LastGlobalAdmin asserts the demotion guard.
func TestUserService_AdminUpdateUser_LastGlobalAdmin(t *testing.T) {
	users, pool, done := setupUserTest(t)
	defer done()
	ctx := context.Background()

	pool.ExecContext(ctx, `DELETE FROM paliad.users`)

	first := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000021")
	seedAuthUser(t, pool, first, "lonely@hlc.com")
	defer cleanupUsers(t, pool, first)

	if _, err := users.Create(ctx, first, "lonely@hlc.com", CreateUserInput{
		DisplayName: "Lonely", Office: "munich", JobTitle: "Counsel",
	}); err != nil {
		t.Fatalf("bootstrap: %v", err)
	}

	standard := "standard"
	_, err := users.AdminUpdateUser(ctx, first, AdminUpdateInput{GlobalRole: &standard})
	if !errors.Is(err, ErrLastGlobalAdmin) {
		t.Fatalf("expected ErrLastGlobalAdmin, got %v", err)
	}
}

// TestUserService_IsAdmin asserts IsAdmin reads the global_role column.
func TestUserService_IsAdmin(t *testing.T) {
	users, pool, done := setupUserTest(t)
	defer done()
	ctx := context.Background()

	pool.ExecContext(ctx, `DELETE FROM paliad.users`)

	id := uuid.MustParse("aaaaaaaa-0000-0000-0000-000000000031")
	seedAuthUser(t, pool, id, "first@hlc.com")
	defer cleanupUsers(t, pool, id)

	if _, err := users.Create(ctx, id, "first@hlc.com", CreateUserInput{
		DisplayName: "First", Office: "munich", JobTitle: "Counsel",
	}); err != nil {
		t.Fatalf("create: %v", err)
	}

	got, err := users.IsAdmin(ctx, id)
	if err != nil {
		t.Fatalf("IsAdmin: %v", err)
	}
	if !got {
		t.Fatalf("bootstrap user should pass IsAdmin")
	}
}
