package host

import (
	"context"
	"errors"
	"os"
	"reflect"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"hyapp/pkg/appcode"
	hostservice "hyapp/services/user-service/internal/service/host"
)

func TestRoleRegionMatchRequiredUsesIndexedOwnerConfigAndCache(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}
	defer db.Close()
	repository := New(db)
	ctx := appcode.WithContext(context.Background(), "tenant-global")

	mock.ExpectQuery(`SELECT base_scope, expanded_scope, region_expansion_configurable, updated_at_ms`).
		WithArgs("tenant-global", hostservice.RoleScopeSceneOrganizationExpansion).
		WillReturnRows(sqlmock.NewRows([]string{"base_scope", "expanded_scope", "region_expansion_configurable", "updated_at_ms"}).
			AddRow("global", "global", false, int64(123)))

	for attempt := 0; attempt < 2; attempt++ {
		required, err := repository.roleRegionMatchRequired(ctx)
		if err != nil {
			t.Fatalf("resolve role scope attempt %d: %v", attempt, err)
		}
		if required {
			t.Fatalf("global owner config must allow cross-region organization links")
		}
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("cache must collapse repeated owner config reads: %v", err)
	}
}

func TestRoleRegionMatchRequiredFailsClosedOnDatabaseError(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}
	defer db.Close()
	repository := New(db)
	ctx := appcode.WithContext(context.Background(), "tenant-a")
	mock.ExpectQuery(`SELECT base_scope, expanded_scope, region_expansion_configurable, updated_at_ms`).
		WithArgs("tenant-a", hostservice.RoleScopeSceneOrganizationExpansion).
		WillReturnError(errors.New("database unavailable"))

	required, err := repository.roleRegionMatchRequired(ctx)
	if err == nil || !required {
		t.Fatalf("database error must fail closed: required=%v err=%v", required, err)
	}
}

func TestRoleScopePolicyMigrationOnlyMaterializesLegacyGlobalTenant(t *testing.T) {
	body, err := os.ReadFile("../../../../deploy/mysql/migrations/022_role_scope_policies.sql")
	if err != nil {
		t.Fatalf("read role scope migration: %v", err)
	}
	sqlText := strings.ToLower(string(body))
	for _, snippet := range []string{
		"primary key (app_code, scene)",
		"'huwaa', 'organization_role_expansion', 'global', 'global'",
		"'huwaa', 'manager_operations', 'global', 'global'",
		"insert ignore into role_scope_policies",
	} {
		if !strings.Contains(sqlText, snippet) {
			t.Fatalf("role scope migration missing %q", snippet)
		}
	}
	for _, app := range []string{"lalu", "fami", "yumi", "aslan"} {
		if strings.Contains(sqlText, "'"+app+"'") {
			t.Fatalf("migration must not seed unrelated app %q", app)
		}
	}
}

func TestExternalTeamCTEEnforcesOwnerAndBDCurrentRegionWithoutDroppingBannedMembers(t *testing.T) {
	ctx := appcode.WithContext(context.Background(), "tenant-external")
	query, args := externalTeamCTE(ctx, []int64{101, 202}, 77)
	wantArgs := []any{
		int64(77),
		int64(77),
		"tenant-external",
		int64(101),
		int64(202),
		"active",
		"tenant-external",
		"active",
		"tenant-external",
		"active",
	}
	if !reflect.DeepEqual(args, wantArgs) {
		t.Fatalf("external team CTE args mismatch: got=%#v want=%#v", args, wantArgs)
	}
	for _, snippet := range []string{
		"parent_owner_user_id in (?,?)",
		"parent_leader_user_id is null",
		"scope_owner.status = 'active' and scope_owner.region_id = ?",
		"bd_user.region_id = ?",
		"m.membership_type = 'member'",
	} {
		if !strings.Contains(strings.ToLower(query), snippet) {
			t.Fatalf("external team CTE missing fail-closed boundary %q", snippet)
		}
	}
	if strings.Contains(strings.ToLower(query), "bd_user.status = 'active'") {
		t.Fatal("team traversal must retain banned/disabled BD so its owner can still manage the BD and descendants")
	}
}

func TestExternalTeamMigrationLeavesLegacyOwnerEmptyAndDeclaresQueryIndexes(t *testing.T) {
	body, err := os.ReadFile("../../../../deploy/mysql/migrations/024_external_admin_team_scope.sql")
	if err != nil {
		t.Fatalf("read external team migration: %v", err)
	}
	sqlText := strings.ToLower(string(body))
	for _, snippet := range []string{
		"idx_bd_profiles_parent_owner (app_code, parent_owner_user_id, status, user_id)",
		"idx_role_invitations_parent_owner (app_code, parent_owner_user_id, status, created_at_ms)",
		"algorithm=instant",
		"algorithm=inplace",
		"lock=none",
	} {
		if !strings.Contains(sqlText, snippet) {
			t.Fatalf("external team migration missing %q", snippet)
		}
	}
	if strings.Contains(sqlText, "set parent_owner_user_id = parent_leader_user_id") {
		t.Fatal("external migration must not backfill Lalu BD Leader ownership into the external owner column")
	}
}
