#!/usr/bin/env python3
"""
修复历史考试的小数分数问题

将历史考试中的小数分数（如 0.9090909090909091）转换为整数分数
使用智能整数分配算法，确保所有题目分数之和等于总分

使用方法：
    # 在后端容器中执行
    cd /app
    python scripts/fix_exam_scores.py --dry-run  # 预览模式，不实际修改
    python scripts/fix_exam_scores.py            # 实际执行修复
"""

import sys
import json
import argparse
from decimal import Decimal

# 添加项目路径
sys.path.insert(0, '/app')

def distribute_integer_scores(total_score: float, question_count: int) -> list:
    """
    整数分配分数
    
    将总分分配为整数，前面的题目分数可能比后面的多1分
    
    示例：
        100分 / 6题 = [17, 17, 17, 17, 16, 16]
        100分 / 11题 = [10, 10, 10, 10, 10, 10, 10, 10, 10, 5, 5]
    """
    total = int(total_score)
    count = question_count
    
    # 基础分数（向下取整）
    base_score = total // count
    # 需要额外加1分的题目数量
    extra_count = total % count
    
    # 生成分数列表
    scores = []
    for i in range(count):
        if i < extra_count:
            scores.append(base_score + 1)
        else:
            scores.append(base_score)
    
    return scores


def is_decimal_score(score) -> bool:
    """检查分数是否是小数（非整数）"""
    if score is None:
        return False
    try:
        score_float = float(score)
        return score_float != int(score_float)
    except (ValueError, TypeError):
        return False


def fix_exam_scores(dry_run: bool = True, db_url: str = None):
    """
    修复考试分数
    
    Args:
        dry_run: 如果为 True，只预览不实际修改
        db_url: 数据库连接字符串，如果为 None 则从环境变量读取
    """
    import os
    from sqlalchemy import create_engine, text
    from sqlalchemy.orm import sessionmaker
    
    # 获取数据库连接
    if db_url is None:
        db_url = os.environ.get('DATABASE_URL')
        if not db_url:
            # 尝试从配置文件读取
            try:
                from app.core.config import settings
                db_url = settings.DATABASE_URL
            except:
                print("错误：无法获取数据库连接字符串")
                print("请设置 DATABASE_URL 环境变量或确保在正确的目录下运行")
                sys.exit(1)
    
    # 将 mysql+aiomysql:// 转换为 mysql+pymysql://
    if 'aiomysql' in db_url:
        db_url = db_url.replace('aiomysql', 'pymysql')
    
    print(f"连接数据库...")
    engine = create_engine(db_url, echo=False)
    Session = sessionmaker(bind=engine)
    session = Session()
    
    try:
        # 查询所有考试记录
        result = session.execute(text("""
            SELECT id, user_id, course_id, exam_name, question_count, total_score, questions 
            FROM exams 
            WHERE questions IS NOT NULL 
            ORDER BY id DESC
        """))
        
        exams = result.fetchall()
        print(f"找到 {len(exams)} 条考试记录")
        
        fixed_count = 0
        skipped_count = 0
        error_count = 0
        
        for exam in exams:
            exam_id, user_id, course_id, exam_name, question_count, total_score, questions_json = exam
            
            try:
                # 解析 questions JSON
                if isinstance(questions_json, str):
                    questions = json.loads(questions_json)
                else:
                    questions = questions_json
                
                if not questions or not isinstance(questions, list):
                    skipped_count += 1
                    continue
                
                # 检查是否有小数分数
                has_decimal = False
                for q in questions:
                    if 'score' in q and is_decimal_score(q['score']):
                        has_decimal = True
                        break
                
                if not has_decimal:
                    skipped_count += 1
                    continue
                
                # 计算新的整数分数
                actual_count = len(questions)
                actual_total = total_score or 100
                new_scores = distribute_integer_scores(actual_total, actual_count)
                
                # 更新每道题的分数
                old_scores = [q.get('score', 0) for q in questions]
                for i, q in enumerate(questions):
                    q['score'] = new_scores[i]
                
                # 验证总分
                new_total = sum(new_scores)
                if abs(new_total - actual_total) > 0.01:
                    print(f"  警告：exam_id={exam_id} 分数总和不匹配: {new_total} != {actual_total}")
                    error_count += 1
                    continue
                
                if dry_run:
                    print(f"[预览] exam_id={exam_id}, 课程={course_id}, 题数={actual_count}, 总分={actual_total}")
                    print(f"        旧分数: {old_scores[:5]}..." if len(old_scores) > 5 else f"        旧分数: {old_scores}")
                    print(f"        新分数: {new_scores[:5]}..." if len(new_scores) > 5 else f"        新分数: {new_scores}")
                else:
                    # 实际更新数据库
                    new_json = json.dumps(questions, ensure_ascii=False)
                    session.execute(text("""
                        UPDATE exams SET questions = :questions WHERE id = :exam_id
                    """), {"questions": new_json, "exam_id": exam_id})
                    print(f"[已修复] exam_id={exam_id}, 题数={actual_count}, 分数: {new_scores}")
                
                fixed_count += 1
                
            except Exception as e:
                print(f"  错误：处理 exam_id={exam_id} 时出错: {e}")
                error_count += 1
                continue
        
        if not dry_run:
            session.commit()
            print("\n已提交数据库更改")
        
        print(f"\n=== 统计 ===")
        print(f"需要修复: {fixed_count}")
        print(f"已跳过（无小数）: {skipped_count}")
        print(f"错误: {error_count}")
        
        if dry_run:
            print("\n这是预览模式，未实际修改数据库。")
            print("如需实际执行，请去掉 --dry-run 参数重新运行。")
        
    except Exception as e:
        print(f"执行失败: {e}")
        session.rollback()
        raise
    finally:
        session.close()


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="修复历史考试的小数分数问题")
    parser.add_argument("--dry-run", action="store_true", help="预览模式，不实际修改数据库")
    parser.add_argument("--db-url", type=str, help="数据库连接字符串")
    args = parser.parse_args()
    
    fix_exam_scores(dry_run=args.dry_run, db_url=args.db_url)
