package routes

import (
	"context"
	stderrors "errors"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/bytedance/sonic"
	"github.com/gofiber/fiber/v2"
	"go.uber.org/zap"
	"gorm.io/gorm"

	systemConfigApp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig"
	"github.com/break/junhong_cmp_fiber/internal/handler/admin"
	systemConfigInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
	internalMiddleware "github.com/break/junhong_cmp_fiber/internal/middleware"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	systemConfigQuery "github.com/break/junhong_cmp_fiber/internal/query/systemconfig"
	"github.com/break/junhong_cmp_fiber/internal/testutil"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
)

type configAuditWriter struct {
	fail bool
}

func (w *configAuditWriter) WriteConfigChange(_ context.Context, tx *gorm.DB, audit systemConfigApp.ChangeAudit) error {
	if w.fail {
		return stderrors.New("测试审计写入失败")
	}
	before, _ := sonic.Marshal(audit.BeforeData)
	after, _ := sonic.Marshal(audit.AfterData)
	return tx.Exec(`INSERT INTO test_system_config_audit
		(config_key, operator_id, request_id, before_data, after_data)
		VALUES (?, ?, ?, ?::jsonb, ?::jsonb)`, audit.ConfigKey, audit.OperatorID, audit.RequestID, string(before), string(after)).Error
}

type recordingAlerts struct {
	mu    sync.Mutex
	codes []string
}

func (a *recordingAlerts) Warn(_ context.Context, code, _, _, _ string) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.codes = append(a.codes, code)
}

type failingCache struct{}

func (failingCache) Get(context.Context, string) (string, error) {
	return "", stderrors.New("缓存不可用")
}
func (failingCache) Set(context.Context, string, string, time.Duration) error {
	return stderrors.New("缓存不可用")
}
func (failingCache) Delete(context.Context, string) error { return stderrors.New("缓存不可用") }

func TestSystemConfigFiberListAndUpdateUseRealPostgresAndRedis(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	redisClient := testutil.NewRedisClient(t)
	testutil.CreateTemporarySystemConfigTable(t, db)
	createTemporarySystemConfigAuditTable(t, db)
	registry := newSystemConfigTestRegistry(t)
	cache := systemConfigInfra.NewRedisCache(redisClient)
	alerts := &recordingAlerts{}
	reader := systemConfigInfra.NewReader(db, registry, cache, alerts)
	handler := admin.NewSystemConfigHandler(
		systemConfigQuery.NewListQuery(reader),
		systemConfigApp.NewUpdateService(db, registry, cache, &configAuditWriter{}, alerts, nil),
	)

	if err := db.Create(&model.SystemConfig{
		ConfigKey: "legacy.unknown.value", ConfigValue: "legacy", ValueType: constants.SystemConfigTypeString,
		Module: "legacy", Description: "未注册遗留配置", IsSensitive: false,
	}).Error; err != nil {
		t.Fatalf("准备未注册配置失败：%v", err)
	}
	if err := redisClient.Set(context.Background(), constants.RedisSystemConfigKey("foundation.demo.int"), "7", constants.SystemConfigCacheTTL).Err(); err != nil {
		t.Fatalf("准备 Redis 配置缓存失败：%v", err)
	}
	t.Cleanup(func() {
		for _, definition := range registry.List() {
			_ = redisClient.Del(context.Background(), constants.RedisSystemConfigKey(definition.Key)).Err()
		}
	})

	app := newSystemConfigTestApp(handler, constants.UserTypeSuperAdmin)
	listResponse, err := app.Test(httptest.NewRequest("GET", "/api/admin/system-configs?module=foundation&page=1&page_size=20", nil))
	if err != nil {
		t.Fatalf("查询系统配置接口失败：%v", err)
	}
	if listResponse.StatusCode != fiber.StatusOK {
		t.Fatalf("查询系统配置状态码错误：%d", listResponse.StatusCode)
	}
	var listBody struct {
		Code int                          `json:"code"`
		Data dto.SystemConfigListResponse `json:"data"`
	}
	if err := sonic.ConfigDefault.NewDecoder(listResponse.Body).Decode(&listBody); err != nil {
		t.Fatalf("解析系统配置列表响应失败：%v", err)
	}
	_ = listResponse.Body.Close()
	if listBody.Code != 0 || listBody.Data.Total != 4 || len(listBody.Data.List) != 4 {
		t.Fatalf("四种注册类型未完整返回：%+v", listBody)
	}
	for _, item := range listBody.Data.List {
		if item.Sensitive && item.Value != "[已配置]" {
			t.Fatalf("敏感配置未脱敏：%+v", item)
		}
	}

	updateRequest := httptest.NewRequest("PUT", "/api/admin/system-configs/foundation.demo.int", strings.NewReader(`{"value":"8"}`))
	updateRequest.Header.Set("Content-Type", "application/json")
	updateResponse, err := app.Test(updateRequest)
	if err != nil {
		t.Fatalf("更新系统配置接口失败：%v", err)
	}
	if updateResponse.StatusCode != fiber.StatusOK {
		t.Fatalf("更新系统配置状态码错误：%d", updateResponse.StatusCode)
	}
	_ = updateResponse.Body.Close()
	var stored model.SystemConfig
	if err := db.Where("config_key = ?", "foundation.demo.int").First(&stored).Error; err != nil || stored.ConfigValue != "8" {
		t.Fatalf("系统配置数据库事实错误：%v，记录：%+v", err, stored)
	}
	var auditCount int64
	if err := db.Table("test_system_config_audit").Where("config_key = ? AND operator_id = ?", stored.ConfigKey, 7).Count(&auditCount).Error; err != nil || auditCount != 1 {
		t.Fatalf("系统配置审计事实错误：%v，数量：%d", err, auditCount)
	}
	if exists, err := redisClient.Exists(context.Background(), constants.RedisSystemConfigKey(stored.ConfigKey)).Result(); err != nil || exists != 0 {
		t.Fatalf("更新后缓存未失效：%v，存在：%d", err, exists)
	}
}

func TestSystemConfigPermissionsValidationRollbackAndCacheFailure(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	testutil.CreateTemporarySystemConfigTable(t, db)
	createTemporarySystemConfigAuditTable(t, db)
	registry := newSystemConfigTestRegistry(t)
	alerts := &recordingAlerts{}
	reader := systemConfigInfra.NewReader(db, registry, failingCache{}, alerts)
	service := systemConfigApp.NewUpdateService(db, registry, failingCache{}, &configAuditWriter{}, alerts, nil)
	handler := admin.NewSystemConfigHandler(systemConfigQuery.NewListQuery(reader), service)

	forbiddenApp := newSystemConfigTestApp(handler, constants.UserTypePlatform)
	response, err := forbiddenApp.Test(httptest.NewRequest("GET", "/api/admin/system-configs", nil))
	if err != nil {
		t.Fatalf("执行无权限查询失败：%v", err)
	}
	if response.StatusCode != fiber.StatusForbidden {
		t.Fatalf("权限不足必须返回 403，得到：%d", response.StatusCode)
	}
	_ = response.Body.Close()

	ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{UserID: 7, UserType: constants.UserTypeSuperAdmin})
	invalidCases := []struct {
		key   string
		value string
	}{
		{key: "foundation.demo.int", value: "not-int"},
		{key: "foundation.demo.bool", value: "yes"},
		{key: "foundation.demo.json", value: "{"},
		{key: "foundation.unknown.value", value: "x"},
		{key: "foundation.demo.readonly", value: "x"},
	}
	for _, item := range invalidCases {
		if _, err := service.Execute(ctx, item.key, dto.UpdateSystemConfigRequest{Value: item.value}); err == nil {
			t.Fatalf("非法配置更新应失败：%s=%s", item.key, item.value)
		}
	}

	failClosed := systemConfigApp.NewUpdateService(db, registry, failingCache{}, &configAuditWriter{fail: true}, alerts, nil)
	if _, err := failClosed.Execute(ctx, "foundation.demo.string", dto.UpdateSystemConfigRequest{Value: "must-rollback"}); err == nil {
		t.Fatal("审计写入失败时配置事务必须回滚")
	}
	var count int64
	if err := db.Model(&model.SystemConfig{}).Where("config_key = ?", "foundation.demo.string").Count(&count).Error; err != nil || count != 0 {
		t.Fatalf("审计失败后配置事实未回滚：%v，数量：%d", err, count)
	}

	if _, err := service.Execute(ctx, "foundation.demo.string", dto.UpdateSystemConfigRequest{Value: "committed"}); err != nil {
		t.Fatalf("缓存失效失败不应回滚数据库事实：%v", err)
	}
	if len(alerts.codes) == 0 {
		t.Fatal("缓存故障必须产生可观察告警")
	}
}

func newSystemConfigTestRegistry(t *testing.T) *systemConfigInfra.Registry {
	t.Helper()
	registry := systemConfigInfra.NewRegistry()
	minimum, maximum := int64(1), int64(10)
	definitions := []systemConfigInfra.Definition{
		{Key: "foundation.demo.string", Module: "foundation", ValueType: constants.SystemConfigTypeString, DefaultValue: "default", Description: "字符串示例", Control: "input"},
		{Key: "foundation.demo.int", Module: "foundation", ValueType: constants.SystemConfigTypeInt, DefaultValue: "5", Description: "整数示例", Control: "number", Min: &minimum, Max: &maximum},
		{Key: "foundation.demo.bool", Module: "foundation", ValueType: constants.SystemConfigTypeBool, DefaultValue: "true", Description: "布尔示例", Control: "switch"},
		{Key: "foundation.demo.json", Module: "foundation", ValueType: constants.SystemConfigTypeJSON, DefaultValue: `{"enabled":true}`, Description: "JSON 示例", Control: "structured", Sensitive: true},
		{Key: "foundation.demo.readonly", Module: "foundation-readonly", ValueType: constants.SystemConfigTypeString, DefaultValue: "fixed", Description: "只读示例", Control: "readonly", Readonly: true},
	}
	for _, definition := range definitions {
		if err := registry.Register(definition); err != nil {
			t.Fatalf("注册测试配置失败：%v", err)
		}
	}
	return registry
}

func newSystemConfigTestApp(handler *admin.SystemConfigHandler, userType int) *fiber.App {
	app := fiber.New(fiber.Config{JSONEncoder: sonic.Marshal, JSONDecoder: sonic.Unmarshal, ErrorHandler: internalMiddleware.ErrorHandler(zap.NewNop())})
	api := app.Group("/api/admin", func(c *fiber.Ctx) error {
		ctx := middleware.SetUserContext(c.UserContext(), &middleware.UserContextInfo{UserID: 7, UserType: userType})
		ctx = context.WithValue(ctx, constants.ContextKeyRequestID, "request-system-config")
		c.SetUserContext(ctx)
		return c.Next()
	})
	registerSystemConfigRoutes(api, handler, nil, "/api/admin")
	return app
}

func createTemporarySystemConfigAuditTable(t *testing.T, db *gorm.DB) {
	t.Helper()
	if err := db.Exec(`CREATE TEMP TABLE test_system_config_audit (
		id bigserial PRIMARY KEY, config_key varchar(150) NOT NULL,
		operator_id bigint NOT NULL, request_id varchar(100) NOT NULL,
		before_data jsonb NOT NULL, after_data jsonb NOT NULL
	) ON COMMIT DROP`).Error; err != nil {
		t.Fatalf("创建系统配置审计测试表失败：%v", err)
	}
}
