package luckygift

import (
	"context"
	"strings"
	"unicode/utf8"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
)

// CreateExperiment 原子完成 treatment 发布与实验登记。treatment 配置必须直接可运行（enabled），
// 否则实验组用户在送礼预检查就会被 RuleNotActive 拦截，实验退化成按人关停功能。
func (s *Service) CreateExperiment(ctx context.Context, cmd domain.CreateExperimentCommand) (domain.Experiment, domain.RuleConfig, error) {
	if err := s.requireRepository(); err != nil {
		return domain.Experiment{}, domain.RuleConfig{}, err
	}
	cmd.Name = strings.TrimSpace(cmd.Name)
	if cmd.Name == "" || utf8.RuneCountInString(cmd.Name) > 64 {
		return domain.Experiment{}, domain.RuleConfig{}, xerr.New(xerr.InvalidArgument, "experiment name must be 1-64 characters")
	}
	if cmd.TreatmentTrafficPPM < 0 || cmd.TreatmentTrafficPPM > domain.ExperimentTrafficPPMScale {
		return domain.Experiment{}, domain.RuleConfig{}, xerr.New(xerr.InvalidArgument, "experiment treatment traffic must be between 0 and 1000000 ppm")
	}
	cmd.TreatmentConfig.AppCode = appcode.FromContext(ctx)
	cmd.TreatmentConfig.PoolID = normalizePoolID(cmd.TreatmentConfig.PoolID)
	cmd.TreatmentConfig = normalizeRuleConfig(cmd.TreatmentConfig)
	if !cmd.TreatmentConfig.Enabled {
		return domain.Experiment{}, domain.RuleConfig{}, xerr.New(xerr.InvalidArgument, "experiment treatment config must be enabled")
	}
	if err := validateRuleConfig(cmd.TreatmentConfig); err != nil {
		return domain.Experiment{}, domain.RuleConfig{}, err
	}
	overrides, err := normalizeExperimentOverrides(cmd.Overrides)
	if err != nil {
		return domain.Experiment{}, domain.RuleConfig{}, err
	}
	cmd.Overrides = overrides
	return s.repository.CreateLuckyGiftExperiment(ctx, cmd, s.now().UTC().UnixMilli())
}

func (s *Service) ListExperiments(ctx context.Context, query domain.ExperimentQuery) ([]domain.Experiment, error) {
	if err := s.requireRepository(); err != nil {
		return nil, err
	}
	if strings.TrimSpace(query.AppCode) == "" {
		query.AppCode = appcode.FromContext(ctx)
	}
	if status := strings.ToLower(strings.TrimSpace(query.Status)); status != "" {
		if status != domain.ExperimentStatusRunning && status != domain.ExperimentStatusPaused && status != domain.ExperimentStatusCompleted {
			return nil, xerr.New(xerr.InvalidArgument, "invalid experiment status filter")
		}
		query.Status = status
	}
	return s.repository.ListLuckyGiftExperiments(ctx, query)
}

func (s *Service) UpdateExperiment(ctx context.Context, cmd domain.UpdateExperimentCommand) (domain.Experiment, error) {
	if err := s.requireRepository(); err != nil {
		return domain.Experiment{}, err
	}
	cmd.ExperimentID = strings.TrimSpace(cmd.ExperimentID)
	if cmd.ExperimentID == "" {
		return domain.Experiment{}, xerr.New(xerr.InvalidArgument, "experiment id is required")
	}
	if cmd.TreatmentTrafficPPM > domain.ExperimentTrafficPPMScale {
		return domain.Experiment{}, xerr.New(xerr.InvalidArgument, "experiment treatment traffic must be between 0 and 1000000 ppm")
	}
	cmd.Status = strings.ToLower(strings.TrimSpace(cmd.Status))
	if cmd.Status != "" && cmd.Status != domain.ExperimentStatusRunning && cmd.Status != domain.ExperimentStatusPaused {
		// completed 只能经 EndExperiment 收口：结束必须同时重发布获胜版本，不能只改状态。
		return domain.Experiment{}, xerr.New(xerr.InvalidArgument, "experiment status can only switch between running and paused")
	}
	if cmd.TreatmentTrafficPPM < 0 && cmd.Status == "" {
		return domain.Experiment{}, xerr.New(xerr.InvalidArgument, "experiment update has nothing to change")
	}
	return s.repository.UpdateLuckyGiftExperiment(ctx, cmd, s.now().UTC().UnixMilli())
}

func (s *Service) SetExperimentOverrides(ctx context.Context, cmd domain.SetExperimentOverridesCommand) (domain.Experiment, error) {
	if err := s.requireRepository(); err != nil {
		return domain.Experiment{}, err
	}
	cmd.ExperimentID = strings.TrimSpace(cmd.ExperimentID)
	if cmd.ExperimentID == "" {
		return domain.Experiment{}, xerr.New(xerr.InvalidArgument, "experiment id is required")
	}
	upserts, err := normalizeExperimentOverrides(cmd.Upserts)
	if err != nil {
		return domain.Experiment{}, err
	}
	cmd.Upserts = upserts
	removes := make([]string, 0, len(cmd.Removes))
	for _, userKey := range cmd.Removes {
		if userKey = strings.TrimSpace(userKey); userKey != "" {
			removes = append(removes, userKey)
		}
	}
	cmd.Removes = removes
	if len(cmd.Upserts) == 0 && len(cmd.Removes) == 0 {
		return domain.Experiment{}, xerr.New(xerr.InvalidArgument, "experiment override update has nothing to change")
	}
	return s.repository.SetLuckyGiftExperimentOverrides(ctx, cmd, s.now().UTC().UnixMilli())
}

func (s *Service) EndExperiment(ctx context.Context, cmd domain.EndExperimentCommand) (domain.Experiment, domain.RuleConfig, error) {
	if err := s.requireRepository(); err != nil {
		return domain.Experiment{}, domain.RuleConfig{}, err
	}
	cmd.ExperimentID = strings.TrimSpace(cmd.ExperimentID)
	if cmd.ExperimentID == "" {
		return domain.Experiment{}, domain.RuleConfig{}, xerr.New(xerr.InvalidArgument, "experiment id is required")
	}
	cmd.WinnerArm = strings.ToLower(strings.TrimSpace(cmd.WinnerArm))
	if !domain.ValidExperimentArm(cmd.WinnerArm) {
		return domain.Experiment{}, domain.RuleConfig{}, xerr.New(xerr.InvalidArgument, "experiment winner arm must be control or treatment")
	}
	return s.repository.EndLuckyGiftExperiment(ctx, cmd, s.now().UTC().UnixMilli())
}

func normalizeExperimentOverrides(overrides []domain.ExperimentOverride) ([]domain.ExperimentOverride, error) {
	if len(overrides) > domain.MaxExperimentOverrides {
		return nil, xerr.New(xerr.InvalidArgument, "experiment overrides exceed the 500 user limit")
	}
	seen := make(map[string]struct{}, len(overrides))
	out := make([]domain.ExperimentOverride, 0, len(overrides))
	for _, override := range overrides {
		override.UserKey = strings.TrimSpace(override.UserKey)
		override.Arm = strings.ToLower(strings.TrimSpace(override.Arm))
		if override.UserKey == "" || utf8.RuneCountInString(override.UserKey) > 128 {
			return nil, xerr.New(xerr.InvalidArgument, "experiment override user key must be 1-128 characters")
		}
		if !domain.ValidExperimentArm(override.Arm) {
			return nil, xerr.New(xerr.InvalidArgument, "experiment override arm must be control or treatment")
		}
		if _, exists := seen[override.UserKey]; exists {
			return nil, xerr.New(xerr.InvalidArgument, "experiment override user key is duplicated: "+override.UserKey)
		}
		seen[override.UserKey] = struct{}{}
		out = append(out, override)
	}
	return out, nil
}
