package integration

import (
	"bytes"
	"context"
	"fmt"
	"log/slog"
	"os"
	"sort"
	"testing"
	"time"

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

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/dbmigrate"
)

// TestRoleRegistrySeedParity is the regression net against seed drift: on a
// freshly migrated DB every seeded role_permissions row must be known to the
// permission catalog (exact key or catalog-covering glob), so key validation
// can never reject a live seed row as unknown.
func TestRoleRegistrySeedParity(t *testing.T) {
	databaseURL := os.Getenv("DATABASE_URL")
	if databaseURL == "" {
		t.Skip("DATABASE_URL is not set")
	}
	ctx := context.Background()

	admin, err := pgxpool.New(ctx, databaseURL)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(admin.Close)

	schema := fmt.Sprintf("role_seed_it_%d", time.Now().UnixNano())
	if _, err := admin.Exec(ctx, "DROP SCHEMA IF EXISTS "+schema+" CASCADE"); err != nil {
		t.Fatal(err)
	}
	if _, err := admin.Exec(ctx, "CREATE SCHEMA "+schema); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _, _ = admin.Exec(context.Background(), "DROP SCHEMA IF EXISTS "+schema+" CASCADE") })

	cfg, err := pgxpool.ParseConfig(databaseURL)
	if err != nil {
		t.Fatal(err)
	}
	cfg.ConnConfig.RuntimeParams["search_path"] = schema
	pool, err := pgxpool.NewWithConfig(ctx, cfg)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(pool.Close)

	log := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
	if err := dbmigrate.Run(ctx, pool, log); err != nil {
		t.Fatalf("dbmigrate.Run: %v", err)
	}

	var names []string
	rows, err := pool.Query(ctx, "SELECT name FROM roles")
	if err != nil {
		t.Fatal(err)
	}
	for rows.Next() {
		var n string
		if err := rows.Scan(&n); err != nil {
			rows.Close()
			t.Fatal(err)
		}
		names = append(names, n)
	}
	rows.Close()
	if err := rows.Err(); err != nil {
		t.Fatal(err)
	}
	sort.Strings(names)

	var wantNames []string
	for _, r := range auth.AllRoles {
		wantNames = append(wantNames, string(r))
	}
	sort.Strings(wantNames)
	if fmt.Sprint(names) != fmt.Sprint(wantNames) {
		t.Fatalf("roles table = %v, want all %d builtin roles %v", names, len(wantNames), wantNames)
	}

	seeded := map[string][]string{}
	mrows, err := pool.Query(ctx, "SELECT role, permission FROM role_permissions")
	if err != nil {
		t.Fatal(err)
	}
	for mrows.Next() {
		var role, perm string
		if err := mrows.Scan(&role, &perm); err != nil {
			mrows.Close()
			t.Fatal(err)
		}
		seeded[role] = append(seeded[role], perm)
	}
	mrows.Close()
	if err := mrows.Err(); err != nil {
		t.Fatal(err)
	}

	for role, perms := range seeded {
		for _, perm := range perms {
			if !auth.IsKnownPermission(perm) {
				t.Errorf("seeded permission %q for role %q is unknown to the catalog", perm, role)
			}
		}
	}

	var check string
	if err := pool.QueryRow(ctx, "SELECT conname FROM pg_constraint WHERE conname = 'users_role_check'").Scan(&check); err == nil {
		t.Errorf("users_role_check still exists after 000057")
	}
}
