"""
AIME数学题目生成器

使用HelloAgents框架生成AIME风格的数学题目
"""

import json
import os
import time
import random
from typing import List, Dict, Any, Optional
from datetime import datetime
from tqdm import tqdm
from hello_agents import SimpleAgent
from hello_agents import HelloAgentsLLM
from datasets import load_dataset


class AIMEGenerator:
    """AIME题目生成器"""
    
    # AIME题目生成提示词（英文）
    GENERATION_PROMPT = """You are a professional mathematics competition problem designer, skilled in creating AIME (American Invitational Mathematics Examination) style problems.

AIME Problem Characteristics:
1. Answer: An integer between 0 and 999
2. Topics: Algebra, Geometry, Number Theory, Combinatorics, Probability, etc.
3. Style: Requires multi-step reasoning, but no advanced theory
4. Difficulty: Medium to hard (similar to AIME problems 6-9)

Please generate an AIME-style mathematics problem, including:
1. Problem statement (clear and complete)
2. Answer (an integer between 0 and 999)
3. Detailed solution (including all reasoning steps)
4. Topic classification (Algebra/Geometry/Number Theory/Combinatorics/Probability)

Please output in the following JSON format, avoid using special escape characters in JSON:
```json
{
    "problem": "Problem statement in English",
    "answer": 123,
    "solution": "Detailed solution steps in English",
    "topic": "Algebra"
}
```
"""
    
    def __init__(
        self,
        llm: HelloAgentsLLM = None,
        delay_seconds: float = 1.0,
        use_reference_examples: bool = True,
        reference_dataset: str = "TianHongZXY/aime-1983-2025"
    ):
        """
        初始化生成器

        Args:
            llm: LLM实例（可选）
            delay_seconds: 每次生成之间的延迟（秒），避免API速率限制
            use_reference_examples: 是否使用真题作为参考样例
            reference_dataset: 参考数据集名称，默认使用TianHongZXY/aime-1983-2025（900+道题）
        """
        # 如果没有提供llm，创建默认的HelloAgentsLLM
        if llm is None:
            self.llm = HelloAgentsLLM()
        else:
            self.llm = llm

        self.agent = SimpleAgent(
            name="AIME Generator",
            llm=self.llm,
            system_prompt="你是一位专业的数学竞赛题目设计专家。"
        )
        self.delay_seconds = delay_seconds
        self.use_reference_examples = use_reference_examples
        self.reference_examples = []

        # 加载参考样例
        if use_reference_examples:
            try:
                print(f"📚 加载AIME真题数据集: {reference_dataset}")
                # 尝试不同的split
                try:
                    dataset = load_dataset(reference_dataset, split="train")
                except:
                    dataset = load_dataset(reference_dataset, split="test")

                # 加载所有题目作为参考
                self.reference_examples = list(dataset)
                print(f"   ✓ 已加载 {len(self.reference_examples)} 道参考题目")

                # 统计年份分布（如果有year字段）
                year_counts = {}
                for item in self.reference_examples:
                    year = item.get('year')
                    if year:
                        year_counts[year] = year_counts.get(year, 0) + 1

                if year_counts:
                    year_range = f"{min(year_counts.keys())}-{max(year_counts.keys())}"
                    print(f"   ℹ️  年份范围: {year_range}")

            except Exception as e:
                print(f"   ⚠️ 加载参考样例失败: {e}")
                print(f"   ℹ️  将使用默认提示词生成")
                self.use_reference_examples = False
    
    def generate_single(self, max_retries: int = 3) -> Dict[str, Any]:
        """
        生成单个题目

        Args:
            max_retries: 最大重试次数

        Returns:
            题目数据
        """
        # 构建提示词
        prompt = self._build_prompt()

        for attempt in range(max_retries):
            try:
                response = self.agent.run(prompt)
                return self._parse_response(response)
            except Exception as e:
                if attempt < max_retries - 1:
                    tqdm.write(f"⚠️ 生成失败（尝试 {attempt + 1}/{max_retries}），{self.delay_seconds}秒后重试...")
                    time.sleep(self.delay_seconds)
                else:
                    tqdm.write(f"❌ 生成失败，已达最大重试次数: {e}")
                    return self._get_default_problem()

    def _build_prompt(self) -> str:
        """构建生成提示词"""
        if not self.use_reference_examples or not self.reference_examples:
            return self.GENERATION_PROMPT

        # 随机选择一个参考样例
        example = random.choice(self.reference_examples)
        example_problem = example.get('problem', 'Example problem')
        example_answer = example.get('answer', 0)

        # 构建带参考样例的提示词（英文）
        prompt = f"""You are a professional mathematics competition problem designer, skilled in creating AIME (American Invitational Mathematics Examination) style problems.

【Reference Example】(For style reference only, please generate a completely different problem)
Problem: {example_problem}
Answer: {example_answer}

AIME Problem Characteristics:
1. Answer: An integer between 0 and 999
2. Topics: Algebra, Geometry, Number Theory, Combinatorics, Probability, etc.
3. Style: Requires multi-step reasoning, but no advanced theory
4. Difficulty: Medium to hard (similar to AIME problems 6-9)

Please generate a **completely different** AIME-style mathematics problem, including:
1. Problem statement (clear and complete, different from the reference)
2. Answer (an integer between 0 and 999, different from the reference)
3. Detailed solution (including all reasoning steps)
4. Topic classification (Algebra/Geometry/Number Theory/Combinatorics/Probability)

Please output in the following JSON format, avoid using special escape characters in JSON:
```json
{{
    "problem": "Problem statement in English",
    "answer": 123,
    "solution": "Detailed solution steps in English",
    "topic": "Algebra"
}}
```

Important Notes:
- **Must generate a completely different problem from the reference**
- You can reference the style, but do not copy the content
- Ensure the problem is creative and original
"""
        return prompt

    def _parse_response(self, response: str) -> Dict[str, Any]:
        """解析LLM响应（支持LaTeX数学公式）"""
        import re

        # 提取JSON部分
        if "```json" in response:
            json_str = response.split("```json")[1].split("```")[0].strip()
        elif "```" in response:
            json_str = response.split("```")[1].split("```")[0].strip()
        else:
            json_str = response.strip()

        # 使用json.loads的strict=False来处理转义字符
        # 但这还不够，我们需要更智能的处理
        try:
            problem_data = json.loads(json_str)
        except json.JSONDecodeError as e:
            # 如果解析失败，尝试修复常见的LaTeX转义问题
            # 方法：先将字符串中的单个反斜杠替换为双反斜杠（但保留已经转义的）
            # 这样LaTeX的 \frac 会变成 \\frac，在JSON中是合法的

            # 使用正则表达式：找到所有未转义的反斜杠（不是\\的\）
            # 并将其替换为\\
            fixed_json_str = re.sub(r'(?<!\\)\\(?!["\\/bfnrtu])', r'\\\\', json_str)

            try:
                problem_data = json.loads(fixed_json_str)
            except json.JSONDecodeError:
                # 如果还是失败，打印错误信息并抛出
                print(f"❌ JSON解析失败:")
                print(f"原始响应: {response[:500]}...")
                print(f"提取的JSON: {json_str[:500]}...")
                raise

        # 验证必需字段
        if "problem" not in problem_data or "answer" not in problem_data:
            raise ValueError("缺少必需字段: problem 或 answer")

        # 验证答案范围
        answer = int(problem_data.get("answer", 0))
        if not (0 <= answer <= 999):
            print(f"⚠️ 答案超出范围: {answer}，调整为0-999范围内")
            answer = max(0, min(999, answer))
            problem_data["answer"] = answer

        # 确保有默认值
        problem_data.setdefault("solution", "No solution provided")
        problem_data.setdefault("topic", "Uncategorized")

        return problem_data

    def _get_default_problem(self) -> Dict[str, Any]:
        """获取默认题目（生成失败时使用）"""
        return {
            "problem": "生成失败，请重新生成",
            "answer": 0,
            "solution": "N/A",
            "topic": "未知"
        }
    
    def generate_batch(
        self,
        num_problems: int = 30,
        checkpoint_path: str = None
    ) -> List[Dict[str, Any]]:
        """
        批量生成题目

        Args:
            num_problems: 生成题目数量
            checkpoint_path: 检查点文件路径（用于保存进度）

        Returns:
            题目列表
        """
        print(f"\n🎯 开始生成AIME题目")
        print(f"   目标数量: {num_problems}")
        print(f"   生成模型: {self.llm.model}")
        print(f"   延迟设置: {self.delay_seconds}秒/题")

        # 尝试从检查点恢复
        problems = []
        start_index = 0

        if checkpoint_path and os.path.exists(checkpoint_path):
            print(f"\n📂 发现检查点文件，尝试恢复...")
            try:
                with open(checkpoint_path, 'r', encoding='utf-8') as f:
                    problems = json.load(f)
                start_index = len(problems)
                print(f"   ✓ 已恢复 {start_index} 个题目，从第 {start_index + 1} 个继续")
            except Exception as e:
                print(f"   ⚠️ 恢复失败: {e}，从头开始")
                problems = []
                start_index = 0

        # 生成题目（使用tqdm显示进度）
        with tqdm(total=num_problems, initial=start_index, desc="生成AIME题目", unit="题") as pbar:
            last_call_time = 0  # 上次API调用的时间

            for i in range(start_index, num_problems):
                # 计算距离上次调用的时间
                if last_call_time > 0:
                    elapsed = time.time() - last_call_time
                    # 如果距离上次调用不足delay_seconds，则等待
                    if elapsed < self.delay_seconds:
                        wait_time = self.delay_seconds - elapsed
                        tqdm.write(f"⏳ 等待 {wait_time:.1f} 秒以避免速率限制...")
                        time.sleep(wait_time)

                # 记录开始时间
                start_time = time.time()

                # 生成题目
                problem = self.generate_single()
                problem["id"] = f"gen_aime_{i + 1}"
                problem["generated_at"] = datetime.now().isoformat()

                # 记录结束时间
                last_call_time = time.time()
                generation_time = last_call_time - start_time

                problems.append(problem)

                # 更新进度条描述
                pbar.set_postfix({
                    "主题": problem.get('topic', 'N/A'),
                    "答案": problem.get('answer', 'N/A'),
                    "耗时": f"{generation_time:.1f}s"
                })
                pbar.update(1)

                # 保存检查点
                if checkpoint_path:
                    try:
                        with open(checkpoint_path, 'w', encoding='utf-8') as f:
                            json.dump(problems, f, ensure_ascii=False, indent=2)
                    except Exception as e:
                        tqdm.write(f"⚠️ 保存检查点失败: {e}")

        print(f"\n✅ 生成完成！共 {len(problems)} 个题目")
        return problems
    
    def save_problems(
        self,
        problems: List[Dict[str, Any]],
        output_path: str
    ):
        """保存题目到文件"""
        # 确保目录存在
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(problems, f, ensure_ascii=False, indent=2)
        
        print(f"\n💾 题目已保存: {output_path}")
    
    def generate_and_save(
        self,
        num_problems: int = 30,
        output_dir: str = "data_generation/generated_data"
    ) -> str:
        """生成并保存题目"""
        # 创建输出目录
        os.makedirs(output_dir, exist_ok=True)

        # 清理旧的检查点文件
        for file in os.listdir(output_dir):
            if file.startswith("checkpoint_") and file.endswith(".json"):
                old_checkpoint = os.path.join(output_dir, file)
                try:
                    os.remove(old_checkpoint)
                    print(f"🗑️  已删除旧检查点文件: {file}")
                except Exception as e:
                    print(f"⚠️ 删除旧检查点失败: {e}")

        # 设置检查点路径
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        checkpoint_path = os.path.join(output_dir, f"checkpoint_{timestamp}.json")

        # 生成题目（带检查点）
        problems = self.generate_batch(num_problems, checkpoint_path=checkpoint_path)

        # 保存题目
        output_path = os.path.join(output_dir, f"aime_generated_{timestamp}.json")
        self.save_problems(problems, output_path)

        # 生成统计报告
        self._generate_statistics_report(problems, output_dir, timestamp)

        # 删除检查点文件
        if os.path.exists(checkpoint_path):
            try:
                os.remove(checkpoint_path)
                print(f"\n🗑️  已删除检查点文件")
            except Exception as e:
                print(f"\n⚠️ 删除检查点文件失败: {e}")

        return output_path
    
    def _generate_statistics_report(
        self,
        problems: List[Dict[str, Any]],
        output_dir: str,
        timestamp: str
    ):
        """生成统计报告"""
        # 统计主题分布
        topics = {}
        answers = []

        for problem in problems:
            topic = problem.get("topic", "未知")
            topics[topic] = topics.get(topic, 0) + 1

            if "answer" in problem:
                answers.append(problem["answer"])
        
        # 生成报告
        report = f"""# AIME题目生成统计报告

## 基本信息

- **生成时间**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
- **题目数量**: {len(problems)}

## 主题分布

| 主题 | 数量 | 占比 |
|------|------|------|
"""
        
        for topic, count in sorted(topics.items(), key=lambda x: x[1], reverse=True):
            percentage = count / len(problems) * 100
            report += f"| {topic} | {count} | {percentage:.1f}% |\n"

        if answers:
            report += f"""
## 答案分析

- **平均答案**: {sum(answers) / len(answers):.2f}
- **最小答案**: {min(answers)}
- **最大答案**: {max(answers)}
- **答案范围**: {min(answers)}-{max(answers)}
"""
        
        report += f"""
## 题目列表

| ID | 主题 | 答案 |
|-----|------|------|
"""

        for problem in problems[:10]:  # 只显示前10个
            report += f"| {problem.get('id', 'N/A')} | {problem.get('topic', 'N/A')} | {problem.get('answer', 'N/A')} |\n"
        
        if len(problems) > 10:
            report += f"\n*（仅显示前10个题目，完整列表请查看JSON文件）*\n"
        
        report += f"""
---

*报告生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}*
"""
        
        # 保存报告
        report_path = os.path.join(output_dir, f"generation_report_{timestamp}.md")
        with open(report_path, 'w', encoding='utf-8') as f:
            f.write(report)
        
        print(f"📊 统计报告已保存: {report_path}")


if __name__ == "__main__":
    # 创建生成器
    generator = AIMEGenerator()
    
    # 生成30个题目
    output_path = generator.generate_and_save(num_problems=30)
    
    print(f"\n✅ 完成！生成的题目保存在: {output_path}")

