package bootstrap

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/testsupport"
	"context"
	"log"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/testcontainers/testcontainers-go"
	postgres_module "github.com/testcontainers/testcontainers-go/modules/postgres"
	"github.com/testcontainers/testcontainers-go/wait"
	gorm_postgres "gorm.io/driver/postgres"
	"gorm.io/gorm"
)

func TestSanitizeLegacyUserMetadataRemovesClassificationFlags(t *testing.T) {
	db := openBootstrapPostgresTestDB(t)
	if err := db.AutoMigrate(&domain.User{}); err != nil {
		t.Fatalf("failed to migrate users table: %v", err)
	}

	user := domain.User{
		ID:     "10000000-0000-0000-0000-000000000001",
		Email:  "legacy@example.com",
		Name:   "Legacy User",
		Role:   domain.RoleUser,
		Status: domain.UserStatusActive,
		Metadata: domain.JSONMap{
			"hanmacFamily": true,
			"userType":     "hanmac",
			"employeeId":   "E001",
			"nested": map[string]any{
				"userType": "must stay nested",
			},
		},
	}
	if err := db.Create(&user).Error; err != nil {
		t.Fatalf("failed to create user: %v", err)
	}

	if err := SanitizeLegacyUserMetadata(db); err != nil {
		t.Fatalf("SanitizeLegacyUserMetadata returned error: %v", err)
	}
	if err := SanitizeLegacyUserMetadata(db); err != nil {
		t.Fatalf("SanitizeLegacyUserMetadata must be idempotent: %v", err)
	}

	var got domain.User
	if err := db.First(&got, "id = ?", user.ID).Error; err != nil {
		t.Fatalf("failed to load sanitized user: %v", err)
	}
	if _, ok := got.Metadata["hanmacFamily"]; ok {
		t.Fatalf("hanmacFamily must be removed from metadata: %#v", got.Metadata)
	}
	if _, ok := got.Metadata["userType"]; ok {
		t.Fatalf("userType must be removed from metadata: %#v", got.Metadata)
	}
	if got.Metadata["employeeId"] != "E001" {
		t.Fatalf("employeeId = %#v, want E001", got.Metadata["employeeId"])
	}
	nested, ok := got.Metadata["nested"].(map[string]any)
	if !ok || nested["userType"] != "must stay nested" {
		t.Fatalf("nested metadata must be preserved: %#v", got.Metadata["nested"])
	}
}

func TestCanonicalizeLegacyUserStatuses(t *testing.T) {
	db := openBootstrapPostgresTestDB(t)
	if err := db.AutoMigrate(&domain.User{}); err != nil {
		t.Fatalf("failed to migrate users table: %v", err)
	}

	users := []domain.User{
		{ID: "11000000-0000-0000-0000-000000000001", Email: "inactive@example.com", Name: "Inactive", Role: domain.RoleUser, Status: "inactive"},
		{ID: "11000000-0000-0000-0000-000000000002", Email: "leave@example.com", Name: "Leave", Role: domain.RoleUser, Status: "leave_of_absence"},
		{ID: "11000000-0000-0000-0000-000000000003", Email: "baron-only@example.com", Name: "Baron Only", Role: domain.RoleUser, Status: "baron_only"},
		{ID: "11000000-0000-0000-0000-000000000004", Email: "active@example.com", Name: "Active", Role: domain.RoleUser, Status: domain.UserStatusActive},
	}
	if err := db.Create(&users).Error; err != nil {
		t.Fatalf("failed to create users: %v", err)
	}

	if err := CanonicalizeLegacyUserStatuses(db); err != nil {
		t.Fatalf("CanonicalizeLegacyUserStatuses returned error: %v", err)
	}
	if err := CanonicalizeLegacyUserStatuses(db); err != nil {
		t.Fatalf("CanonicalizeLegacyUserStatuses must be idempotent: %v", err)
	}

	got := map[string]string{}
	var loaded []domain.User
	if err := db.Find(&loaded).Error; err != nil {
		t.Fatalf("failed to load users: %v", err)
	}
	for _, user := range loaded {
		got[user.Email] = user.Status
	}

	if got["inactive@example.com"] != domain.UserStatusPreboarding {
		t.Fatalf("inactive status = %q, want %q", got["inactive@example.com"], domain.UserStatusPreboarding)
	}
	if got["leave@example.com"] != domain.UserStatusTemporaryLeave {
		t.Fatalf("leave status = %q, want %q", got["leave@example.com"], domain.UserStatusTemporaryLeave)
	}
	if got["baron-only@example.com"] != domain.UserStatusBaronGuest {
		t.Fatalf("baron_only status = %q, want %q", got["baron-only@example.com"], domain.UserStatusBaronGuest)
	}
	if got["active@example.com"] != domain.UserStatusActive {
		t.Fatalf("active status = %q, want %q", got["active@example.com"], domain.UserStatusActive)
	}
}

func TestRunSanitizesLegacyUserMetadata(t *testing.T) {
	db := openBootstrapPostgresTestDB(t)
	if err := db.AutoMigrate(&domain.User{}); err != nil {
		t.Fatalf("failed to migrate users table: %v", err)
	}

	user := domain.User{
		ID:     "20000000-0000-0000-0000-000000000001",
		Email:  "run-legacy@example.com",
		Name:   "Run Legacy User",
		Role:   domain.RoleUser,
		Status: domain.UserStatusActive,
		Metadata: domain.JSONMap{
			"hanmacFamily": true,
			"userType":     "external",
			"employeeId":   "E002",
		},
	}
	if err := db.Create(&user).Error; err != nil {
		t.Fatalf("failed to create user: %v", err)
	}

	dir := t.TempDir()
	path := filepath.Join(dir, "seed-tenant.csv")
	csv := "id,name,type,parent_tenant_slug,slug,memo,email_domain\n" +
		"30000000-0000-0000-0000-000000000001,Seed Root,COMPANY_GROUP,,seed-root,seed root,\n"
	if err := os.WriteFile(path, []byte(csv), 0o600); err != nil {
		t.Fatalf("failed to write seed csv: %v", err)
	}
	t.Setenv(seedTenantCSVPathEnv, path)

	if err := Run(db); err != nil {
		t.Fatalf("Run returned error: %v", err)
	}

	var got domain.User
	if err := db.First(&got, "id = ?", user.ID).Error; err != nil {
		t.Fatalf("failed to load sanitized user: %v", err)
	}
	if _, ok := got.Metadata["hanmacFamily"]; ok {
		t.Fatalf("Run must remove hanmacFamily from metadata: %#v", got.Metadata)
	}
	if _, ok := got.Metadata["userType"]; ok {
		t.Fatalf("Run must remove userType from metadata: %#v", got.Metadata)
	}
	if got.Metadata["employeeId"] != "E002" {
		t.Fatalf("employeeId = %#v, want E002", got.Metadata["employeeId"])
	}
}

func openBootstrapPostgresTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	if !testsupport.DockerAvailable() {
		t.Skip("Docker provider is unavailable in this environment")
	}

	ctx := context.Background()
	postgresContainer, err := postgres_module.Run(ctx,
		"postgres:16-alpine",
		postgres_module.WithDatabase("testdb"),
		postgres_module.WithUsername("user"),
		postgres_module.WithPassword("password"),
		testcontainers.WithWaitStrategy(
			wait.ForLog("database system is ready to accept connections").
				WithOccurrence(2).
				WithStartupTimeout(30*time.Second),
		),
	)
	if err != nil {
		t.Fatalf("failed to start postgres container: %v", err)
	}
	t.Cleanup(func() {
		if err := postgresContainer.Terminate(ctx); err != nil {
			log.Printf("failed to terminate postgres container: %v", err)
		}
	})

	connStr, err := postgresContainer.ConnectionString(ctx, "sslmode=disable")
	if err != nil {
		t.Fatalf("failed to get postgres connection string: %v", err)
	}
	db, err := gorm.Open(gorm_postgres.Open(connStr), &gorm.Config{})
	if err != nil {
		t.Fatalf("failed to open postgres connection: %v", err)
	}
	return db
}
