package hostagencypolicy

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/repository"
)

const (
	// active/disabled 只表达后台政策是否可用于结算匹配；历史结算记录不应因为停用政策被回写改口径。
	policyStatusActive   = "active"
	policyStatusDisabled = "disabled"

	// 第一阶段只配置可选结算节奏，真正的日结/半月结算调度会在后续结算任务里消费该字段。
	settlementModeDaily     = "daily"
	settlementModeHalfMonth = "half_month"

	// 自动触发由 cron-service 扫描结算；手动触发只进入后台待结算池，避免运营要求人工复核时被定时任务提前发放。
	settlementTriggerAutomatic = "automatic"
	settlementTriggerManual    = "manual"

	// 礼物金币转主播钻石由 gift_diamond_ratio_configs 统一控制；工资政策保留 1:1 快照只用于运行表字段完整。
	// 月底剩余钻石转美元默认不开启，由后台显式配置。
	defaultGiftCoinToDiamondRatio = "1"
	defaultResidualDiamondRate    = "0"

	publishStatusDraft     = "draft"
	publishStatusPublished = "published"
	publishStatusFailed    = "failed"
)

type Service struct {
	store    *repository.Store
	walletDB *sql.DB
}

func NewService(store *repository.Store, walletDB *sql.DB) *Service {
	return &Service{store: store, walletDB: walletDB}
}

func (s *Service) List(appCode string, options repository.HostAgencySalaryPolicyListOptions) ([]policyDTO, int64, error) {
	// 列表入口统一收敛 app_code、状态和结算方式，避免前端传别名或非法值时把无效条件下推到 DB。
	options.AppCode = appctx.Normalize(appCode)
	options.Status = normalizeStatusFilter(options.Status)
	options.SettlementMode = normalizeSettlementModeFilter(options.SettlementMode)
	options.SettlementTriggerMode = normalizeSettlementTriggerModeFilter(options.SettlementTriggerMode)
	items, total, err := s.store.ListHostAgencySalaryPolicies(options)
	if err != nil {
		return nil, 0, err
	}
	out := make([]policyDTO, 0, len(items))
	for _, item := range items {
		out = append(out, policyFromModel(item))
	}
	return out, total, nil
}

func (s *Service) Create(appCode string, actorID uint, req policyRequest) (policyDTO, error) {
	// 创建时先把请求规整为模型，所有金额和等级递增规则都在这个边界完成，仓储层只负责持久化。
	item, err := policyModelFromRequest(appctx.Normalize(appCode), actorID, req)
	if err != nil {
		return policyDTO{}, err
	}
	// 启用政策必须先做时间段冲突校验，否则同一区域结算时会匹配到两套工资规则。
	if err := s.ensureNoActiveOverlap(item, 0); err != nil {
		return policyDTO{}, err
	}
	if err := s.store.CreateHostAgencySalaryPolicy(&item); err != nil {
		return policyDTO{}, err
	}
	return policyFromModel(item), nil
}

func (s *Service) Update(appCode string, actorID uint, id uint, req policyRequest) (policyDTO, error) {
	// 更新先读原记录，保证 app_code 和主键归属来自数据库，避免请求体越权改租户。
	item, err := s.store.GetHostAgencySalaryPolicy(appctx.Normalize(appCode), id)
	if err != nil {
		return policyDTO{}, err
	}
	// 请求体重新走创建同一套校验，编辑和新增保持完全一致的字段约束。
	updated, err := policyModelFromRequest(item.AppCode, actorID, req)
	if err != nil {
		return policyDTO{}, err
	}
	item.Name = updated.Name
	item.RegionID = updated.RegionID
	item.Status = updated.Status
	item.SettlementMode = updated.SettlementMode
	item.SettlementTriggerMode = updated.SettlementTriggerMode
	item.GiftCoinToDiamondRatio = updated.GiftCoinToDiamondRatio
	item.ResidualDiamondToUSDRate = updated.ResidualDiamondToUSDRate
	item.EffectiveFromMS = updated.EffectiveFromMS
	item.EffectiveToMS = updated.EffectiveToMS
	item.Description = updated.Description
	item.UpdatedByAdminID = actorID
	// 编辑后运行快照可能已经落后于 admin 配置，因此保留上次发布时间但把状态重新标成 draft。
	item.PublishStatus = publishStatusDraft
	item.PublishError = ""
	// 等级明细按整包替换处理：前端提交的是一套完整政策梯度，不做局部 patch，减少排序和递增口径漂移。
	item.Levels = updated.Levels
	if err := s.ensureNoActiveOverlap(item, id); err != nil {
		return policyDTO{}, err
	}
	if err := s.store.UpdateHostAgencySalaryPolicy(&item); err != nil {
		return policyDTO{}, err
	}
	return policyFromModel(item), nil
}

func (s *Service) Delete(ctx context.Context, appCode string, id uint) error {
	// 先按 app_code 查一次，确保删除动作只能落在当前应用上下文内，随后仓储层事务删除主表和等级表。
	if _, err := s.store.GetHostAgencySalaryPolicy(appctx.Normalize(appCode), id); err != nil {
		return err
	}
	// 删除后台配置前先清掉 wallet 运行快照，避免 admin 记录消失后结算仍命中过期政策。
	if err := s.deleteRuntimePolicy(ctx, appctx.Normalize(appCode), id); err != nil {
		return err
	}
	return s.store.DeleteHostAgencySalaryPolicy(appctx.Normalize(appCode), id)
}

func (s *Service) Publish(ctx context.Context, appCode string, actorID uint, id uint) (policyDTO, error) {
	appCode = appctx.Normalize(appCode)
	item, err := s.store.GetHostAgencySalaryPolicy(appCode, id)
	if err != nil {
		return policyDTO{}, err
	}
	if err := s.ensureNoActiveOverlap(item, id); err != nil {
		return policyDTO{}, err
	}
	publishedAtMS := time.Now().UTC().UnixMilli()
	if err := s.publishRuntimePolicy(ctx, item, publishedAtMS); err != nil {
		// 发布失败也写回状态，方便后台列表明确展示“配置未进入 wallet 运行态”的事实。
		_ = s.store.UpdateHostAgencySalaryPolicyPublishState(appCode, id, publishStatusFailed, truncateRunes(err.Error(), 255), item.PublishedAtMS, actorID)
		return policyDTO{}, err
	}
	if err := s.store.UpdateHostAgencySalaryPolicyPublishState(appCode, id, publishStatusPublished, "", publishedAtMS, actorID); err != nil {
		return policyDTO{}, err
	}
	item.PublishStatus = publishStatusPublished
	item.PublishError = ""
	item.PublishedAtMS = publishedAtMS
	item.PublishedByAdminID = actorID
	return policyFromModel(item), nil
}

func (s *Service) ensureNoActiveOverlap(item model.HostAgencySalaryPolicy, excludeID uint) error {
	// 停用政策允许提前配置多套草稿；只有 active 才会参与结算匹配，所以冲突检查只约束启用态。
	if item.Status != policyStatusActive {
		return nil
	}
	// 时间段判断采用半开区间：[effective_from_ms, effective_to_ms)，effective_to_ms=0 表示长期有效。
	overlap, err := s.store.HasOverlappingActiveHostAgencySalaryPolicy(item.AppCode, item.RegionID, item.EffectiveFromMS, item.EffectiveToMS, excludeID)
	if err != nil {
		return err
	}
	if overlap {
		return errors.New("同一区域同一时间只能存在一个启用政策")
	}
	return nil
}

func (s *Service) publishRuntimePolicy(ctx context.Context, item model.HostAgencySalaryPolicy, publishedAtMS int64) error {
	if s == nil || s.walletDB == nil {
		return errors.New("wallet mysql is not configured")
	}
	if len(item.Levels) == 0 {
		return errors.New("至少需要配置一个等级")
	}
	tx, err := s.walletDB.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() { _ = tx.Rollback() }()

	// wallet-service 运行表以 admin policy_id 为主键；每次发布整包覆盖主表和等级，保证结算读取到一套完整梯度。
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO host_agency_salary_policies (
		  app_code, policy_id, name, region_id, status, settlement_mode, settlement_trigger_mode,
		  gift_coin_to_diamond_ratio, residual_diamond_to_usd_rate,
		  effective_from_ms, effective_to_ms, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
		  name = VALUES(name),
		  region_id = VALUES(region_id),
		  status = VALUES(status),
		  settlement_mode = VALUES(settlement_mode),
		  settlement_trigger_mode = VALUES(settlement_trigger_mode),
		  gift_coin_to_diamond_ratio = VALUES(gift_coin_to_diamond_ratio),
		  residual_diamond_to_usd_rate = VALUES(residual_diamond_to_usd_rate),
		  effective_from_ms = VALUES(effective_from_ms),
		  effective_to_ms = VALUES(effective_to_ms),
		  updated_at_ms = VALUES(updated_at_ms)`,
		item.AppCode,
		item.ID,
		item.Name,
		item.RegionID,
		item.Status,
		item.SettlementMode,
		firstNonBlank(item.SettlementTriggerMode, settlementTriggerAutomatic),
		item.GiftCoinToDiamondRatio,
		item.ResidualDiamondToUSDRate,
		item.EffectiveFromMS,
		item.EffectiveToMS,
		nonZeroMS(item.CreatedAtMS, publishedAtMS),
		publishedAtMS,
	); err != nil {
		return err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM host_agency_salary_policy_levels WHERE app_code = ? AND policy_id = ?`, item.AppCode, item.ID); err != nil {
		return err
	}
	for _, level := range item.Levels {
		_, hostSalaryMinor, err := parseFixedDecimal(level.HostSalaryUSD, 2, true, "主播美元工资")
		if err != nil {
			return err
		}
		_, agencySalaryMinor, err := parseFixedDecimal(level.AgencySalaryUSD, 2, true, "代理美元工资")
		if err != nil {
			return err
		}
		// 运行表保存美分整数，避免 wallet-service 结算时再解析 decimal 字符串造成重复口径。
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO host_agency_salary_policy_levels (
			  app_code, policy_id, level_no, required_diamonds, host_salary_usd_minor,
			  host_coin_reward, agency_salary_usd_minor, status, sort_order, created_at_ms, updated_at_ms
			) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			item.AppCode,
			item.ID,
			level.LevelNo,
			level.RequiredDiamonds,
			hostSalaryMinor,
			level.HostCoinReward,
			agencySalaryMinor,
			level.Status,
			level.SortOrder,
			nonZeroMS(level.CreatedAtMS, publishedAtMS),
			publishedAtMS,
		); err != nil {
			return err
		}
	}
	return tx.Commit()
}

func (s *Service) deleteRuntimePolicy(ctx context.Context, appCode string, policyID uint) error {
	if s == nil || s.walletDB == nil {
		return errors.New("wallet mysql is not configured")
	}
	tx, err := s.walletDB.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() { _ = tx.Rollback() }()
	if _, err := tx.ExecContext(ctx, `DELETE FROM host_agency_salary_policy_levels WHERE app_code = ? AND policy_id = ?`, appCode, policyID); err != nil {
		return err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM host_agency_salary_policies WHERE app_code = ? AND policy_id = ?`, appCode, policyID); err != nil {
		return err
	}
	return tx.Commit()
}

func policyModelFromRequest(appCode string, actorID uint, req policyRequest) (model.HostAgencySalaryPolicy, error) {
	name := strings.TrimSpace(req.Name)
	if name == "" || len([]rune(name)) > 120 {
		return model.HostAgencySalaryPolicy{}, errors.New("政策名称不正确")
	}
	// 当前阶段保持单区域政策：一个区域同一时段只能命中一条 active 规则，后续结算查询也以 region_id 做索引。
	if req.RegionID <= 0 {
		return model.HostAgencySalaryPolicy{}, errors.New("请选择适用区域")
	}
	status := normalizeStatus(req.Status)
	if status == "" {
		// 新建默认停用，避免运营保存草稿时立即影响结算口径。
		status = policyStatusDisabled
	}
	if !validStatus(status) {
		return model.HostAgencySalaryPolicy{}, errors.New("政策状态不正确")
	}
	settlementMode := normalizeSettlementMode(req.SettlementMode)
	if settlementMode == "" {
		// 产品当前默认日结；半月结算只是调度节奏差异，等级和差额发放规则相同。
		settlementMode = settlementModeDaily
	}
	if !validSettlementMode(settlementMode) {
		return model.HostAgencySalaryPolicy{}, errors.New("结算方式不正确")
	}
	settlementTriggerMode := normalizeSettlementTriggerMode(req.SettlementTriggerMode)
	if settlementTriggerMode == "" {
		// 默认自动触发，兼容现有政策和现有 cron 日结/半月结任务；需要人工复核时由后台显式切到 manual。
		settlementTriggerMode = settlementTriggerAutomatic
	}
	if !validSettlementTriggerMode(settlementTriggerMode) {
		return model.HostAgencySalaryPolicy{}, errors.New("结算触发方式不正确")
	}
	if req.EffectiveFromMS < 0 || req.EffectiveToMS < 0 || (req.EffectiveToMS > 0 && req.EffectiveToMS <= req.EffectiveFromMS) {
		return model.HostAgencySalaryPolicy{}, errors.New("政策生效时间不正确")
	}
	// 送礼入主播周期钻石的实际比例已经由“礼物钻石”配置在 wallet 扣礼物时写入周期账户；
	// 这里不再接受工资政策单独覆盖，避免同一笔礼物在入账和结算阶段出现两套钻石口径。
	giftRatio, _, err := parseFixedDecimal(defaultGiftCoinToDiamondRatio, 6, false, "金币转钻石比例")
	if err != nil {
		return model.HostAgencySalaryPolicy{}, err
	}
	residualRate := firstNonBlank(req.ResidualDiamondToUSDRate, defaultResidualDiamondRate)
	residualRate, _, err = parseFixedDecimal(residualRate, 12, true, "剩余钻石转美元比例")
	if err != nil {
		return model.HostAgencySalaryPolicy{}, err
	}
	description := strings.TrimSpace(req.Description)
	if len([]rune(description)) > 255 {
		return model.HostAgencySalaryPolicy{}, errors.New("备注不能超过 255 个字符")
	}
	// 等级表保存的是累计门槛和累计权益；实际发薪时应减去上一已结算等级，只发差额。
	levels, err := levelModelsFromRequest(req.Levels)
	if err != nil {
		return model.HostAgencySalaryPolicy{}, err
	}
	return model.HostAgencySalaryPolicy{
		AppCode:                  appCode,
		Name:                     name,
		RegionID:                 req.RegionID,
		Status:                   status,
		SettlementMode:           settlementMode,
		SettlementTriggerMode:    settlementTriggerMode,
		GiftCoinToDiamondRatio:   giftRatio,
		ResidualDiamondToUSDRate: residualRate,
		EffectiveFromMS:          req.EffectiveFromMS,
		EffectiveToMS:            req.EffectiveToMS,
		Description:              description,
		CreatedByAdminID:         actorID,
		UpdatedByAdminID:         actorID,
		Levels:                   levels,
	}, nil
}

func levelModelsFromRequest(requests []levelRequest) ([]model.HostAgencySalaryLevel, error) {
	if len(requests) == 0 {
		return nil, errors.New("至少需要配置一个等级")
	}
	// 先按等级号排序再校验，允许前端在编辑过程中临时乱序提交，但最终持久化必须是稳定梯度。
	levels := append([]levelRequest(nil), requests...)
	sortLevelRequests(levels)
	seen := map[int32]struct{}{}
	out := make([]model.HostAgencySalaryLevel, 0, len(levels))
	var previousRequiredDiamonds int64
	var previousHostSalaryCents int64
	var previousHostCoinReward int64
	var previousAgencySalaryCents int64
	for index, req := range levels {
		// 等级号是结算差额的顺序锚点，不能为 0，也不能重复。
		if req.Level <= 0 {
			return nil, errors.New("等级必须大于 0")
		}
		if _, ok := seen[req.Level]; ok {
			return nil, fmt.Errorf("等级 %d 重复", req.Level)
		}
		seen[req.Level] = struct{}{}
		if req.RequiredDiamonds <= 0 {
			return nil, fmt.Errorf("等级 %d 所需钻石必须大于 0", req.Level)
		}
		// 钻石门槛必须严格递增，否则用户达到高等级时无法确定唯一命中等级。
		if index > 0 && req.RequiredDiamonds <= previousRequiredDiamonds {
			return nil, fmt.Errorf("等级 %d 所需钻石必须大于上一等级", req.Level)
		}
		// 主播美元工资是“累计应得”，后续结算以当前等级累计值减去已结算累计值发差额。
		hostSalary, hostSalaryCents, err := parseFixedDecimal(req.HostSalaryUSD, 2, true, "主播美元工资")
		if err != nil {
			return nil, fmt.Errorf("等级 %d %w", req.Level, err)
		}
		if index > 0 && hostSalaryCents < previousHostSalaryCents {
			return nil, fmt.Errorf("等级 %d 主播美元工资不能小于上一等级", req.Level)
		}
		if req.HostCoinReward < 0 {
			return nil, fmt.Errorf("等级 %d 主播金币奖励不能小于 0", req.Level)
		}
		// 金币奖励同样是累计配置，不能比上一等级低，否则差额发放会变成负数。
		if index > 0 && req.HostCoinReward < previousHostCoinReward {
			return nil, fmt.Errorf("等级 %d 主播金币奖励不能小于上一等级", req.Level)
		}
		// 代理美元工资跟随主播结算同时发放，也按累计值减已结算累计值计算差额。
		agencySalary, agencySalaryCents, err := parseFixedDecimal(req.AgencySalaryUSD, 2, true, "代理美元工资")
		if err != nil {
			return nil, fmt.Errorf("等级 %d %w", req.Level, err)
		}
		if index > 0 && agencySalaryCents < previousAgencySalaryCents {
			return nil, fmt.Errorf("等级 %d 代理美元工资不能小于上一等级", req.Level)
		}
		status := normalizeStatus(req.Status)
		if status == "" {
			// 单个等级默认启用；需要临时屏蔽某个等级时由运营显式停用。
			status = policyStatusActive
		}
		if !validStatus(status) {
			return nil, fmt.Errorf("等级 %d 状态不正确", req.Level)
		}
		sortOrder := req.SortOrder
		if sortOrder == 0 {
			// 默认排序跟等级号一致，前端展示和后续结算扫描都能保持自然顺序。
			sortOrder = req.Level
		}
		out = append(out, model.HostAgencySalaryLevel{
			LevelNo:          req.Level,
			RequiredDiamonds: req.RequiredDiamonds,
			HostSalaryUSD:    hostSalary,
			HostCoinReward:   req.HostCoinReward,
			AgencySalaryUSD:  agencySalary,
			Status:           status,
			SortOrder:        sortOrder,
		})
		previousRequiredDiamonds = req.RequiredDiamonds
		previousHostSalaryCents = hostSalaryCents
		previousHostCoinReward = req.HostCoinReward
		previousAgencySalaryCents = agencySalaryCents
	}
	return out, nil
}

func sortLevelRequests(items []levelRequest) {
	for i := 1; i < len(items); i++ {
		item := items[i]
		j := i - 1
		for j >= 0 && items[j].Level > item.Level {
			items[j+1] = items[j]
			j--
		}
		items[j+1] = item
	}
}

func parseFixedDecimal(raw string, scale int, allowZero bool, field string) (string, int64, error) {
	value := strings.TrimSpace(raw)
	if strings.HasPrefix(value, "+") {
		value = strings.TrimPrefix(value, "+")
	}
	if value == "" || strings.HasPrefix(value, "-") {
		return "", 0, fmt.Errorf("%s不正确", field)
	}
	parts := strings.Split(value, ".")
	if len(parts) > 2 || parts[0] == "" || !allDigits(parts[0]) {
		return "", 0, fmt.Errorf("%s不正确", field)
	}
	if len(parts) == 2 && (parts[1] == "" || len(parts[1]) > scale || !allDigits(parts[1])) {
		return "", 0, fmt.Errorf("%s最多支持 %d 位小数", field, scale)
	}
	whole, err := strconv.ParseInt(parts[0], 10, 64)
	if err != nil {
		return "", 0, fmt.Errorf("%s过大", field)
	}
	multiplier := pow10(scale)
	if whole > (1<<62)/multiplier {
		return "", 0, fmt.Errorf("%s过大", field)
	}
	var fraction int64
	if len(parts) == 2 {
		fractionText := parts[1] + strings.Repeat("0", scale-len(parts[1]))
		fraction, err = strconv.ParseInt(fractionText, 10, 64)
		if err != nil {
			return "", 0, fmt.Errorf("%s不正确", field)
		}
	}
	scaled := whole*multiplier + fraction
	if !allowZero && scaled <= 0 {
		return "", 0, fmt.Errorf("%s必须大于 0", field)
	}
	return strconv.FormatInt(whole, 10) + "." + fixedDigits(fraction, scale), scaled, nil
}

func fixedDigits(value int64, scale int) string {
	text := strconv.FormatInt(value, 10)
	if len(text) >= scale {
		return text
	}
	return strings.Repeat("0", scale-len(text)) + text
}

func trimDecimalZeros(value string) string {
	value = strings.TrimSpace(value)
	if !strings.Contains(value, ".") {
		return value
	}
	value = strings.TrimRight(value, "0")
	return strings.TrimRight(value, ".")
}

func pow10(scale int) int64 {
	var out int64 = 1
	for i := 0; i < scale; i++ {
		out *= 10
	}
	return out
}

func allDigits(value string) bool {
	for _, r := range value {
		if r < '0' || r > '9' {
			return false
		}
	}
	return true
}

func firstNonBlank(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}

func nonZeroMS(value int64, fallback int64) int64 {
	if value > 0 {
		return value
	}
	return fallback
}

func truncateRunes(value string, limit int) string {
	if limit <= 0 {
		return ""
	}
	runes := []rune(strings.TrimSpace(value))
	if len(runes) <= limit {
		return string(runes)
	}
	return string(runes[:limit])
}

func normalizeStatus(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", policyStatusActive, "enabled":
		if strings.TrimSpace(value) == "" {
			return ""
		}
		return policyStatusActive
	case policyStatusDisabled, "inactive":
		return policyStatusDisabled
	default:
		return strings.ToLower(strings.TrimSpace(value))
	}
}

func normalizeStatusFilter(value string) string {
	status := normalizeStatus(value)
	if !validStatus(status) {
		return ""
	}
	return status
}

func validStatus(value string) bool {
	return value == policyStatusActive || value == policyStatusDisabled
}

func normalizeSettlementMode(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", settlementModeDaily, "day":
		if strings.TrimSpace(value) == "" {
			return ""
		}
		return settlementModeDaily
	case settlementModeHalfMonth, "half-month", "semi_month":
		return settlementModeHalfMonth
	default:
		return strings.ToLower(strings.TrimSpace(value))
	}
}

func normalizeSettlementModeFilter(value string) string {
	mode := normalizeSettlementMode(value)
	if !validSettlementMode(mode) {
		return ""
	}
	return mode
}

func validSettlementMode(value string) bool {
	return value == settlementModeDaily || value == settlementModeHalfMonth
}

func normalizeSettlementTriggerMode(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", settlementTriggerAutomatic, "auto":
		if strings.TrimSpace(value) == "" {
			return ""
		}
		return settlementTriggerAutomatic
	case settlementTriggerManual, "manual_settlement":
		return settlementTriggerManual
	default:
		return strings.ToLower(strings.TrimSpace(value))
	}
}

func normalizeSettlementTriggerModeFilter(value string) string {
	mode := normalizeSettlementTriggerMode(value)
	if !validSettlementTriggerMode(mode) {
		return ""
	}
	return mode
}

func validSettlementTriggerMode(value string) bool {
	return value == settlementTriggerAutomatic || value == settlementTriggerManual
}
