# 文件: zyplib\artifact\stat_points_unified.py
"""
点生成模块; 教学用

功能是模拟生成一些点的值，使其满足我们给定的统计分布条件。


该模块提供三种不同的算法策略来生成满足指定统计特性的数据点：
1. 生成-调整策略 (Generative-Adjust)：基于标准分布生成后调整
2. 优化求解策略 (Optimization)：直接优化目标函数求解
3. 混合策略 (Hybrid)：结合前两者优势的智能方法

适用场景：
- 模拟实验数据生成
- 机器学习模型性能评估
- 统计教学演示
- 算法测试数据构造
"""

import time
import warnings
from dataclasses import dataclass, field
from enum import Enum
from typing import Callable, Dict, List, Optional, Tuple, Union

import numpy as np
from numpy.typing import NDArray
from scipy import optimize, stats


class GenerationStrategy(Enum):
    """生成策略枚举"""

    GENERATIVE = 'generative'  # 生成-调整策略
    OPTIMIZATION = 'optimization'  # 优化求解策略
    HYBRID = 'hybrid'  # 混合策略
    AUTO = 'auto'  # 自动选择最佳策略


@dataclass
class OutlierSpec:
    """异常值规格说明"""

    n_outliers: int
    """异常值数量"""

    extremeness: float = 3.0
    """异常值极端程度（标准差倍数）"""

    direction: str = 'both'
    """异常值方向: 'positive', 'negative', 'both'"""


@dataclass
class StatSpec:
    """统计特性规格说明"""

    # === 基础参数 ===
    n_points: int
    """生成的数据点数量"""

    # === 统计目标 ===
    target_mean: Optional[float] = None
    """目标均值"""

    target_std: Optional[float] = None
    """目标标准差"""

    target_median: Optional[float] = None
    """目标中位数"""

    target_skewness: Optional[float] = None
    """目标偏度 (负值左偏，正值右偏)"""

    target_kurtosis: Optional[float] = None
    """目标峰度 (相对于正态分布)"""

    # === 范围和分布控制 ===
    distribution_range: Optional[Tuple[float, float]] = None
    """核心分布的'软'范围 (min, max)，用于指导生成，异常值可超出"""

    absolute_range: Optional[Tuple[float, float]] = None
    """所有点(包括异常值)的'硬'边界 (min, max)，最终强制约束"""

    value_range: Optional[Tuple[float, float]] = None
    """DEPRECATED: 数值范围，建议使用 distribution_range"""

    distribution_hint: str = 'normal'
    """分布形状提示: 'normal', 'skewed', 'bimodal', 'uniform', 'exponential', 'beta'"""

    # === 异常值控制 ===
    outliers: Optional[OutlierSpec] = None
    """异常值规格"""

    # === 策略和精度控制 ===
    strategy: GenerationStrategy = GenerationStrategy.AUTO
    """生成策略选择"""

    precision_tolerance: float = 0.05
    """统计量精度容忍度 (相对误差)"""

    max_iterations: int = 200
    """优化最大迭代次数"""

    # === 其他 ===
    random_seed: Optional[int] = None
    """随机种子"""


@dataclass
class GenerationResult:
    """生成结果"""

    spec: StatSpec
    """原始规格"""

    points: NDArray
    """生成的数据点"""

    actual_stats: Dict[str, float] = field(default_factory=dict)
    """实际统计特性"""

    target_stats: Dict[str, float] = field(default_factory=dict)
    """目标统计特性"""

    strategy_used: str = ''
    """实际使用的策略"""

    generation_time: float = 0.0
    """生成耗时（秒）"""

    success: bool = True
    """生成是否成功"""

    warnings: List[str] = field(default_factory=list)
    """警告信息"""

    convergence_info: Dict = field(default_factory=dict)
    """收敛信息（用于优化策略）"""

    outlier_indices: Optional[List[int]] = None
    """异常值索引"""


class BaseStatGenerator:
    """统计点生成器基类"""

    def __init__(self, spec: StatSpec):
        self.spec = spec
        self.rng = np.random.RandomState(spec.random_seed)
        self._validate_spec()
        self._resolve_ranges()

    def _validate_spec(self):
        """验证输入规格"""
        if self.spec.n_points <= 0:
            raise ValueError('n_points must be positive')
        if self.spec.target_std is not None and self.spec.target_std <= 0:
            raise ValueError('target_std must be positive')

        # 验证范围参数
        for range_name, range_value in [
            ('value_range', self.spec.value_range),
            ('distribution_range', self.spec.distribution_range),
            ('absolute_range', self.spec.absolute_range),
        ]:
            if range_value is not None:
                min_val, max_val = range_value
                if min_val >= max_val:
                    raise ValueError(f'{range_name} min must be less than max')

    def _set_defaults(self):
        """设置默认参数"""
        spec = self.spec
        if spec.target_mean is None:
            if spec.distribution_range is not None:
                spec.target_mean = float(np.mean(spec.distribution_range))
            else:
                spec.target_mean = 0.0

        if spec.target_std is None:
            if spec.distribution_range is not None:
                spec.target_std = (
                    spec.distribution_range[1] - spec.distribution_range[0]
                ) / 6
            else:
                spec.target_std = 1.0

    def _calculate_stats(self, points: NDArray) -> Dict[str, float]:
        """计算统计特性"""
        if len(points) == 0:
            return {}

        stats_dict = {
            'n_points': len(points),
            'mean': float(np.mean(points)),
            'std': float(np.std(points, ddof=1)) if len(points) > 1 else 0.0,
            'median': float(np.median(points)),
            'min': float(np.min(points)),
            'max': float(np.max(points)),
        }

        if len(points) > 2:
            stats_dict['skewness'] = float(stats.skew(points))
            stats_dict['kurtosis'] = float(stats.kurtosis(points))

        return stats_dict

    def _get_target_stats(self) -> Dict[str, float]:
        """获取目标统计特性"""
        target_stats: Dict[str, float] = {'n_points': float(self.spec.n_points)}

        for attr in [
            'target_mean',
            'target_std',
            'target_median',
            'target_skewness',
            'target_kurtosis',
        ]:
            value = getattr(self.spec, attr)
            if value is not None:
                target_stats[attr.replace('target_', '')] = value

        if self.spec.distribution_range is not None:
            target_stats['min'] = self.spec.distribution_range[0]
            target_stats['max'] = self.spec.distribution_range[1]

        return target_stats

    def _add_outliers(self, points: NDArray) -> Tuple[NDArray, List[int]]:
        """添加异常值"""
        if self.spec.outliers is None or self.spec.outliers.n_outliers == 0:
            return points, []

        outlier_spec = self.spec.outliers
        n_outliers = min(outlier_spec.n_outliers, len(points))

        # 选择异常值位置
        outlier_indices = self.rng.choice(len(points), n_outliers, replace=False)

        # 计算基准统计量
        base_mean = np.mean(points)
        base_std = np.std(points, ddof=1) if np.std(points, ddof=1) > 0 else 1.0

        # 生成异常值
        for idx in outlier_indices:
            if outlier_spec.direction == 'positive':
                direction = 1
            elif outlier_spec.direction == 'negative':
                direction = -1
            else:  # 'both'
                direction = self.rng.choice([-1, 1])

            # 添加随机性避免异常值完全相同
            random_factor = self.rng.uniform(0.8, 1.2)
            outlier_value = (
                base_mean
                + direction * outlier_spec.extremeness * base_std * random_factor
            )
            points[idx] = outlier_value

        return points, outlier_indices.tolist()

    def _resolve_ranges(self):
        """解析和兼容范围参数"""
        # 向后兼容处理
        if self.spec.distribution_range is None and self.spec.value_range is not None:
            self.spec.distribution_range = self.spec.value_range
            warnings.warn(
                'value_range is deprecated, use distribution_range instead',
                DeprecationWarning,
                stacklevel=3,
            )

    def _apply_final_clip(self, points: NDArray) -> NDArray:
        """应用最终的绝对范围约束"""
        if self.spec.absolute_range is not None:
            return np.clip(
                points, self.spec.absolute_range[0], self.spec.absolute_range[1]
            )
        return points


class GenerativeAdjustGenerator(BaseStatGenerator):
    """
    生成-调整策略

    算法流程：
    1. 根据distribution_hint从标准分布族中采样
    2. 通过线性变换调整均值和标准差
    3. 应用范围约束和添加异常值

    优势：
    - 生成速度快
    - 分布形状自然
    - 直观易懂

    劣势：
    - 线性调整可能扭曲原始分布形状
    - 难以精确控制高阶矩（偏度、峰度）
    """

    def generate(self) -> GenerationResult:
        """执行生成-调整策略"""
        start_time = time.time()
        self._set_defaults()

        try:
            # 1. 从分布族中生成初始点
            initial_points = self._generate_from_distribution()

            # 2. 调整统计特性
            adjusted_points = self._adjust_statistics(initial_points)

            # 3. 添加异常值
            final_points, outlier_indices = self._add_outliers(adjusted_points)

            # 4. 应用最终的绝对范围约束
            final_points = self._apply_final_clip(final_points)

            # 5. 计算结果统计量
            actual_stats = self._calculate_stats(final_points)
            target_stats = self._get_target_stats()

            generation_time = time.time() - start_time

            return GenerationResult(
                spec=self.spec,
                points=final_points,
                actual_stats=actual_stats,
                target_stats=target_stats,
                strategy_used='generative-adjust',
                generation_time=generation_time,
                success=True,
                outlier_indices=outlier_indices,
            )

        except Exception as e:
            return GenerationResult(
                spec=self.spec,
                points=np.array([]),
                actual_stats={},
                target_stats=self._get_target_stats(),
                strategy_used='generative-adjust',
                generation_time=time.time() - start_time,
                success=False,
                warnings=[f'Generation failed: {str(e)}'],
            )

    def _generate_from_distribution(self) -> NDArray:
        """根据分布提示生成初始点（仅核心点，不包括异常值）"""
        n = self.spec.n_points
        if self.spec.outliers:
            n -= self.spec.outliers.n_outliers  # 只生成核心点数量

        hint = self.spec.distribution_hint.lower()
        dist_range = self.spec.distribution_range

        if hint == 'normal':
            if dist_range:
                # 使用截断正态分布，避免后续clip
                mean, std = 0, 1  # 标准参数，后续会调整
                a, b = (dist_range[0] - mean) / std, (dist_range[1] - mean) / std
                return np.asarray(
                    stats.truncnorm.rvs(
                        a, b, loc=mean, scale=std, size=n, random_state=self.rng
                    ),
                    dtype=float,
                )
            else:
                return np.asarray(self.rng.normal(0, 1, n), dtype=float)
        elif hint == 'skewed':
            skew_param = self.spec.target_skewness or 1.5
            points = np.asarray(
                stats.skewnorm.rvs(a=skew_param, size=n, random_state=self.rng),
                dtype=float,
            )
            if dist_range:
                # 对偏态分布也应用截断
                points = np.clip(points, dist_range[0], dist_range[1])
            return points
        elif hint == 'bimodal':
            n1, n2 = n // 2, n - n // 2
            points1 = self.rng.normal(-1, 0.5, n1)
            points2 = self.rng.normal(1, 0.5, n2)
            points = np.concatenate([points1, points2])
            self.rng.shuffle(points)
            if dist_range:
                points = np.clip(points, dist_range[0], dist_range[1])
            return points
        elif hint == 'uniform':
            if dist_range:
                return np.asarray(
                    self.rng.uniform(dist_range[0], dist_range[1], n), dtype=float
                )
            else:
                return np.asarray(self.rng.uniform(-1, 1, n), dtype=float)
        elif hint == 'exponential':
            points = np.asarray(self.rng.exponential(1, n), dtype=float)
            if dist_range:
                points = np.clip(points, dist_range[0], dist_range[1])
            return points
        elif hint == 'beta':
            points = np.asarray(
                stats.beta.rvs(2, 2, size=n, random_state=self.rng), dtype=float
            )
            if dist_range:
                # beta分布默认在[0,1]，需要缩放到指定范围
                points = points * (dist_range[1] - dist_range[0]) + dist_range[0]
            return points
        else:
            # 默认正态分布
            if dist_range:
                mean, std = 0, 1
                a, b = (dist_range[0] - mean) / std, (dist_range[1] - mean) / std
                return np.asarray(
                    stats.truncnorm.rvs(
                        a, b, loc=mean, scale=std, size=n, random_state=self.rng
                    ),
                    dtype=float,
                )
            else:
                return np.asarray(self.rng.normal(0, 1, n), dtype=float)

    def _adjust_statistics(self, points: NDArray) -> NDArray:
        """调整统计特性以匹配目标"""
        # 调整均值
        if self.spec.target_mean is not None:
            current_mean = np.mean(points)
            points = points - current_mean + self.spec.target_mean

        # 调整标准差
        if self.spec.target_std is not None:
            current_std = np.std(points, ddof=1)
            if current_std > 1e-10:  # 避免除零
                scale_factor = self.spec.target_std / current_std
                reference_mean = (
                    self.spec.target_mean
                    if self.spec.target_mean is not None
                    else float(np.mean(points))
                )
                points = (points - reference_mean) * scale_factor + reference_mean

        return points


class OptimizationGenerator(BaseStatGenerator):
    """
    优化求解策略

    算法流程：
    1. 定义多目标优化函数（统计误差的加权和）
    2. 选择合适的初始猜测
    3. 使用数值优化算法求解
    4. 添加异常值和应用约束

    优势：
    - 理论上可精确控制多个统计量
    - 极高的灵活性
    - 易于扩展新的统计约束

    劣势：
    - 计算开销较大
    - 可能收敛到局部最优
    - 生成的分布可能不够"自然"
    """

    def generate(self) -> GenerationResult:
        """执行优化求解策略"""
        start_time = time.time()
        self._set_defaults()

        try:
            # 考虑异常值，先生成核心点
            n_core = self.spec.n_points
            if self.spec.outliers:
                n_core -= self.spec.outliers.n_outliers

            # 1. 生成核心点
            core_points, convergence_info = self._optimize_core_points(n_core)

            # 2. 添加异常值
            final_points, outlier_indices = self._add_outliers(core_points)

            # 3. 应用最终的绝对范围约束
            final_points = self._apply_final_clip(final_points)

            # 4. 计算结果统计量
            actual_stats = self._calculate_stats(final_points)
            target_stats = self._get_target_stats()

            generation_time = time.time() - start_time

            return GenerationResult(
                spec=self.spec,
                points=final_points,
                actual_stats=actual_stats,
                target_stats=target_stats,
                strategy_used='optimization',
                generation_time=generation_time,
                success=convergence_info.get('success', True),
                convergence_info=convergence_info,
                outlier_indices=outlier_indices,
            )

        except Exception as e:
            return GenerationResult(
                spec=self.spec,
                points=np.array([]),
                actual_stats={},
                target_stats=self._get_target_stats(),
                strategy_used='optimization',
                generation_time=time.time() - start_time,
                success=False,
                warnings=[f'Optimization failed: {str(e)}'],
            )

    def _optimize_core_points(self, n_points: int) -> Tuple[NDArray, Dict]:
        """优化核心数据点"""
        if n_points <= 0:
            return np.array([]), {'success': True, 'message': 'No core points needed'}

        # 1. 生成初始猜测
        initial_guess = self._get_initial_guess(n_points)

        # 2. 定义目标函数
        objective_func = self._create_objective_function()

        # 3. 设置约束（使用 distribution_range 作为优化边界）
        bounds = None
        if self.spec.distribution_range:
            bounds = optimize.Bounds(
                self.spec.distribution_range[0], self.spec.distribution_range[1]
            )

        # 4. 执行优化
        result = optimize.minimize(
            objective_func,
            initial_guess,
            method='L-BFGS-B',
            bounds=bounds,
            options={'maxiter': self.spec.max_iterations, 'ftol': 1e-9, 'gtol': 1e-6},
        )

        convergence_info = {
            'success': result.success,
            'message': result.message,
            'n_iterations': result.nit,
            'final_error': result.fun,
            'optimization_method': 'L-BFGS-B',
        }

        return result.x, convergence_info

    def _get_initial_guess(self, n_points: int) -> NDArray:
        """生成优化的初始猜测"""
        mean = self.spec.target_mean or 0.0
        std = self.spec.target_std or 1.0

        # 根据分布提示选择初始分布
        if self.spec.distribution_hint == 'uniform':
            if self.spec.distribution_range:
                return self.rng.uniform(
                    self.spec.distribution_range[0],
                    self.spec.distribution_range[1],
                    n_points,
                )
            else:
                width = std * np.sqrt(12)
                return self.rng.uniform(mean - width / 2, mean + width / 2, n_points)
        else:
            # 默认从目标正态分布开始
            points = self.rng.normal(mean, std, n_points)
            if self.spec.distribution_range:
                points = np.clip(
                    points,
                    self.spec.distribution_range[0],
                    self.spec.distribution_range[1],
                )
            return points

    def _create_objective_function(self) -> Callable[[NDArray], float]:
        """创建多目标优化函数"""

        # 权重设计：不同统计量的重要性
        weights = {
            'mean': 10.0,  # 均值最重要
            'std': 5.0,  # 标准差次重要
            'median': 2.0,  # 中位数
            'skewness': 1.0,  # 偏度
            'kurtosis': 0.5,  # 峰度最不重要
        }

        def objective_function(x: NDArray) -> float:
            """
            多目标优化函数
            返回所有统计误差的加权和
            """
            if len(x) == 0:
                return float('inf')

            total_error = 0.0

            # 均值误差
            if self.spec.target_mean is not None:
                mean_error = (np.mean(x) - self.spec.target_mean) ** 2
                total_error += weights['mean'] * mean_error

            # 标准差误差
            if self.spec.target_std is not None and len(x) > 1:
                std_error = (np.std(x, ddof=1) - self.spec.target_std) ** 2
                total_error += weights['std'] * std_error

            # 中位数误差
            if self.spec.target_median is not None:
                median_error = (np.median(x) - self.spec.target_median) ** 2
                total_error += weights['median'] * median_error

            # 偏度误差
            if self.spec.target_skewness is not None and len(x) > 2:
                try:
                    skew_error = (stats.skew(x) - self.spec.target_skewness) ** 2
                    total_error += weights['skewness'] * skew_error
                except Exception:
                    pass  # 忽略计算错误

            # 峰度误差
            if self.spec.target_kurtosis is not None and len(x) > 3:
                try:
                    kurt_error = (stats.kurtosis(x) - self.spec.target_kurtosis) ** 2
                    total_error += weights['kurtosis'] * kurt_error
                except Exception:
                    pass  # 忽略计算错误

            return float(total_error)

        return objective_function


class HybridGenerator(BaseStatGenerator):
    """
    混合策略

    算法流程：
    1. 使用生成式方法获得高质量初始分布
    2. 检查是否满足精度要求
    3. 如需优化，使用轻量级优化进行微调
    4. 保持原始分布特征的同时满足统计约束

    优势：
    - 结合两种方法的优势
    - 生成速度快且结果精确
    - 保持分布的自然性

    特点：
    - 智能选择：根据约束复杂度自适应
    - 温和优化：避免过度拟合破坏分布形状
    """

    def generate(self) -> GenerationResult:
        """执行混合策略"""
        start_time = time.time()
        self._set_defaults()

        try:
            # 阶段1：生成高质量初始分布
            generative_gen = GenerativeAdjustGenerator(self.spec)
            initial_result = generative_gen.generate()

            if not initial_result.success:
                return initial_result

            initial_points = initial_result.points

            # 阶段2：评估是否需要优化
            needs_optimization, error_info = self._evaluate_need_for_optimization(
                initial_points
            )

            if needs_optimization:
                # 阶段3：温和优化调整
                optimized_points, opt_info = self._gentle_optimization(initial_points)
                final_points = optimized_points
                strategy_detail = 'hybrid(generative+optimization)'
            else:
                final_points = initial_points
                opt_info = {'skipped': True, 'reason': 'precision_sufficient'}
                strategy_detail = 'hybrid(generative_only)'

            # 计算最终统计量
            actual_stats = self._calculate_stats(final_points)
            target_stats = self._get_target_stats()

            generation_time = time.time() - start_time

            # 合并收敛信息
            convergence_info = {
                'initial_errors': error_info,
                'optimization_info': opt_info,
                'needs_optimization': needs_optimization,
            }

            return GenerationResult(
                spec=self.spec,
                points=final_points,
                actual_stats=actual_stats,
                target_stats=target_stats,
                strategy_used=strategy_detail,
                generation_time=generation_time,
                success=True,
                convergence_info=convergence_info,
                outlier_indices=initial_result.outlier_indices,
            )

        except Exception as e:
            return GenerationResult(
                spec=self.spec,
                points=np.array([]),
                actual_stats={},
                target_stats=self._get_target_stats(),
                strategy_used='hybrid',
                generation_time=time.time() - start_time,
                success=False,
                warnings=[f'Hybrid generation failed: {str(e)}'],
            )

    def _evaluate_need_for_optimization(
        self, points: NDArray
    ) -> Tuple[bool, Dict[str, object]]:
        """评估是否需要优化调整"""
        errors: Dict[str, float] = {}
        total_relative_error = 0.0
        constraint_count = 0

        # 计算各项相对误差
        if self.spec.target_mean is not None:
            actual_mean = np.mean(points)
            rel_error = float(
                abs(actual_mean - self.spec.target_mean)
                / max(abs(self.spec.target_mean), 1e-10)
            )
            errors['mean'] = rel_error
            total_relative_error += rel_error
            constraint_count += 1

        if self.spec.target_std is not None and len(points) > 1:
            actual_std = np.std(points, ddof=1)
            rel_error = float(
                abs(actual_std - self.spec.target_std) / max(self.spec.target_std, 1e-10)
            )
            errors['std'] = rel_error
            total_relative_error += rel_error
            constraint_count += 1

        if self.spec.target_skewness is not None and len(points) > 2:
            actual_skew = stats.skew(points)
            rel_error = float(
                abs(actual_skew - self.spec.target_skewness)
                / max(abs(self.spec.target_skewness), 1.0)
            )
            errors['skewness'] = rel_error
            total_relative_error += rel_error
            constraint_count += 1

        if self.spec.target_median is not None:
            actual_median = np.median(points)
            rel_error = float(
                abs(actual_median - self.spec.target_median)
                / max(abs(self.spec.target_median), 1e-10)
            )
            errors['median'] = rel_error
            total_relative_error += rel_error
            constraint_count += 1

        # 决策逻辑
        if constraint_count == 0:
            needs_opt = False
        else:
            avg_error = total_relative_error / constraint_count
            needs_opt = bool(avg_error > self.spec.precision_tolerance)

        error_info: Dict[str, object] = {
            'individual_errors': errors,
            'average_error': total_relative_error / max(constraint_count, 1),
            'tolerance': self.spec.precision_tolerance,
            'constraint_count': constraint_count,
        }

        return needs_opt, error_info

    def _gentle_optimization(self, initial_points: NDArray) -> Tuple[NDArray, Dict]:
        """温和的优化调整，保持分布特征"""

        def gentle_objective(x: NDArray) -> float:
            """温和的目标函数，平衡约束满足和形状保持"""

            # 统计约束误差（降低权重避免过度拟合）
            constraint_error = 0.0
            weights = {'mean': 1.0, 'std': 1.0, 'skew': 0.5, 'median': 0.8}

            if self.spec.target_mean is not None:
                constraint_error += (
                    weights['mean'] * (np.mean(x) - self.spec.target_mean) ** 2
                )

            if self.spec.target_std is not None and len(x) > 1:
                constraint_error += (
                    weights['std'] * (np.std(x, ddof=1) - self.spec.target_std) ** 2
                )

            if self.spec.target_skewness is not None and len(x) > 2:
                try:
                    constraint_error += (
                        weights['skew'] * (stats.skew(x) - self.spec.target_skewness) ** 2
                    )
                except Exception:
                    pass

            if self.spec.target_median is not None:
                constraint_error += (
                    weights['median'] * (np.median(x) - self.spec.target_median) ** 2
                )

            # 形状保持惩罚：避免过度偏离初始分布
            shape_penalty = np.mean((x - initial_points) ** 2)

            # 总目标：平衡约束满足和形状保持
            total_error = constraint_error + 0.1 * shape_penalty

            return float(total_error)

        # 设置约束（使用 distribution_range）
        bounds = None
        if self.spec.distribution_range:
            bounds = optimize.Bounds(
                self.spec.distribution_range[0], self.spec.distribution_range[1]
            )

        # 执行轻量级优化
        result = optimize.minimize(
            gentle_objective,
            initial_points,
            method='L-BFGS-B',
            bounds=bounds,
            options={
                'maxiter': min(50, self.spec.max_iterations // 4),  # 限制迭代次数
                'ftol': 1e-6,
                'gtol': 1e-4,
            },
        )

        opt_info = {
            'success': result.success,
            'n_iterations': result.nit,
            'final_error': result.fun,
            'method': 'gentle_L-BFGS-B',
        }

        return result.x if result.success else initial_points, opt_info


class StrategySelector:
    """智能策略选择器"""

    @staticmethod
    def select_optimal_strategy(spec: StatSpec) -> GenerationStrategy:
        """
        根据规格自动选择最优策略

        选择逻辑：
        1. 复杂分布形状 → Generative
        2. 高精度 + 多约束 → Optimization
        3. 大样本量 → Generative
        4. 平衡需求 → Hybrid
        """

        # 1. 分析约束复杂度
        constraint_count = sum(
            [
                spec.target_mean is not None,
                spec.target_std is not None,
                spec.target_median is not None,
                spec.target_skewness is not None,
                spec.target_kurtosis is not None,
            ]
        )

        # 2. 复杂分布形状检测
        complex_distributions = ['bimodal', 'exponential']
        if spec.distribution_hint in complex_distributions:
            return GenerationStrategy.GENERATIVE

        # 3. 高精度多约束场景
        if constraint_count >= 3 and spec.precision_tolerance < 0.02:
            return GenerationStrategy.OPTIMIZATION

        # 4. 大样本量场景（生成式更快）
        if spec.n_points > 1000:
            return GenerationStrategy.GENERATIVE

        # 5. 简单场景
        if constraint_count <= 1:
            return GenerationStrategy.GENERATIVE

        # 6. 默认使用混合策略
        return GenerationStrategy.HYBRID


class UnifiedStatGenerator:
    """
    统一统计点生成器

    这是主要的用户接口，支持三种生成策略：
    - GenerativeAdjust: 快速生成，基于标准分布
    - Optimization: 精确控制，基于数值优化
    - Hybrid: 智能混合，平衡速度和精度
    """

    def __init__(self, spec: StatSpec):
        self.spec = spec

    def generate(self) -> GenerationResult:
        """根据策略生成统计点"""

        # 自动策略选择
        if self.spec.strategy == GenerationStrategy.AUTO:
            self.spec.strategy = StrategySelector.select_optimal_strategy(self.spec)

        # 选择对应的生成器
        if self.spec.strategy == GenerationStrategy.GENERATIVE:
            generator = GenerativeAdjustGenerator(self.spec)
        elif self.spec.strategy == GenerationStrategy.OPTIMIZATION:
            generator = OptimizationGenerator(self.spec)
        elif self.spec.strategy == GenerationStrategy.HYBRID:
            generator = HybridGenerator(self.spec)
        else:
            raise ValueError(f'Unknown strategy: {self.spec.strategy}')

        return generator.generate()


# === 便捷接口函数 ===


def generate_stat_points(spec: StatSpec) -> GenerationResult:
    """
    生成统计点的便捷函数

    Args:
        spec: 统计规格说明

    Returns:
        GenerationResult: 生成结果

    Example:
        >>> spec = StatSpec(
        ...     n_points=100,
        ...     target_mean=0.85,
        ...     target_std=0.05,
        ...     strategy=GenerationStrategy.AUTO
        ... )
        >>> result = generate_stat_points(spec)
        >>> print(f"Generated {len(result.points)} points")
    """
    generator = UnifiedStatGenerator(spec)
    return generator.generate()


def compare_strategies(spec: StatSpec) -> Dict[str, GenerationResult]:
    """
    比较三种策略的效果

    Args:
        spec: 统计规格说明

    Returns:
        Dict[str, GenerationResult]: 各策略的生成结果
    """
    strategies = [
        GenerationStrategy.GENERATIVE,
        GenerationStrategy.OPTIMIZATION,
        GenerationStrategy.HYBRID,
    ]

    results = {}
    for strategy in strategies:
        spec_copy = StatSpec(**spec.__dict__)
        spec_copy.strategy = strategy

        generator = UnifiedStatGenerator(spec_copy)
        result = generator.generate()
        results[strategy.value] = result

    return results


def analyze_generation_quality(result: GenerationResult) -> Dict[str, float]:
    """
    分析生成质量

    Args:
        result: 生成结果

    Returns:
        Dict[str, float]: 质量指标
    """
    if not result.success or len(result.points) == 0:
        return {'overall_score': 0.0}

    quality_metrics = {}

    # 计算各项相对误差
    errors = []
    for stat_name in ['mean', 'std', 'median', 'skewness', 'kurtosis']:
        if stat_name in result.target_stats and stat_name in result.actual_stats:
            target = result.target_stats[stat_name]
            actual = result.actual_stats[stat_name]

            if abs(target) > 1e-10:
                rel_error = abs(actual - target) / abs(target)
            else:
                rel_error = abs(actual - target)

            quality_metrics[f'{stat_name}_error'] = rel_error
            errors.append(rel_error)

    # 整体质量分数
    if errors:
        avg_error = np.mean(errors)
        quality_metrics['average_relative_error'] = avg_error
        quality_metrics['overall_score'] = max(0.0, 1.0 - avg_error)
    else:
        quality_metrics['overall_score'] = 1.0

    # 时间效率
    quality_metrics['generation_time'] = result.generation_time
    quality_metrics['efficiency_score'] = min(
        1.0, 1.0 / max(result.generation_time, 0.001)
    )

    return quality_metrics
