
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
穹狼乐高编程课评自动双轨保存工具
版本：v4.0（课评规范V1.0同步版）

功能：
1. 自动检测学生档案位置
2. 自动保存个人课评文件到正确路径
3. 自动处理补课学生的双向记录
4. 自动更新班级汇总文件
5. 支持交互式输入和批量处理

使用方法：
    python evaluation_saver.py -i
    python evaluation_saver.py --config config.json
"""

import sys
import io
import re
import json
import argparse
from pathlib import Path
from datetime import datetime, date
from typing import Dict, List, Optional, Tuple

# 设置UTF-8输出编码
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

# ============ 配置项 ============

# 基础路径配置
BASE_PATH = Path(__file__).parent.parent
CLASS_DATA_PATH = BASE_PATH / '.claude' / 'memory' / 'class'

# 周数计算基准
SEMESTER_START_DATE = date(2026, 3, 2)

# 课程代码映射（周数->课程代码）
WEEK_TO_COURSE = {
    'DISC': {
        1: 'DISC-001', 2: 'DISC-002', 3: 'DISC-003',
        4: 'DISC-004', 5: 'DISC-005', 6: 'DISC-006',
        7: 'DISC-007', 8: 'DISC-008', 9: 'DISC-009',
        10: 'DISC-010', 11: 'DISC-011', 12: 'DISC-012',
        13: 'DISC-013', 14: 'DISC-014', 15: 'DISC-015',
        16: 'DISC-016', 17: 'DISC-017', 18: 'DISC-018',
        19: 'DISC-019', 20: 'DISC-020',
    },
    'SPIKE': {
        1: 'SPIKE-001', 2: 'SPIKE-002', 3: 'SPIKE-003',
        4: 'SPIKE-004', 5: 'SPIKE-005', 6: 'SPIKE-006',
        7: 'SPIKE-007', 8: 'SPIKE-008', 9: 'SPIKE-009',
        10: 'SPIKE-010', 11: 'SPIKE-011', 12: 'SPIKE-012',
        13: 'SPIKE-013', 14: 'SPIKE-014', 15: 'SPIKE-015',
        16: 'SPIKE-016', 17: 'SPIKE-017', 18: 'SPIKE-018',
        19: 'SPIKE-019', 20: 'SPIKE-020',
    },
    'AICODE03': {
        1: 'AICODE03-001', 2: 'AICODE03-002',
        3: 'AICODE03-003', 4: 'AICODE03-004',
        5: 'AICODE03-005', 6: 'AICODE03-006',
        7: 'AICODE03-007', 8: 'AICODE03-008',
        9: 'AICODE03-009', 10: 'AICODE03-010',
        11: 'AICODE03-011', 12: 'AICODE03-012',
    },
    'CREATE': {
        1: 'CREATE-001', 2: 'CREATE-002', 3: 'CREATE-003',
        4: 'CREATE-004', 5: 'CREATE-005', 6: 'CREATE-006',
        7: 'CREATE-007', 8: 'CREATE-008', 9: 'CREATE-009',
        10: 'CREATE-010',
    },
    'INVENT': {
        1: 'INVENT-001', 2: 'INVENT-002', 3: 'INVENT-003',
        4: 'INVENT-004', 5: 'INVENT-005', 6: 'INVENT-006',
        7: 'INVENT-007', 8: 'INVENT-008', 9: 'INVENT-009',
        10: 'INVENT-010',
    },
}

# ============ 工具函数 ============

def calculate_week_number(target_date: date) -> int:
    """计算给定日期是第几周"""
    delta = target_date - SEMESTER_START_DATE
    week_number = delta.days // 7 + 1
    return max(1, min(20, week_number))

def get_course_code_for_week(class_name: str, week_number: int) -> str:
    """根据班级名称和周数获取课程代码"""
    course_type = None
    if 'AICODE03' in class_name:
        course_type = 'AICODE03'
    elif 'SPIKE' in class_name:
        course_type = 'SPIKE'
    elif 'CREATE' in class_name or 'Wedo' in class_name:
        course_type = 'CREATE'
    elif 'INVENT' in class_name or '发明世界' in class_name:
        course_type = 'INVENT'
    elif 'DISC' in class_name or '发现世界' in class_name or '小小班' in class_name or '小班' in class_name or '中班' in class_name:
        course_type = 'DISC'

    if course_type and week_number in WEEK_TO_COURSE.get(course_type, {}):
        return WEEK_TO_COURSE[course_type][week_number]
    return f"{course_type}-{week_number:02d}" if course_type else f"UNKNOWN-{week_number:02d}"

def find_all_classes() -> List[Path]:
    """查找所有班级目录"""
    classes = []
    if CLASS_DATA_PATH.exists():
        for item in CLASS_DATA_PATH.iterdir():
            if item.is_dir() and not item.name.startswith('.'):
                classes.append(item)
    return classes

def find_student_profile(student_name: str) -&gt; Optional[Tuple[Path, Path]]:
    """
    查找学生档案，返回(班级目录, 学生目录)
    如果找到多个档案，返回第一个
    """
    for class_dir in find_all_classes():
        # 先查找正式学生目录
        student_dir = class_dir / student_name
        if student_dir.exists() and (student_dir / 'profile.md').exists():
            return (class_dir, student_dir)

        # 也检查一下补课目录（但补课目录一般不需要profile）
        makeup_dir = class_dir / '补课' / student_name
        if makeup_dir.exists():
            # 检查其他班级是否有这个学生的正式档案
            for other_class in find_all_classes():
                if other_class != class_dir:
                    other_student = other_class / student_name
                    if other_student.exists() and (other_student / 'profile.md').exists():
                        return (other_class, other_student)

    return None

def find_class_dir_by_name(class_name: str) -&gt; Optional[Path]:
    """根据班级名称查找班级目录"""
    for class_dir in find_all_classes():
        if class_name in class_dir.name:
            return class_dir
    return None

def get_date_str(date_obj: date = None) -&gt; str:
    """获取日期字符串（YYYYMMDD格式）"""
    if date_obj is None:
        date_obj = date.today()
    return date_obj.strftime('%Y%m%d')

def parse_date_str(date_str: str) -&gt; date:
    """解析日期字符串（YYYYMMDD格式）"""
    return datetime.strptime(date_str, '%Y%m%d').date()

# ============ 课评保存器类 ============

class EvaluationSaver:
    def __init__(self, class_name: str, teaching_date: date = None):
        self.class_name = class_name
        self.teaching_date = teaching_date or date.today()
        self.week_number = calculate_week_number(self.teaching_date)
        self.date_str = get_date_str(self.teaching_date)

        # 查找班级目录
        self.class_dir = find_class_dir_by_name(class_name)
        if not self.class_dir:
            raise ValueError(f"找不到班级目录：{class_name}")

        self.course_code = get_course_code_for_week(self.class_dir.name, self.week_number)

        print(f"📅 授课日期：{self.teaching_date}")
        print(f"📚 学期周数：第{self.week_number}周")
        print(f"🎯 课程代码：{self.course_code}")
        print(f"📁 班级目录：{self.class_dir.name}")

    def save_individual_evaluation(
        self,
        student_name: str,
        evaluation_text: str,
        is_makeup: bool = False,
        original_class: Optional[str] = None,
        course_topic: str = "",
    ) -&gt; Tuple[bool, List[Path]]:
        """
        保存单条学生课评

        参数：
            student_name: 学生姓名
            evaluation_text: 课评内容
            is_makeup: 是否是补课学生
            original_class: 原班级名称（仅is_makeup=True时需要）
            course_topic: 课程主题

        返回：
            (是否成功, 保存的文件列表)
        """
        saved_files = []

        # 步骤1：查找学生档案位置
        profile_info = find_student_profile(student_name)

        # 步骤2：判断处理方式
        if is_makeup and original_class:
            # 明确指定了是跨班级补课
            original_class_dir = find_class_dir_by_name(original_class)
            if not original_class_dir:
                print(f"⚠️  找不到原班级目录：{original_class}，尝试自动查找...")
                profile_info = find_student_profile(student_name)
                if profile_info:
                    original_class_dir = profile_info[0]
                    print(f"✅ 自动找到原班级：{original_class_dir.name}")
                else:
                    print(f"❌ 无法确定原班级，只保存到补课班级侧")
                    original_class_dir = None

            success, files = self._save_makeup_evaluation(
                student_name, evaluation_text, course_topic, original_class_dir
            )
            return success, files

        elif is_makeup and profile_info:
            # 标记为补课但档案在其他班级 → 跨班级补课
            original_class_dir, _ = profile_info
            if original_class_dir.name != self.class_dir.name:
                # 确实是跨班级
                print(f"🔄 检测到跨班级补课：学生档案在「{original_class_dir.name}」")
                success, files = self._save_makeup_evaluation(
                    student_name, evaluation_text, course_topic, original_class_dir
                )
                return success, files
            else:
                # 档案在当前班级 → 本班补课
                print(f"✅ 本班补课学生：{student_name}")

        # 标准保存方式（正式学生或补课但档案在当前班级）
        # 确定学生目录
        student_dir = self.class_dir / student_name
        if not student_dir.exists() or not (student_dir / 'profile.md').exists():
            if is_makeup:
                # 补课但没有档案 → 使用补课目录
                student_dir = self.class_dir / '补课' / student_name
                print(f"⚠️  找不到正式档案，使用补课目录：{student_dir}")
            else:
                # 可能是新学生
                print(f"⚠️  找不到学生档案：{student_name}")
                create = input(f"是否为新学生创建档案？(y/n) ").lower() == 'y'
                if create:
                    student_dir.mkdir(parents=True, exist_ok=True)
                    self._create_empty_profile(student_dir / 'profile.md', student_name)
                else:
                    return False, []

        # 创建feedback目录
        feedback_dir = student_dir / 'feedback'
        feedback_dir.mkdir(parents=True, exist_ok=True)

        # 生成文件名
        eval_filename = f"{self.date_str}_{self.course_code}.md"
        eval_filepath = feedback_dir / eval_filename

        # 格式化并保存课评
        formatted_eval = self._format_individual_evaluation(
            student_name, evaluation_text, course_topic
        )

        eval_filepath.write_text(formatted_eval, encoding='utf-8')
        saved_files.append(eval_filepath)

        print(f"✅ 已保存个人课评：{eval_filepath}")

        return True, saved_files

    def _save_makeup_evaluation(
        self,
        student_name: str,
        evaluation_text: str,
        course_topic: str,
        original_class_dir: Optional[Path],
    ) -&gt; Tuple[bool, List[Path]]:
        """
        保存补课学生的双向记录

        规则：
        1. 补课班级侧：保存到「补课/{学生}/feedback/YYYYMMDD_课程编号.md」，内容开头注明来源
        2. 原班级侧：保存到「{学生}/feedback/YYYYMMDD_课程编号-课次(补课-去班级名).md」，简单记录去向
        """
        saved_files = []

        # 第一部分：补课班级侧记录
        makeup_student_dir = self.class_dir / '补课' / student_name
        makeup_feedback_dir = makeup_student_dir / 'feedback'
        makeup_feedback_dir.mkdir(parents=True, exist_ok=True)

        makeup_eval_filename = f"{self.date_str}_{self.course_code}.md"
        makeup_eval_filepath = makeup_feedback_dir / makeup_eval_filename

        # 添加来源标注
        source_note = f"（补课，来自 {original_class_dir.name if original_class_dir else '其他班级'}）"
        full_eval_text = f"{source_note}\n\n{evaluation_text}"

        formatted_eval = self._format_individual_evaluation(
            student_name, full_eval_text, course_topic
        )

        makeup_eval_filepath.write_text(formatted_eval, encoding='utf-8')
        saved_files.append(makeup_eval_filepath)
        print(f"✅ 已保存补课班级侧记录：{makeup_eval_filepath}")

        # 第二部分：原班级侧记录
        if original_class_dir:
            original_student_dir = original_class_dir / student_name
            if original_student_dir.exists():
                original_feedback_dir = original_student_dir / 'feedback'
                original_feedback_dir.mkdir(parents=True, exist_ok=True)

                # 原班级侧文件名
                original_filename = f"{self.date_str}_{self.course_code}(补课-去{self.class_dir.name}).md"
                original_filepath = original_feedback_dir / original_filename

                # 原班级侧只需要简单记录
                makeup_tracking_content = self._format_makeup_tracking(
                    student_name, self.class_dir.name
                )

                original_filepath.write_text(makeup_tracking_content, encoding='utf-8')
                saved_files.append(original_filepath)
                print(f"✅ 已保存原班级侧记录：{original_filepath}")
            else:
                print(f"⚠️  找不到原班级学生目录，跳过原班级侧记录")

        return True, saved_files

    def _format_individual_evaluation(
        self,
        student_name: str,
        evaluation_text: str,
        course_topic: str = "",
    ) -&gt; str:
        """格式化个人课评文件内容"""
        lines = []

        title = f"课程名称：{self.course_code}"
        if course_topic:
            title += f" {course_topic}"

        lines.append(f"# {title}")
        lines.append("")
        lines.append(f"**授课日期**：{self.teaching_date}")
        lines.append("")
        lines.append("**课评内容**：")
        lines.append("")
        lines.append(evaluation_text.strip())

        return '\n'.join(lines)

    def _format_makeup_tracking(self, student_name: str, makeup_class_name: str) -&gt; str:
        """格式化补课追踪记录（原班级侧）"""
        lines = [
            "# 补课记录",
            "",
            f"**日期**：{self.teaching_date}",
            f"**课程**：{self.course_code}",
            f"**学生**：{student_name}",
            "**状态**：补课",
            f"**去向班级**：{makeup_class_name}",
            "**说明**：本节课程在{makeup_class_name}完成补课"
        ]
        return '\n'.join(lines)

    def _create_empty_profile(self, profile_path: Path, student_name: str):
        """创建空的学生档案"""
        content = f"""# 学生档案：{student_name}

## 基础信息
- 姓名：{student_name}
- 小名：
- 年龄：

## 性格特点
- 气质类型：
- 互动模式：
- 遇到困难时：

## 学习特点
- 学习方式偏好：
- 平均专注时长：
- 核心优势能力：

## 家长沟通
- 主要接送人：
- 期望沟通频率：
"""
        profile_path.write_text(content, encoding='utf-8')
        print(f"✅ 已创建学生档案：{profile_path}")


# ============ 交互式界面 ============

def interactive_mode():
    """交互式输入模式"""
    print("="*60)
    print("📚 穹狼乐高编程课评自动保存工具 v4.0")
    print("="*60)

    # 选择班级
    print("\n可用班级：")
    classes = find_all_classes()
    for i, cls in enumerate(classes, 1):
        print(f"  {i}. {cls.name}")

    class_choice = input("\n请输入班级编号或班级名称：").strip()
    if class_choice.isdigit():
        class_idx = int(class_choice) - 1
        if 0 &lt;= class_idx &lt; len(classes):
            selected_class = classes[class_idx].name
        else:
            print("❌ 无效的班级编号")
            return
    else:
        selected_class = class_choice

    # 输入日期
    date_input = input("\n请输入授课日期（YYYYMMDD，留空使用今天）：").strip()
    teaching_date = date.today()
    if date_input:
        teaching_date = parse_date_str(date_input)

    # 创建保存器
    try:
        saver = EvaluationSaver(selected_class, teaching_date)
    except ValueError as e:
        print(f"❌ {e}")
        return

    # 批量输入学生
    print("\n" + "="*60)
    print("📝 开始输入学生表现（输入'q'结束）")
    print("输入格式：学生姓名 课评内容 [补课]")
    print("示例：小明 今天表现很好，搭建了很棒的作品!")
    print("示例：小红 今天表现很稳定 补课")
    print("="*60)

    all_saved_files = []

    while True:
        print()
        line = input("请输入：").strip()
        if not line:
            continue

        if line.lower() == 'q':
            break

        # 解析输入
        # 格式1：姓名 课评内容 标记
        parts = line.split(maxsplit=1)
        if len(parts) &lt; 2:
            print("❌ 格式不正确：需要至少包含学生姓名和课评内容")
            continue

        student_name = parts[0]
        rest = parts[1]

        is_makeup = False
        original_class = None

        # 检查是否有补课标记
        if '补课' in rest:
            is_makeup = True
            # 从文本中移除标记
            rest = rest.replace('补课', '').strip()

        # 询问课程主题
        course_topic = input(f"\n请输入{student_name}的课程主题（可选）：").strip()

        # 保存
        print(f"\n正在保存 {student_name} 的课评...")
        success, saved_files = saver.save_individual_evaluation(
            student_name=student_name,
            evaluation_text=rest,
            is_makeup=is_makeup,
            original_class=original_class,
            course_topic=course_topic
        )

        if success:
            all_saved_files.extend(saved_files)
            print(f"✅ 保存成功！")
        else:
            print(f"❌ 保存失败")

    # 总结
    print("\n" + "="*60)
    print(f"📊 全部完成！共保存 {len(all_saved_files)} 个文件")
    for f in all_saved_files:
        print(f"  ✅ {f}")
    print("="*60)


# ============ 主函数 ============

def main():
    parser = argparse.ArgumentParser(description='课评自动双轨保存工具')
    parser.add_argument('-i', '--interactive', action='store_true', help='交互式输入模式')
    parser.add_argument('--config', help='使用配置文件批量处理')
    parser.add_argument('--class', help='班级名称')
    parser.add_argument('--date', help='授课日期（YYYYMMDD）')
    parser.add_argument('--student', help='学生姓名')
    parser.add_argument('--text', help='课评内容文本文件路径')
    parser.add_argument('--makeup', action='store_true', help='标记为补课学生')
    parser.add_argument('--original-class', help='原班级名称（补课学生使用）')
    parser.add_argument('--topic', help='课程主题')

    args = parser.parse_args()

    if args.interactive:
        interactive_mode()
        return

    if not getattr(args, 'class'):
        print("❌ 请指定班级名称（--class）或使用交互式模式（-i）")
        return

    if args.config:
        print(f"📁 读取配置文件：{args.config}")
        with open(args.config, 'r', encoding='utf-8') as f:
            config = json.load(f)

        # 批量处理
        teaching_date = date.today()
        if 'date' in config:
            teaching_date = parse_date_str(config['date'])

        saver = EvaluationSaver(config['class'], teaching_date)

        for student_data in config.get('students', []):
            print(f"\n正在处理：{student_data['name']}")
            success, files = saver.save_individual_evaluation(
                student_name=student_data['name'],
                evaluation_text=student_data['text'],
                is_makeup=student_data.get('makeup', False),
                original_class=student_data.get('original_class'),
                course_topic=student_data.get('topic', ''),
            )
            if success:
                print(f"✅ {student_data['name']} 保存成功")
        return

    # 单条处理
    class_name = getattr(args, 'class')

    teaching_date = date.today()
    if args.date:
        teaching_date = parse_date_str(args.date)

    if not args.student:
        print("❌ 请指定学生姓名（--student）")
        return

    # 读取课评内容
    if args.text:
        with open(args.text, 'r', encoding='utf-8') as f:
            evaluation_text = f.read()
    else:
        print("❌ 请指定课评内容文件（--text）")
        return

    saver = EvaluationSaver(class_name, teaching_date)
    success, files = saver.save_individual_evaluation(
        student_name=args.student,
        evaluation_text=evaluation_text,
        is_makeup=args.makeup,
        original_class=args.original_class,
        course_topic=args.topic or "",
    )

    if success:
        print(f"✅ 保存成功！文件：{files}")


if __name__ == '__main__':
    main()

