package appconfig

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

const (
	giftComboConfigGroup = "room-gift-combo"
	giftComboConfigKey   = "default"
)

// GiftComboConfigRequest is intentionally a full replacement document. Operators can inspect the
// current normalized response before updating it, and enabled=false is therefore an unambiguous
// one-click kill switch rather than an omitted boolean accidentally inheriting true.
type GiftComboConfigRequest struct {
	Enabled              bool  `json:"enabled"`
	APIVersion           int32 `json:"apiVersion"`
	WindowMS             int32 `json:"windowMs"`
	MaxGiftCountPerBatch int32 `json:"maxGiftCountPerBatch"`
	MaxGiftUnitsPerBatch int32 `json:"maxGiftUnitsPerBatch"`
	MaxInFlight          int32 `json:"maxInFlight"`
	MaxPendingBatches    int32 `json:"maxPendingBatches"`
	RetryMaxAttempts     int32 `json:"retryMaxAttempts"`
	RetryBaseDelayMS     int32 `json:"retryBaseDelayMs"`
	RolloutBasisPoints   int32 `json:"rolloutBasisPoints"`
}

type storedGiftComboConfig struct {
	Enabled              bool  `json:"enabled"`
	APIVersion           int32 `json:"api_version"`
	WindowMS             int32 `json:"window_ms"`
	MaxGiftCountPerBatch int32 `json:"max_gift_count_per_batch"`
	MaxGiftUnitsPerBatch int32 `json:"max_gift_units_per_batch"`
	MaxInFlight          int32 `json:"max_in_flight"`
	MaxPendingBatches    int32 `json:"max_pending_batches"`
	RetryMaxAttempts     int32 `json:"retry_max_attempts"`
	RetryBaseDelayMS     int32 `json:"retry_base_delay_ms"`
	RolloutBasisPoints   int32 `json:"rollout_basis_points"`
}

type GiftComboConfigDTO struct {
	GiftComboConfigRequest
	AppCode     string `json:"appCode"`
	UpdatedAtMS int64  `json:"updatedAtMs"`
}

func defaultStoredGiftComboConfig() storedGiftComboConfig {
	return storedGiftComboConfig{
		Enabled:              true,
		APIVersion:           2,
		WindowMS:             300,
		MaxGiftCountPerBatch: 999,
		MaxGiftUnitsPerBatch: 5_000,
		MaxInFlight:          2,
		MaxPendingBatches:    20,
		RetryMaxAttempts:     5,
		RetryBaseDelayMS:     250,
		// 新 App 没有配置行时只展示安全的 0% 灰度，必须由管理员显式放量。
		RolloutBasisPoints: 0,
	}
}

func normalizeStoredGiftComboConfig(config storedGiftComboConfig) (storedGiftComboConfig, error) {
	if config.APIVersion == 0 {
		config.APIVersion = 2
	}
	if config.APIVersion != 2 {
		// 后台只允许调整微批窗口、背压和灰度；接口版本不是运营开关，避免把当前 Flutter
		// 从具备完整结算结果重放的 V2 静默切回 V1。
		return storedGiftComboConfig{}, errors.New("apiVersion must be 2")
	}
	if config.WindowMS < 100 || config.WindowMS > 1_000 {
		return storedGiftComboConfig{}, errors.New("windowMs must be between 100 and 1000")
	}
	if config.MaxGiftCountPerBatch < 1 || config.MaxGiftCountPerBatch > 999 {
		return storedGiftComboConfig{}, errors.New("maxGiftCountPerBatch must be between 1 and 999")
	}
	if config.MaxGiftUnitsPerBatch < 1 || config.MaxGiftUnitsPerBatch > 5_000 {
		return storedGiftComboConfig{}, errors.New("maxGiftUnitsPerBatch must be between 1 and 5000")
	}
	if config.MaxInFlight < 1 || config.MaxInFlight > 2 {
		return storedGiftComboConfig{}, errors.New("maxInFlight must be between 1 and 2")
	}
	if config.MaxPendingBatches < 1 || config.MaxPendingBatches > 50 {
		return storedGiftComboConfig{}, errors.New("maxPendingBatches must be between 1 and 50")
	}
	if config.MaxPendingBatches < config.MaxInFlight {
		// The admin contract must reject policies Flutter cannot execute literally; normalizing this
		// mismatch on-device would make an audited configuration differ from production behavior.
		return storedGiftComboConfig{}, errors.New("maxPendingBatches must be greater than or equal to maxInFlight")
	}
	if config.RetryMaxAttempts < 1 || config.RetryMaxAttempts > 10 {
		return storedGiftComboConfig{}, errors.New("retryMaxAttempts must be between 1 and 10")
	}
	if config.RetryBaseDelayMS < 100 || config.RetryBaseDelayMS > 5_000 {
		return storedGiftComboConfig{}, errors.New("retryBaseDelayMs must be between 100 and 5000")
	}
	if config.RolloutBasisPoints < 0 || config.RolloutBasisPoints > 10_000 {
		return storedGiftComboConfig{}, errors.New("rolloutBasisPoints must be between 0 and 10000")
	}
	return config, nil
}

func normalizeStoredGiftComboConfigForRead(config storedGiftComboConfig) (storedGiftComboConfig, error) {
	if config.APIVersion == 1 {
		// 兼容早期已落库的灰度行，但只迁移存储标记，不恢复客户端 V1 行为。管理员下一次
		// 保存会写回 2；Update 入口仍通过严格校验拒绝新建 apiVersion=1 配置。
		config.APIVersion = 2
	}
	return normalizeStoredGiftComboConfig(config)
}

func storedGiftComboConfigFromRequest(request GiftComboConfigRequest) (storedGiftComboConfig, error) {
	return normalizeStoredGiftComboConfig(storedGiftComboConfig{
		Enabled:              request.Enabled,
		APIVersion:           request.APIVersion,
		WindowMS:             request.WindowMS,
		MaxGiftCountPerBatch: request.MaxGiftCountPerBatch,
		MaxGiftUnitsPerBatch: request.MaxGiftUnitsPerBatch,
		MaxInFlight:          request.MaxInFlight,
		MaxPendingBatches:    request.MaxPendingBatches,
		RetryMaxAttempts:     request.RetryMaxAttempts,
		RetryBaseDelayMS:     request.RetryBaseDelayMS,
		RolloutBasisPoints:   request.RolloutBasisPoints,
	})
}

func giftComboConfigDTO(appCode string, config storedGiftComboConfig, updatedAtMS int64) GiftComboConfigDTO {
	return GiftComboConfigDTO{
		AppCode:     appctx.Normalize(appCode),
		UpdatedAtMS: updatedAtMS,
		GiftComboConfigRequest: GiftComboConfigRequest{
			Enabled:              config.Enabled,
			APIVersion:           config.APIVersion,
			WindowMS:             config.WindowMS,
			MaxGiftCountPerBatch: config.MaxGiftCountPerBatch,
			MaxGiftUnitsPerBatch: config.MaxGiftUnitsPerBatch,
			MaxInFlight:          config.MaxInFlight,
			MaxPendingBatches:    config.MaxPendingBatches,
			RetryMaxAttempts:     config.RetryMaxAttempts,
			RetryBaseDelayMS:     config.RetryBaseDelayMS,
			RolloutBasisPoints:   config.RolloutBasisPoints,
		},
	}
}

func (s *AppConfigService) GetGiftComboConfig(appCode string) (GiftComboConfigDTO, error) {
	appCode = appctx.Normalize(appCode)
	item, err := s.store.GetScopedAppConfig(appCode, giftComboConfigGroup, giftComboConfigKey)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return giftComboConfigDTO(appCode, defaultStoredGiftComboConfig(), 0), nil
	}
	if err != nil {
		return GiftComboConfigDTO{}, err
	}
	config := defaultStoredGiftComboConfig()
	if err := json.Unmarshal([]byte(strings.TrimSpace(item.Value)), &config); err != nil {
		return GiftComboConfigDTO{}, fmt.Errorf("stored gift combo config is invalid: %w", err)
	}
	config, err = normalizeStoredGiftComboConfigForRead(config)
	if err != nil {
		return GiftComboConfigDTO{}, err
	}
	return giftComboConfigDTO(appCode, config, item.UpdatedAtMS), nil
}

func (s *AppConfigService) UpdateGiftComboConfig(appCode string, request GiftComboConfigRequest) (GiftComboConfigDTO, error) {
	appCode = appctx.Normalize(appCode)
	config, err := storedGiftComboConfigFromRequest(request)
	if err != nil {
		return GiftComboConfigDTO{}, err
	}
	raw, err := json.Marshal(config)
	if err != nil {
		return GiftComboConfigDTO{}, err
	}
	// UpsertScopedAppConfigs writes only the selected app_code, so a rollout or kill switch for one App
	// cannot unexpectedly alter every tenant sharing this admin database.
	if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{{
		AppCode:     appCode,
		Group:       giftComboConfigGroup,
		Key:         giftComboConfigKey,
		Value:       string(raw),
		Description: "Flutter room gift combo batching policy",
	}}); err != nil {
		return GiftComboConfigDTO{}, err
	}
	return s.GetGiftComboConfig(appCode)
}

func (h *Handler) GetGiftComboConfig(c *gin.Context) {
	item, err := h.service.GetGiftComboConfig(appctx.FromContext(c.Request.Context()))
	if err != nil {
		response.ServerError(c, "获取送礼连击配置失败")
		return
	}
	response.OK(c, item)
}

func (h *Handler) UpdateGiftComboConfig(c *gin.Context) {
	var request GiftComboConfigRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		response.BadRequest(c, "参数不正确")
		return
	}
	item, err := h.service.UpdateGiftComboConfig(appctx.FromContext(c.Request.Context()), request)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLog(c, h.audit, "update-room-gift-combo", "admin_app_configs", "success", fmt.Sprintf("app_code=%s enabled=%t rollout_bps=%d", item.AppCode, item.Enabled, item.RolloutBasisPoints))
	response.OK(c, item)
}
