package routes

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/bytedance/sonic"
	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/bootstrap"
	"github.com/break/junhong_cmp_fiber/internal/handler/admin"
	internalMiddleware "github.com/break/junhong_cmp_fiber/internal/middleware"
	"github.com/break/junhong_cmp_fiber/internal/model"
	shopService "github.com/break/junhong_cmp_fiber/internal/service/shop"
	shopCommissionService "github.com/break/junhong_cmp_fiber/internal/service/shop_commission"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/auth"
	"github.com/break/junhong_cmp_fiber/pkg/config"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/database"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	pkgMiddleware "github.com/break/junhong_cmp_fiber/pkg/middleware"
)

type shopTestResponse struct {
	Code int              `json:"code"`
	Msg  string           `json:"msg"`
	Data shopTestPageData `json:"data"`
}

type shopTestPageData struct {
	Items []map[string]any `json:"items"`
	Total int64            `json:"total"`
	Page  int              `json:"page"`
	Size  int              `json:"size"`
}

type shopTestEnv struct {
	app          *fiber.App
	db           *gorm.DB
	redis        *redis.Client
	tokenManager *auth.TokenManager
}

// TestShopListValidatesAllQueryParameters 验证店铺列表会完整校验所有查询参数。
func TestShopListValidatesAllQueryParameters(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{
		UserID:   uniqueTestID(),
		UserType: constants.UserTypeSuperAdmin,
		Username: "ur60-super-admin",
	})

	testCases := []struct {
		name  string
		query string
	}{
		{name: "页码小于一", query: "page=0"},
		{name: "每页数量小于一", query: "page_size=0"},
		{name: "每页数量超过上限", query: "page_size=101"},
		{name: "店铺层级非法", query: "level=8"},
		{name: "店铺状态非法", query: "status=2"},
		{name: "店铺名称过长", query: "shop_name=" + strings.Repeat("店", 101)},
		{name: "店铺编号过长", query: "shop_code=" + strings.Repeat("A", 51)},
		{name: "参数解析失败", query: "parent_id=invalid"},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			status, body := env.request(t, http.MethodGet, "/api/admin/shops?"+testCase.query, token)
			if status != http.StatusBadRequest {
				t.Fatalf("期望 HTTP 400，实际为 %d，响应：%s", status, body)
			}

			var response struct {
				Code int    `json:"code"`
				Msg  string `json:"msg"`
				Data any    `json:"data"`
			}
			if err := sonic.Unmarshal(body, &response); err != nil {
				t.Fatalf("解析响应失败：%v", err)
			}
			if response.Code != errors.CodeInvalidParam || response.Msg != "参数验证失败" || response.Data != nil {
				t.Fatalf("参数错误响应不符合契约：%s", body)
			}
			if !strings.Contains(string(body), "timestamp") {
				t.Fatalf("参数错误响应缺少时间戳：%s", body)
			}
			if strings.Contains(string(body), "validation") || strings.Contains(string(body), "strconv") {
				t.Fatalf("参数错误响应泄露底层细节：%s", body)
			}
		})
	}
}

// TestShopListUsesNormalizedAndExplicitPagination 验证默认分页归一化且显式分页保持不变。
func TestShopListUsesNormalizedAndExplicitPagination(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{
		UserID:   uniqueTestID(),
		UserType: constants.UserTypeSuperAdmin,
		Username: "ur60-super-admin",
	})

	testCases := []struct {
		name         string
		query        string
		expectedPage int
		expectedSize int
	}{
		{name: "默认分页", expectedPage: constants.DefaultPage, expectedSize: constants.DefaultPageSize},
		{name: "显式分页", query: "?page=2&page_size=7&shop_name=ur60-no-match", expectedPage: 2, expectedSize: 7},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			status, body := env.request(t, http.MethodGet, "/api/admin/shops"+testCase.query, token)
			if status != http.StatusOK {
				t.Fatalf("期望 HTTP 200，实际为 %d，响应：%s", status, body)
			}

			var response shopTestResponse
			if err := sonic.Unmarshal(body, &response); err != nil {
				t.Fatalf("解析响应失败：%v", err)
			}
			if response.Code != errors.CodeSuccess || response.Data.Page != testCase.expectedPage || response.Data.Size != testCase.expectedSize {
				t.Fatalf("分页响应不符合契约：%s", body)
			}
		})
	}
}

// TestShopListKeepsAuthenticationContract 验证店铺列表保持既有认证错误契约。
func TestShopListKeepsAuthenticationContract(t *testing.T) {
	env := newShopTestEnv(t)

	testCases := []struct {
		name  string
		token string
	}{
		{name: "未认证"},
		{name: "无效令牌", token: "ur60-invalid-token"},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			status, body := env.request(t, http.MethodGet, "/api/admin/shops", testCase.token)
			if status != http.StatusUnauthorized {
				t.Fatalf("期望 HTTP 401，实际为 %d，响应：%s", status, body)
			}
			if !strings.Contains(string(body), "timestamp") {
				t.Fatalf("认证错误响应缺少时间戳：%s", body)
			}
		})
	}
}

// TestEnterpriseCannotAccessCoreShopManagementRoutes 验证企业账号无法访问五个核心店铺管理入口。
func TestEnterpriseCannotAccessCoreShopManagementRoutes(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{
		UserID:       uniqueTestID(),
		UserType:     constants.UserTypeEnterprise,
		EnterpriseID: uniqueTestID(),
		Username:     "ur60-enterprise",
	})

	testCases := []struct {
		name   string
		method string
		path   string
	}{
		{name: "店铺列表", method: http.MethodGet, path: "/api/admin/shops"},
		{name: "创建店铺", method: http.MethodPost, path: "/api/admin/shops"},
		{name: "更新店铺", method: http.MethodPut, path: "/api/admin/shops/1"},
		{name: "删除店铺", method: http.MethodDelete, path: "/api/admin/shops/1"},
		{name: "联级查询", method: http.MethodGet, path: "/api/admin/shops/cascade"},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			status, body := env.request(t, testCase.method, testCase.path, token)
			if status != http.StatusForbidden {
				t.Fatalf("期望 HTTP 403，实际为 %d，响应：%s", status, body)
			}
			var response struct {
				Code int    `json:"code"`
				Msg  string `json:"msg"`
				Data any    `json:"data"`
			}
			if err := sonic.Unmarshal(body, &response); err != nil {
				t.Fatalf("解析响应失败：%v", err)
			}
			if response.Code != errors.CodeForbidden || response.Msg != constants.ShopManagementForbiddenMessage || response.Data != nil {
				t.Fatalf("企业账号禁止响应不符合契约：%s", body)
			}
			if !strings.Contains(string(body), "timestamp") {
				t.Fatalf("企业账号禁止响应缺少时间戳：%s", body)
			}
		})
	}
}

// TestCoreShopRestrictionDoesNotAffectOtherShopRoutes 验证核心店铺权限限制不影响其他店铺前缀路由。
func TestCoreShopRestrictionDoesNotAffectOtherShopRoutes(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{
		UserID:       uniqueTestID(),
		UserType:     constants.UserTypeEnterprise,
		EnterpriseID: uniqueTestID(),
		Username:     "ur60-enterprise",
	})

	paths := []string{
		"/api/admin/shops/1/roles",
		"/api/admin/shops/fund-summary",
		"/api/admin/shops/1/withdrawal-requests",
		"/api/admin/shops/1/commission-records",
		"/api/admin/shops/1/main-wallet/transactions",
	}
	for _, path := range paths {
		status, body := env.request(t, http.MethodGet, path, token)
		if status == http.StatusForbidden && strings.Contains(string(body), constants.ShopManagementForbiddenMessage) {
			t.Fatalf("核心店铺管理拦截误伤独立路由 %s：%s", path, body)
		}
	}
}

// TestNonEnterpriseAccountsKeepCoreShopListAccess 验证非企业账号保留核心店铺列表访问能力。
func TestNonEnterpriseAccountsKeepCoreShopListAccess(t *testing.T) {
	env := newShopTestEnv(t)
	testCases := []struct {
		name     string
		userType int
		shopID   uint
	}{
		{name: "超级管理员", userType: constants.UserTypeSuperAdmin},
		{name: "平台账号", userType: constants.UserTypePlatform},
		{name: "代理账号", userType: constants.UserTypeAgent, shopID: 1},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			token := env.newToken(t, auth.TokenInfo{
				UserID:   uniqueTestID(),
				UserType: testCase.userType,
				ShopID:   testCase.shopID,
				Username: "ur60-allowed-user",
			})
			status, body := env.request(t, http.MethodGet, "/api/admin/shops?page=1&page_size=1", token)
			if status != http.StatusOK {
				t.Fatalf("期望保留列表访问能力，实际 HTTP %d，响应：%s", status, body)
			}
		})
	}
}

// TestShopListFiltersContactPhoneExactly 验证联系电话精确匹配、AND 组合、重复号码、排序及软删除语义。
func TestShopListFiltersContactPhoneExactly(t *testing.T) {
	env := newShopTestEnv(t)
	phone := "13800000000"
	parent := env.createShop(t, "UR60父店", phone, nil, 1, time.Now().Add(-4*time.Minute))
	newer := env.createShop(t, "UR60目标新店", phone, &parent.ID, 2, time.Now().Add(-time.Minute))
	older := env.createShop(t, "UR60目标旧店", phone, &parent.ID, 2, time.Now().Add(-2*time.Minute))
	env.createShop(t, "UR60相似号码", "13800000001", &parent.ID, 2, time.Now())
	deleted := env.createShop(t, "UR60软删除店", phone, &parent.ID, 2, time.Now().Add(time.Minute))
	if err := env.db.Delete(deleted).Error; err != nil {
		t.Fatalf("软删除测试店铺失败：%v", err)
	}

	token := env.newToken(t, auth.TokenInfo{
		UserID:   uniqueTestID(),
		UserType: constants.UserTypeSuperAdmin,
		Username: "ur60-super-admin",
	})
	status, body := env.request(t, http.MethodGet, "/api/admin/shops?contact_phone="+phone+"&shop_name=UR60目标&parent_id="+strconv.FormatUint(uint64(parent.ID), 10)+"&level=2&status=1", token)
	if status != http.StatusOK {
		t.Fatalf("联系电话组合查询失败，HTTP %d：%s", status, body)
	}

	var response shopTestResponse
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析联系电话查询响应失败：%v", err)
	}
	if response.Data.Total != 2 || len(response.Data.Items) != 2 {
		t.Fatalf("联系电话组合查询应返回两条可见记录：%s", body)
	}
	if uint(response.Data.Items[0]["id"].(float64)) != newer.ID || uint(response.Data.Items[1]["id"].(float64)) != older.ID {
		t.Fatalf("联系电话查询未保持 created_at DESC：%s", body)
	}

	status, body = env.request(t, http.MethodGet, "/api/admin/shops?contact_phone=13800000001&shop_code="+newer.ShopCode, token)
	if status != http.StatusOK {
		t.Fatalf("联系电话精确性查询失败，HTTP %d：%s", status, body)
	}
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析联系电话精确性响应失败：%v", err)
	}
	if response.Data.Total != 0 {
		t.Fatalf("联系电话必须与其他条件按 AND 精确匹配：%s", body)
	}

	platformToken := env.newToken(t, auth.TokenInfo{
		UserID:   uniqueTestID(),
		UserType: constants.UserTypePlatform,
		Username: "ur60-platform",
	})
	status, body = env.request(t, http.MethodGet, "/api/admin/shops?contact_phone="+phone+"&shop_name=UR60目标", platformToken)
	if status != http.StatusOK {
		t.Fatalf("平台账号联系电话查询失败，HTTP %d：%s", status, body)
	}
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析平台账号联系电话响应失败：%v", err)
	}
	if response.Data.Total != 2 {
		t.Fatalf("平台账号联系电话查询结果不符合契约：%s", body)
	}
}

// TestShopListContactPhoneKeepsEmptyAndPaginationSemantics 验证空电话、空结果和显式分页契约。
func TestShopListContactPhoneKeepsEmptyAndPaginationSemantics(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{UserID: uniqueTestID(), UserType: constants.UserTypeSuperAdmin, Username: "ur60-super-admin"})

	status, body := env.request(t, http.MethodGet, "/api/admin/shops?contact_phone=&page=2&page_size=3", token)
	if status != http.StatusOK {
		t.Fatalf("空联系电话不应改变列表语义，HTTP %d：%s", status, body)
	}
	var response shopTestResponse
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析空联系电话响应失败：%v", err)
	}
	if response.Data.Page != 2 || response.Data.Size != 3 {
		t.Fatalf("联系电话筛选不应重置显式分页：%s", body)
	}

	status, body = env.request(t, http.MethodGet, "/api/admin/shops?contact_phone=19999999999", token)
	if status != http.StatusOK {
		t.Fatalf("无匹配联系电话应返回成功空分页，HTTP %d：%s", status, body)
	}
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析空结果响应失败：%v", err)
	}
	if response.Data.Total != 0 || len(response.Data.Items) != 0 {
		t.Fatalf("无匹配联系电话响应不符合空分页契约：%s", body)
	}
}

// TestShopListRejectsInvalidContactPhoneBeforeQuery 验证非法联系电话在执行店铺查询前被拒绝。
func TestShopListRejectsInvalidContactPhoneBeforeQuery(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{UserID: uniqueTestID(), UserType: constants.UserTypeSuperAdmin, Username: "ur60-super-admin"})
	var shopQueryCount atomic.Int64
	callbackName := fmt.Sprintf("ur60:count_shop_queries:%d", time.Now().UnixNano())
	if err := env.db.Callback().Query().Before("gorm:query").Register(callbackName, func(db *gorm.DB) {
		if db.Statement != nil && db.Statement.Table == (model.Shop{}).TableName() {
			shopQueryCount.Add(1)
		}
	}); err != nil {
		t.Fatalf("注册店铺查询计数回调失败：%v", err)
	}
	t.Cleanup(func() { _ = env.db.Callback().Query().Remove(callbackName) })

	invalidPhones := []string{
		"1380000000", "138000000000", "1380000000A", "+8613800000000",
		"138-0000-0000", " 13800000000", "１３８００００００００",
	}
	for _, phone := range invalidPhones {
		shopQueryCount.Store(0)
		status, body := env.request(t, http.MethodGet, "/api/admin/shops?contact_phone="+url.QueryEscape(phone), token)
		if status != http.StatusBadRequest || shopQueryCount.Load() != 0 {
			t.Fatalf("非法联系电话应在查询前返回 400，phone=%q, queries=%d, body=%s", phone, shopQueryCount.Load(), body)
		}
	}
}

// TestAgentContactPhoneSearchKeepsShopScope 验证代理账号无法通过联系电话越过店铺层级范围。
func TestAgentContactPhoneSearchKeepsShopScope(t *testing.T) {
	env := newShopTestEnv(t)
	phone := "13700000000"
	root := env.createShop(t, "UR60代理根店", phone, nil, 1, time.Now().Add(-3*time.Minute))
	child := env.createShop(t, "UR60代理下级", phone, &root.ID, 2, time.Now().Add(-2*time.Minute))
	env.createShop(t, "UR60范围外店铺", phone, nil, 1, time.Now().Add(-time.Minute))
	token := env.newToken(t, auth.TokenInfo{UserID: uniqueTestID(), UserType: constants.UserTypeAgent, ShopID: root.ID, Username: "ur60-agent"})

	status, body := env.request(t, http.MethodGet, "/api/admin/shops?contact_phone="+phone, token)
	if status != http.StatusOK {
		t.Fatalf("代理联系电话查询失败，HTTP %d：%s", status, body)
	}
	var response shopTestResponse
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析代理联系电话响应失败：%v", err)
	}
	if response.Data.Total != 2 {
		t.Fatalf("代理联系电话查询应只返回自身及下级：%s", body)
	}
	returned := map[uint]bool{}
	for _, item := range response.Data.Items {
		returned[uint(item["id"].(float64))] = true
	}
	if !returned[root.ID] || !returned[child.ID] {
		t.Fatalf("代理联系电话查询缺少范围内店铺：%s", body)
	}
}

// TestShopListDatabaseFailureIsSanitized 验证数据库错误通过统一 500 响应脱敏。
func TestShopListDatabaseFailureIsSanitized(t *testing.T) {
	env := newShopTestEnv(t)
	token := env.newToken(t, auth.TokenInfo{UserID: uniqueTestID(), UserType: constants.UserTypeSuperAdmin, Username: "ur60-super-admin"})
	sqlDB, err := env.db.DB()
	if err != nil {
		t.Fatalf("获取测试数据库连接失败：%v", err)
	}
	if err := sqlDB.Close(); err != nil {
		t.Fatalf("关闭测试数据库连接失败：%v", err)
	}

	status, body := env.request(t, http.MethodGet, "/api/admin/shops?contact_phone=13800000000", token)
	if status != http.StatusInternalServerError {
		t.Fatalf("数据库失败应返回 HTTP 500，实际 %d：%s", status, body)
	}
	var response struct {
		Code int    `json:"code"`
		Msg  string `json:"msg"`
		Data any    `json:"data"`
	}
	if err := sonic.Unmarshal(body, &response); err != nil {
		t.Fatalf("解析数据库错误响应失败：%v", err)
	}
	if response.Code != errors.CodeInternalError || response.Msg != "内部服务器错误" || response.Data != nil {
		t.Fatalf("数据库错误响应不符合脱敏契约：%s", body)
	}
	for _, leaked := range []string{"sql", "postgres", "host", "driver", "database is closed"} {
		if strings.Contains(strings.ToLower(string(body)), leaked) {
			t.Fatalf("数据库错误响应泄露底层信息 %q：%s", leaked, body)
		}
	}
}

// TestShopContactPhoneIndexDefinition 验证联系电话索引的类型、唯一性和部分条件。
func TestShopContactPhoneIndexDefinition(t *testing.T) {
	env := newShopTestEnv(t)
	var indexCount int64
	if err := env.db.Raw(`
		SELECT COUNT(*)
		FROM pg_class
		WHERE relname = ?
	`, "idx_shop_contact_phone").Scan(&indexCount).Error; err != nil {
		t.Fatalf("查询联系电话索引数量失败：%v", err)
	}
	if os.Getenv("UR60_EXPECT_INDEX_ABSENT") == "1" {
		if indexCount != 0 {
			t.Fatalf("回滚后联系电话索引仍然存在")
		}
		return
	}
	if indexCount != 1 {
		t.Fatalf("联系电话索引数量异常：%d", indexCount)
	}

	var index struct {
		AccessMethod string `gorm:"column:access_method"`
		IsUnique     bool   `gorm:"column:is_unique"`
		Definition   string `gorm:"column:definition"`
		Predicate    string `gorm:"column:predicate"`
	}
	err := env.db.Raw(`
		SELECT am.amname AS access_method,
		       ix.indisunique AS is_unique,
		       pg_get_indexdef(ix.indexrelid) AS definition,
		       pg_get_expr(ix.indpred, ix.indrelid) AS predicate
		FROM pg_index ix
		JOIN pg_class i ON i.oid = ix.indexrelid
		JOIN pg_am am ON am.oid = i.relam
		WHERE i.relname = ?
	`, "idx_shop_contact_phone").Scan(&index).Error
	if err != nil {
		t.Fatalf("查询联系电话索引定义失败：%v", err)
	}
	if index.AccessMethod != "btree" || index.IsUnique || !strings.Contains(index.Definition, "contact_phone") || !strings.Contains(index.Predicate, "deleted_at IS NULL") {
		t.Fatalf("联系电话索引定义不符合契约：%+v", index)
	}
}

func newShopTestEnv(t *testing.T) *shopTestEnv {
	t.Helper()
	if os.Getenv("JUNHONG_DATABASE_HOST") == "" || os.Getenv("JUNHONG_REDIS_ADDRESS") == "" {
		t.Skip("未加载 .env.local，跳过依赖真实 PostgreSQL 和 Redis 的店铺 HTTP 集成测试")
	}

	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("加载测试配置失败：%v", err)
	}
	logger := zap.NewNop()
	db, err := database.InitPostgreSQL(&cfg.Database, logger)
	if err != nil {
		t.Fatalf("连接 PostgreSQL 失败：%v", err)
	}
	redisClient, err := database.NewRedisClient(database.RedisConfig{
		Address:      cfg.Redis.Address + ":" + strconv.Itoa(cfg.Redis.Port),
		Password:     cfg.Redis.Password,
		DB:           cfg.Redis.DB,
		PoolSize:     cfg.Redis.PoolSize,
		MinIdleConns: cfg.Redis.MinIdleConns,
		DialTimeout:  cfg.Redis.DialTimeout,
		ReadTimeout:  cfg.Redis.ReadTimeout,
		WriteTimeout: cfg.Redis.WriteTimeout,
	}, logger)
	if err != nil {
		t.Fatalf("连接 Redis 失败：%v", err)
	}

	t.Cleanup(func() {
		_ = redisClient.Close()
		if sqlDB, dbErr := db.DB(); dbErr == nil {
			_ = sqlDB.Close()
		}
	})

	shopStore := postgres.NewShopStore(db, redisClient)
	service := shopService.New(shopStore, nil, nil, nil)
	shopCommission := shopCommissionService.New(
		shopStore,
		postgres.NewAccountStore(db, redisClient),
		postgres.NewAgentWalletStore(db, redisClient),
		postgres.NewCommissionWithdrawalRequestStore(db, redisClient),
		postgres.NewCommissionWithdrawalSettingStore(db, redisClient),
		postgres.NewCommissionRecordStore(db, redisClient),
		postgres.NewAgentWalletTransactionStore(db, redisClient),
		db,
		logger,
	)
	tokenManager := auth.NewTokenManager(redisClient, cfg.JWT.AccessTokenTTL, cfg.JWT.RefreshTokenTTL)
	authMiddleware := pkgMiddleware.Auth(pkgMiddleware.AuthConfig{
		TokenValidator: func(token string) (*pkgMiddleware.UserContextInfo, error) {
			info, validateErr := tokenManager.ValidateAccessToken(context.Background(), token)
			if validateErr != nil {
				return nil, errors.New(errors.CodeInvalidToken, "认证令牌无效或已过期")
			}
			return &pkgMiddleware.UserContextInfo{
				UserID:       info.UserID,
				UserType:     info.UserType,
				Username:     info.Username,
				ShopID:       info.ShopID,
				EnterpriseID: info.EnterpriseID,
			}, nil
		},
		ShopStore: shopStore,
	})

	app := fiber.New(fiber.Config{
		JSONEncoder:  sonic.Marshal,
		JSONDecoder:  sonic.Unmarshal,
		ErrorHandler: internalMiddleware.ErrorHandler(logger),
	})
	RegisterAdminRoutes(app.Group("/api/admin"), &bootstrap.Handlers{
		Shop:           admin.NewShopHandler(service, validator.New()),
		ShopRole:       admin.NewShopRoleHandler(service),
		ShopCommission: admin.NewShopCommissionHandler(shopCommission),
	}, &bootstrap.Middlewares{AdminAuth: authMiddleware}, nil, "/api/admin")

	return &shopTestEnv{app: app, db: db, redis: redisClient, tokenManager: tokenManager}
}

func (e *shopTestEnv) createShop(t *testing.T, name, phone string, parentID *uint, level int, createdAt time.Time) *model.Shop {
	t.Helper()
	shop := &model.Shop{
		ShopName:     name,
		ShopCode:     fmt.Sprintf("UR60-%d", time.Now().UnixNano()),
		ParentID:     parentID,
		Level:        level,
		ContactPhone: phone,
		Status:       constants.ShopStatusEnabled,
	}
	shop.CreatedAt = createdAt
	shop.UpdatedAt = createdAt
	shop.Creator = uniqueTestID()
	shop.Updater = shop.Creator
	if err := e.db.Create(shop).Error; err != nil {
		t.Fatalf("创建测试店铺失败：%v", err)
	}
	t.Cleanup(func() { _ = e.db.Unscoped().Delete(shop).Error })
	return shop
}

func (e *shopTestEnv) newToken(t *testing.T, info auth.TokenInfo) string {
	t.Helper()
	accessToken, refreshToken, err := e.tokenManager.GenerateTokenPair(context.Background(), &info)
	if err != nil {
		t.Fatalf("创建测试令牌失败：%v", err)
	}
	t.Cleanup(func() {
		_ = e.tokenManager.RevokeToken(context.Background(), accessToken)
		_ = e.tokenManager.RevokeToken(context.Background(), refreshToken)
		_ = e.redis.Del(context.Background(), constants.RedisUserTokensKey(info.UserID)).Err()
	})
	return accessToken
}

func (e *shopTestEnv) request(t *testing.T, method, path, token string) (int, []byte) {
	t.Helper()
	request, err := http.NewRequest(method, path, nil)
	if err != nil {
		t.Fatalf("创建 HTTP 请求失败：%v", err)
	}
	if token != "" {
		request.Header.Set(fiber.HeaderAuthorization, "Bearer "+token)
	}
	response, err := e.app.Test(request, -1)
	if err != nil {
		t.Fatalf("执行 HTTP 请求失败：%v", err)
	}
	defer response.Body.Close()
	body, err := io.ReadAll(response.Body)
	if err != nil {
		t.Fatalf("读取 HTTP 响应失败：%v", err)
	}
	return response.StatusCode, body
}

func uniqueTestID() uint {
	return uint(time.Now().UnixNano() % 1_000_000_000)
}
