"""
反馈与学习示例 (Feedback and Learning Example)
=============================================

本示例展示如何在空间智能系统中实现反馈机制和学习能力。
反馈和学习使系统能够从经验中改进，提高决策质量。

核心概念:
1. 反馈循环 - 收集用户/系统的反馈
2. 性能评估 - 评估决策效果
3. 参数调整 - 根据反馈调整系统参数
4. 经验存储 - 保存和检索历史经验
5. 迁移学习 - 将知识应用到新场景

应用场景:
- 自适应决策权重调整
- 模型参数优化
- 用户偏好学习
- 决策效果跟踪

作者: CC4SI 项目组
"""

import math
import json
from typing import List, Dict, Tuple, Optional, Any, Callable
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
import random


# ============================================================================
# 反馈类型定义
# ============================================================================

class FeedbackType(Enum):
    """反馈类型枚举"""
    EXPLICIT = "explicit"       # 显式反馈 (用户评分/评价)
    IMPLICIT = "implicit"       # 隐式反馈 (行为数据)
    OUTCOME = "outcome"         # 结果反馈 (实际结果)
    CORRECTION = "correction"   # 纠正反馈 (修正建议)
    RANKING = "ranking"         # 排序反馈 (偏好排序)


class FeedbackSource(Enum):
    """反馈来源枚举"""
    HUMAN_EXPERT = "human_expert"    # 人类专家
    SYSTEM_AUTO = "system_auto"      # 系统自动
    SENSOR_DATA = "sensor_data"      # 传感器数据
    CROWDSOURCING = "crowdsourcing"  # 众包
    PEER_REVIEW = "peer_review"      # 同行评审


@dataclass
class Feedback:
    """
    反馈数据结构

    表示一次具体的反馈事件。
    """
    feedback_id: str
    feedback_type: FeedbackType
    source: FeedbackSource
    target_decision_id: str
    value: float  # 反馈值 (如评分)
    content: Optional[str] = None  # 反馈内容
    metadata: Dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=datetime.now)

    def __repr__(self) -> str:
        return f"Feedback({self.feedback_type.value}, value={self.value:.2f})"


# ============================================================================
# 决策记录
# ============================================================================

@dataclass
class Decision:
    """
    决策记录

    保存系统做出的一次决策的完整信息。
    """
    decision_id: str
    context: Dict[str, Any]  # 决策上下文
    alternatives: List[Dict[str, Any]]  # 可选方案
    selected_alternative: int  # 选择的方案索引
    model_version: str  # 使用的模型版本
    parameters: Dict[str, Any]  # 决策参数
    predicted_outcome: Optional[float] = None  # 预测结果
    actual_outcome: Optional[float] = None  # 实际结果
    feedback_list: List[Feedback] = field(default_factory=list)
    timestamp: datetime = field(default_factory=datetime.now)

    def add_feedback(self, feedback: Feedback) -> None:
        """添加反馈"""
        self.feedback_list.append(feedback)

    def get_average_feedback(self) -> float:
        """获取平均反馈分数"""
        if not self.feedback_list:
            return 0.0
        return sum(f.value for f in self.feedback_list) / len(self.feedback_list)

    def get_outcome_error(self) -> Optional[float]:
        """获取预测误差"""
        if self.predicted_outcome is not None and self.actual_outcome is not None:
            return abs(self.predicted_outcome - self.actual_outcome)
        return None

    def calculate_regret(self) -> float:
        """
        计算后悔值

        后悔值 = 最优选择的结果 - 实际选择的结果
        """
        if not self.alternatives or self.actual_outcome is None:
            return 0.0

        # 假设alternatives中存储了各个选项的实际结果
        best_outcome = max(
            alt.get("actual_outcome", self.actual_outcome)
            for alt in self.alternatives
        )
        return best_outcome - self.actual_outcome


# ============================================================================
# 经验存储
# ============================================================================

class ExperienceStore:
    """
    经验存储

    存储和检索历史决策经验，用于学习和改进。
    """

    def __init__(self, capacity: int = 1000):
        """
        初始化经验存储

        Args:
            capacity: 最大存储容量
        """
        self.capacity = capacity
        self.decisions: Dict[str, Decision] = {}
        self.decision_list: List[str] = []  # 按时间顺序的ID列表

    def add_decision(self, decision: Decision) -> None:
        """添加决策记录"""
        self.decisions[decision.decision_id] = decision
        self.decision_list.append(decision.decision_id)

        # 超过容量时删除最旧的
        if len(self.decision_list) > self.capacity:
            oldest_id = self.decision_list.pop(0)
            del self.decisions[oldest_id]

    def get_decision(self, decision_id: str) -> Optional[Decision]:
        """获取决策记录"""
        return self.decisions.get(decision_id)

    def get_recent_decisions(self, n: int = 10) -> List[Decision]:
        """获取最近的n条决策"""
        recent_ids = self.decision_list[-n:]
        return [self.decisions[id] for id in recent_ids]

    def find_similar_decisions(self, context: Dict[str, Any],
                               threshold: float = 0.8) -> List[Decision]:
        """
        查找相似上下文的决策

        Args:
            context: 目标上下文
            threshold: 相似度阈值

        Returns:
            相似决策列表
        """
        similar = []

        for decision in self.decisions.values():
            similarity = self._calculate_similarity(context, decision.context)
            if similarity >= threshold:
                similar.append((decision, similarity))

        similar.sort(key=lambda x: x[1], reverse=True)
        return [d for d, _ in similar]

    def _calculate_similarity(self, ctx1: Dict[str, Any],
                             ctx2: Dict[str, Any]) -> float:
        """计算上下文相似度 (简化版本)"""
        # 简化: 使用键的交集比例
        keys1 = set(ctx1.keys())
        keys2 = set(ctx2.keys())
        intersection = keys1 & keys2
        union = keys1 | keys2

        if not union:
            return 0.0

        # 值相似度
        value_similarity = 0.0
        count = 0

        for key in intersection:
            v1 = ctx1.get(key)
            v2 = ctx2.get(key)
            if isinstance(v1, (int, float)) and isinstance(v2, (int, float)):
                # 归一化差异
                max_val = max(abs(v1), abs(v2), 1)
                diff = abs(v1 - v2) / max_val
                value_similarity += (1 - diff)
                count += 1

        if count > 0:
            value_similarity /= count

        # 组合相似度
        key_similarity = len(intersection) / len(union)
        return 0.3 * key_similarity + 0.7 * value_similarity

    def get_statistics(self) -> Dict[str, Any]:
        """获取统计信息"""
        total = len(self.decisions)

        if total == 0:
            return {"total_decisions": 0}

        with_feedback = sum(1 for d in self.decisions.values() if d.feedback_list)
        with_outcome = sum(1 for d in self.decisions.values()
                          if d.actual_outcome is not None)

        avg_feedback = sum(d.get_average_feedback()
                          for d in self.decisions.values()
                          if d.feedback_list) / max(with_feedback, 1)

        return {
            "total_decisions": total,
            "decisions_with_feedback": with_feedback,
            "decisions_with_outcome": with_outcome,
            "average_feedback_score": avg_feedback
        }


# ============================================================================
# 学习器接口
# ============================================================================

class Learner(ABC):
    """学习器抽象基类"""

    def __init__(self, name: str = ""):
        self.name = name

    @abstractmethod
    def learn_from_feedback(self, decision: Decision, feedback: Feedback) -> None:
        """从反馈中学习"""
        pass

    @abstractmethod
    def learn_from_outcome(self, decision: Decision) -> None:
        """从结果中学习"""
        pass

    @abstractmethod
    def get_parameters(self) -> Dict[str, Any]:
        """获取当前参数"""
        pass

    @abstractmethod
    def update_parameters(self, params: Dict[str, Any]) -> None:
        """更新参数"""
        pass


# ============================================================================
# 权重学习器
# ============================================================================

class WeightLearner(Learner):
    """
    权重学习器

    通过反馈学习多准则决策的权重。
    """

    def __init__(self, initial_weights: List[float],
                 learning_rate: float = 0.1,
                 min_weight: float = 0.05,
                 max_weight: float = 0.5):
        """
        初始化权重学习器

        Args:
            initial_weights: 初始权重列表
            learning_rate: 学习率
            min_weight: 最小权重
            max_weight: 最大权重
        """
        super().__init__("WeightLearner")
        self.weights = initial_weights.copy()
        self.learning_rate = learning_rate
        self.min_weight = min_weight
        self.max_weight = max_weight
        self.update_count = 0

    def learn_from_feedback(self, decision: Decision, feedback: Feedback) -> None:
        """
        从反馈中学习权重

        使用梯度下降法调整权重:
        - 如果反馈为正，增加选中选项的优势准则权重
        - 如果反馈为负，减少选中选项的优势准则权重
        """
        if not decision.alternatives or decision.selected_alternative >= len(decision.alternatives):
            return

        selected = decision.alternatives[decision.selected_alternative]

        # 计算调整方向
        feedback_normalized = (feedback.value - 0.5) * 2  # 转换到 [-1, 1]

        # 获取准则值 (假设存储在criteria字段)
        criteria_values = selected.get("criteria", [])

        if len(criteria_values) != len(self.weights):
            return

        # 计算梯度
        # 简化: 增加高值准则的权重 (如果反馈为正)
        max_value = max(criteria_values) if criteria_values else 1
        gradients = []

        for i, value in enumerate(criteria_values):
            # 归一化值
            norm_value = value / max_value if max_value > 0 else 0
            # 梯度: 高值准则应该有更大权重
            gradient = (norm_value - 0.5) * feedback_normalized
            gradients.append(gradient)

        # 更新权重
        for i, gradient in enumerate(gradients):
            self.weights[i] += self.learning_rate * gradient

        # 归一化权重
        self._normalize_weights()
        self.update_count += 1

    def learn_from_outcome(self, decision: Decision) -> None:
        """
        从结果中学习

        如果实际结果好于预期，增加选中策略的权重
        """
        if decision.predicted_outcome is None or decision.actual_outcome is None:
            return

        # 计算结果误差
        error = decision.actual_outcome - decision.predicted_outcome

        # 归一化误差
        error_normalized = math.tanh(error / 100)  # 假设100为合理的误差范围

        # 根据误差调整权重
        feedback = Feedback(
            feedback_id=f"outcome_{decision.decision_id}",
            feedback_type=FeedbackType.OUTCOME,
            source=FeedbackSource.SYSTEM_AUTO,
            target_decision_id=decision.decision_id,
            value=0.5 + error_normalized * 0.25  # 转换到合理范围
        )

        self.learn_from_feedback(decision, feedback)

    def _normalize_weights(self) -> None:
        """归一化权重并限制范围"""
        # 限制范围
        self.weights = [
            max(self.min_weight, min(self.max_weight, w))
            for w in self.weights
        ]

        # 归一化使和为1
        total = sum(self.weights)
        self.weights = [w / total for w in self.weights]

    def get_parameters(self) -> Dict[str, Any]:
        return {
            "weights": self.weights,
            "learning_rate": self.learning_rate,
            "update_count": self.update_count
        }

    def update_parameters(self, params: Dict[str, Any]) -> None:
        if "weights" in params:
            self.weights = params["weights"].copy()
        if "learning_rate" in params:
            self.learning_rate = params["learning_rate"]


# ============================================================================
# 自适应决策系统
# ============================================================================

class AdaptiveDecisionSystem:
    """
    自适应决策系统

    结合反馈和学习的智能决策系统。
    """

    def __init__(self, criteria: List[str],
                 initial_weights: List[float] = None):
        """
        初始化自适应决策系统

        Args:
            criteria: 决策准则列表
            initial_weights: 初始权重
        """
        self.criteria = criteria
        self.n_criteria = len(criteria)

        if initial_weights is None:
            # 均匀初始权重
            initial_weights = [1.0 / self.n_criteria] * self.n_criteria

        # 归一化权重
        total = sum(initial_weights)
        self.weights = [w / total for w in initial_weights]

        # 创建学习器
        self.learner = WeightLearner(self.weights)

        # 创建经验存储
        self.experience_store = ExperienceStore()

        # 决策计数器
        self.decision_counter = 0

        print(f"[自适应决策系统] 初始化完成")
        print(f"  准则: {self.criteria}")
        print(f"  初始权重: {[f'{w:.3f}' for w in self.weights]}")

    def make_decision(self, alternatives: List[Dict[str, float]],
                     context: Dict[str, Any] = None) -> Tuple[int, Dict[str, Any]]:
        """
        做出决策

        Args:
            alternatives: 备选方案列表，每个方案包含各准则的值
            context: 决策上下文

        Returns:
            (选中方案索引, 决策信息)
        """
        if not alternatives:
            raise ValueError("没有备选方案")

        # 计算每个方案的综合得分
        scores = []
        for alt in alternatives:
            score = self._calculate_score(alt)
            scores.append(score)

        # 选择得分最高的
        selected_idx = max(range(len(scores)), key=lambda i: scores[i])

        # 创建决策记录
        decision_id = f"decision_{self.decision_counter}"
        self.decision_counter += 1

        decision = Decision(
            decision_id=decision_id,
            context=context or {},
            alternatives=[
                {"criteria": alt, "score": score}
                for alt, score in zip(alternatives, scores)
            ],
            selected_alternative=selected_idx,
            model_version="1.0",
            parameters=self.get_parameters()
        )

        decision_info = {
            "decision_id": decision_id,
            "selected_index": selected_idx,
            "selected_alternative": alternatives[selected_idx],
            "score": scores[selected_idx],
            "all_scores": scores,
            "weights": self.weights.copy()
        }

        # 存储决策
        self.experience_store.add_decision(decision)

        return selected_idx, decision_info

    def _calculate_score(self, alternative: Dict[str, float]) -> float:
        """
        计算方案的综合得分

        使用加权求和模型
        """
        score = 0.0
        for i, criterion in enumerate(self.criteria):
            if criterion in alternative:
                score += self.weights[i] * alternative[criterion]
        return score

    def provide_feedback(self, decision_id: str, feedback_value: float,
                        feedback_type: FeedbackType = FeedbackType.EXPLICIT,
                        source: FeedbackSource = FeedbackSource.HUMAN_EXPERT,
                        content: str = None) -> None:
        """
        为决策提供反馈

        Args:
            decision_id: 决策ID
            feedback_value: 反馈值 (通常在0-1范围)
            feedback_type: 反馈类型
            source: 反馈来源
            content: 反馈内容
        """
        decision = self.experience_store.get_decision(decision_id)
        if not decision:
            print(f"警告: 找不到决策 {decision_id}")
            return

        # 创建反馈
        feedback = Feedback(
            feedback_id=f"fb_{decision_id}_{len(decision.feedback_list)}",
            feedback_type=feedback_type,
            source=source,
            target_decision_id=decision_id,
            value=feedback_value,
            content=content
        )

        # 添加到决策记录
        decision.add_feedback(feedback)

        # 从反馈中学习
        self.learner.learn_from_feedback(decision, feedback)

        # 更新系统权重
        self.weights = self.learner.weights.copy()

        print(f"[反馈] 收到反馈: {feedback_value:.2f}")
        print(f"[学习] 更新后权重: {[f'{w:.3f}' for w in self.weights]}")

    def report_outcome(self, decision_id: str, actual_outcome: float) -> None:
        """
        报告实际结果

        Args:
            decision_id: 决策ID
            actual_outcome: 实际结果值
        """
        decision = self.experience_store.get_decision(decision_id)
        if not decision:
            print(f"警告: 找不到决策 {decision_id}")
            return

        decision.actual_outcome = actual_outcome

        # 从结果中学习
        self.learner.learn_from_outcome(decision)

        # 更新系统权重
        self.weights = self.learner.weights.copy()

        print(f"[结果] 决策 {decision_id} 实际结果: {actual_outcome:.2f}")

    def get_parameters(self) -> Dict[str, Any]:
        """获取当前系统参数"""
        return {
            "weights": self.weights.copy(),
            "criteria": self.criteria.copy()
        }

    def set_parameters(self, params: Dict[str, Any]) -> None:
        """设置系统参数"""
        if "weights" in params:
            self.weights = params["weights"].copy()

    def get_performance_summary(self) -> Dict[str, Any]:
        """获取性能摘要"""
        stats = self.experience_store.get_statistics()

        # 计算平均反馈分数
        decisions = list(self.experience_store.decisions.values())
        if decisions:
            avg_feedback = sum(d.get_average_feedback()
                             for d in decisions if d.feedback_list)
            feedback_count = sum(1 for d in decisions if d.feedback_list)
            avg_feedback = avg_feedback / feedback_count if feedback_count > 0 else None
        else:
            avg_feedback = None

        # 计算平均后悔值
        regrets = [d.calculate_regret() for d in decisions
                  if d.actual_outcome is not None]
        avg_regret = sum(regrets) / len(regrets) if regrets else None

        return {
            "total_decisions": stats["total_decisions"],
            "decisions_with_feedback": stats["decisions_with_feedback"],
            "average_feedback_score": avg_feedback,
            "average_regret": avg_regret,
            "current_weights": self.weights.copy()
        }

    def print_summary(self) -> None:
        """打印系统摘要"""
        print("\n" + "="*60)
        print("自适应决策系统摘要")
        print("="*60)

        print("\n决策准则:")
        for i, criterion in enumerate(self.criteria):
            print(f"  {i+1}. {criterion:15s} 权重: {self.weights[i]:.4f}")

        perf = self.get_performance_summary()
        print(f"\n性能统计:")
        print(f"  总决策数:       {perf['total_decisions']}")
        print(f"  有反馈的决策:   {perf['decisions_with_feedback']}")

        if perf['average_feedback_score'] is not None:
            print(f"  平均反馈分数:   {perf['average_feedback_score']:.3f}")

        if perf['average_regret'] is not None:
            print(f"  平均后悔值:     {perf['average_regret']:.3f}")

        print("="*60 + "\n")


# ============================================================================
# 主程序
# ============================================================================

def main():
    """主程序 - 演示反馈与学习的使用"""

    print("="*70)
    print("反馈与学习示例演示")
    print("="*70)

    # ========================================================================
    # 1. 创建自适应决策系统
    # ========================================================================
    print("\n[步骤 1] 创建自适应决策系统")
    print("-" * 50)

    criteria = ["经济效益", "环境影响", "社会影响", "技术可行性"]
    initial_weights = [0.4, 0.3, 0.2, 0.1]  # 偏重经济效益

    system = AdaptiveDecisionSystem(criteria, initial_weights)
    system.print_summary()

    # ========================================================================
    # 2. 第一次决策
    # ========================================================================
    print("\n[步骤 2] 第一次决策 - 工厂选址")
    print("-" * 50)

    alternatives = [
        {"经济效益": 0.8, "环境影响": 0.3, "社会影响": 0.5, "技术可行性": 0.9},  # 位置A
        {"经济效益": 0.5, "环境影响": 0.7, "社会影响": 0.8, "技术可行性": 0.6},  # 位置B
        {"经济效益": 0.6, "环境影响": 0.9, "社会影响": 0.6, "技术可行性": 0.7},  # 位置C
    ]

    selected_idx, decision_info = system.make_decision(
        alternatives,
        context={"task": "工厂选址", "region": "华东地区"}
    )

    print(f"\n决策结果:")
    print(f"  选中方案: 位置{chr(65 + selected_idx)}")
    print(f"  得分: {decision_info['score']:.3f}")
    print(f"  各方案得分: {[f'{s:.2f}' for s in decision_info['all_scores']]}")

    decision_id_1 = decision_info['decision_id']

    # ========================================================================
    # 3. 提供反馈
    # ========================================================================
    print("\n[步骤 3] 收集反馈")
    print("-" * 50)

    # 专家反馈: 环境影响被低估了
    print("\n3.1 专家反馈: 环境影响应该更重视")
    system.provide_feedback(
        decision_id_1,
        feedback_value=0.6,  # 中等偏下的评分
        feedback_type=FeedbackType.EXPLICIT,
        source=FeedbackSource.HUMAN_EXPERT,
        content="环境影响权重太低，应提高"
    )

    # 更多反馈强化
    system.provide_feedback(
        decision_id_1,
        feedback_value=0.5,
        feedback_type=FeedbackType.CORRECTION,
        source=FeedbackSource.HUMAN_EXPERT
    )

    # ========================================================================
    # 4. 第二次决策 (学习后的权重)
    # ========================================================================
    print("\n[步骤 4] 第二次决策 - 另一个选址")
    print("-" * 50)

    alternatives_2 = [
        {"经济效益": 0.7, "环境影响": 0.4, "社会影响": 0.6, "技术可行性": 0.8},  # 位置D
        {"经济效益": 0.4, "环境影响": 0.9, "社会影响": 0.7, "技术可行性": 0.7},  # 位置E
    ]

    selected_idx_2, decision_info_2 = system.make_decision(
        alternatives_2,
        context={"task": "工厂选址", "region": "华南地区"}
    )

    print(f"\n决策结果:")
    print(f"  选中方案: 位置{chr(68 + selected_idx_2)}")
    print(f"  得分: {decision_info_2['score']:.3f}")
    print(f"  当前权重: {[f'{w:.3f}' for w in system.weights]}")

    decision_id_2 = decision_info_2['decision_id']

    # ========================================================================
    # 5. 报告结果并学习
    # ========================================================================
    print("\n[步骤 5] 报告实际结果")
    print("-" * 50)

    # 第一个决策的结果
    print(f"\n5.1 决策 {decision_id_1} 的实际结果")
    system.report_outcome(decision_id_1, actual_outcome=75)  # 预测可能不同

    # 第二个决策的结果
    print(f"\n5.2 决策 {decision_id_2} 的实际结果")
    system.report_outcome(decision_id_2, actual_outcome=85)

    # ========================================================================
    # 6. 多轮学习
    # ========================================================================
    print("\n[步骤 6] 多轮学习")
    print("-" * 50)

    # 模拟多次决策和反馈
    for i in range(10):
        alt1 = {
            "经济效益": random.uniform(0.5, 0.9),
            "环境影响": random.uniform(0.3, 0.7),
            "社会影响": random.uniform(0.4, 0.8),
            "技术可行性": random.uniform(0.5, 0.9)
        }
        alt2 = {
            "经济效益": random.uniform(0.3, 0.7),
            "环境影响": random.uniform(0.6, 0.95),
            "社会影响": random.uniform(0.5, 0.9),
            "技术可行性": random.uniform(0.4, 0.8)
        }

        idx, info = system.make_decision([alt1, alt2])
        did = info['decision_id']

        # 模拟反馈 (随着环境意识增强，对高环境影响的方案给低分)
        selected_env_impact = ([alt1, alt2][idx])["环境影响"]
        if selected_env_impact < 0.6:
            feedback_val = random.uniform(0.3, 0.5)  # 低分
        else:
            feedback_val = random.uniform(0.7, 0.95)  # 高分

        system.provide_feedback(did, feedback_val)
        system.report_outcome(did, actual_outcome=random.uniform(60, 90))

    print("\n多轮学习后:")
    system.print_summary()

    # ========================================================================
    # 7. 权重变化分析
    # ========================================================================
    print("\n[步骤 7] 权重变化分析")
    print("-" * 50)

    final_weights = system.weights
    print(f"\n初始权重: {[f'{w:.3f}' for w in initial_weights]}")
    print(f"最终权重: {[f'{w:.3f}' for w in final_weights]}")

    print("\n权重变化:")
    for i, criterion in enumerate(criteria):
        change = final_weights[i] - initial_weights[i]
        arrow = "↑" if change > 0 else "↓" if change < 0 else "→"
        print(f"  {criterion:15s}: {initial_weights[i]:.3f} → {final_weights[i]:.3f} "
              f"({arrow}{abs(change):.3f})")

    # ========================================================================
    # 8. 经验检索
    # ========================================================================
    print("\n[步骤 8] 相似决策检索")
    print("-" * 50)

    similar_decisions = system.experience_store.find_similar_decisions(
        {"task": "工厂选址", "region": "华东地区"},
        threshold=0.3
    )

    print(f"\n找到 {len(similar_decisions)} 个相似决策:")
    for i, decision in enumerate(similar_decisions[:3], 1):
        print(f"  {i}. {decision.decision_id} - "
              f"选中: {decision.selected_alternative}, "
              f"反馈: {decision.get_average_feedback():.2f}")

    print("\n" + "="*70)
    print("演示完成!")
    print("="*70)


if __name__ == "__main__":
    main()
