#!/usr/bin/env python3
"""
千问AI代码评审系统 - 带自动发布功能
"""

import os
import sys

# 检查必要的依赖
try:
    import requests
    import json
    from datetime import datetime
except ImportError as e:
    print(f"❌ 缺少必要的Python库: {e}")
    print("请运行: pip3 install requests")
    sys.exit(1)

class QwenCodeReviewer:
    def __init__(self):
        # 诊断环境
        self.mr_iid = os.getenv('CI_MERGE_REQUEST_IID')
        self.project_id = os.getenv('CI_PROJECT_ID')
        self.gitlab_url = os.getenv('CI_SERVER_URL', 'http://192.168.22.227')
        self.gitlab_token = os.getenv('AI_GITLAB_TOKEN')

        # 打印环境变量
        print("=" * 60)
        print("环境变量诊断:")
        print(f"  CI_MERGE_REQUEST_IID = {self.mr_iid}")
        print(f"  CI_PROJECT_ID = {self.project_id}")
        print(f"  CI_SERVER_URL = {self.gitlab_url}")
        print(f"  AI_GITLAB_TOKEN (token) = {self.gitlab_token if self.gitlab_token else 'None'}")
        print("=" * 60)

        # 千问API配置
        self.api_url = "http://192.168.22.230:8000/v1/chat/completions"
        self.model_name = "Qwen3-32B-AWQ"

        print(f"🔍 环境检测: MR#{self.mr_iid}, 项目#{self.project_id}")
    
    def get_mr_changes(self):
        """获取MR的真实代码差异"""
        if not all([self.mr_iid, self.project_id, self.gitlab_token]):
            print("❌ 缺少必要的MR环境变量或Token")
            return None
            
        print(f"📥 获取MR #{self.mr_iid} 的代码变更...")
        url = f"{self.gitlab_url}/api/v4/projects/{self.project_id}/merge_requests/{self.mr_iid}/changes"
        headers = {"PRIVATE-TOKEN": self.gitlab_token}
        
        try:
            response = requests.get(url, headers=headers, timeout=30)
            response.raise_for_status()
            data = response.json()
            changes = data.get('changes', [])
            
            if not changes:
                print("⚠️  MR中没有代码变更")
                return []
                
            print(f"✅ 获取到 {len(changes)} 个文件变更")
            return changes
        except Exception as e:
            print(f"❌ 获取MR变更失败: {e}")
            return None
    
    def create_review_prompt(self, changes):
        """创建优化的AI代码评审提示词"""
        if not changes or not isinstance(changes, list):
            return None

        # 添加项目上下文信息
        project_context = f"""
项目上下文：
- 项目名称: {os.getenv('CI_PROJECT_NAME', 'Unknown')}
- 分支: {os.getenv('CI_COMMIT_REF_NAME', 'Unknown')}
- MR作者: {os.getenv('GITLAB_USER_NAME', 'Unknown')}
- 变更文件数: {len(changes)}
"""

        prompt = f"""# 角色定义
你是一位拥有15年经验的资深代码架构师和代码评审专家。你的评审风格：
- 严格但建设性：指出问题，同时给出可操作的改进建议
- 实用主义：优先关注可能引入bug、安全漏洞或性能问题的代码
- 教育导向：解释问题背后的原理，帮助开发者提升技能
- 适度原则：不过度批评小问题，平衡代码质量和开发效率

# 评审维度与优先级（按重要性降序）

## 🔴 P0 - 必须修复（阻塞性问题）
1. **功能缺陷**
   - 逻辑错误、边界条件处理不当
   - 空指针解引用、数组越界
   - 资源泄漏（文件、socket、内存）
   - 竞态条件和并发安全问题

2. **安全漏洞**（CVSS ≥ 7.0）
   - SQL注入、XSS、命令注入
   - 敏感信息泄露（密钥、密码、token）
   - 认证/授权绕过
   - 不安全的加密使用

3. **数据一致性风险**
   - 数据库事务使用错误
   - 并发更新导致的脏读/幻读
   - 缓存一致性破坏

## 🟠 P1 - 强烈建议修复
4. **性能瓶颈**
   - O(n²)以上的嵌套循环（数据量大时）
   - N+1查询问题
   - 不必要的数据库连接
   - 同步阻塞I/O操作

5. **可维护性问题**
   - 超过50行的单一函数
   - 嵌套层次超过4层
   - 复杂的条件表达式（圈复杂度 > 10）
   - 重复代码（DRY原则）

6. **错误处理缺陷**
   - 吞掉异常或记录不当
   - 缺少必要的参数校验
   - 错误消息对调试无帮助

## 🟡 P2 - 建议改进
7. **代码规范**
   - 命名不符合项目约定
   - 缺少必要的注释
   - 过度注释（注释代码而非意图）

8. **最佳实践**
   - 未遵循设计模式
   - 可用标准库却自己实现
   - 硬编码配置值

## ⚪ P3 - 可选优化
9. **代码风格**
   - 格式不一致
   - 多余的空行或注释
   - 魔法数字未定义常量

# 输出格式要求

## 整体结构
\`\`\`
## 📊 评审总结
[2-3句话概括主要发现]

## 🔴 P0 问题（必须修复）
### 问题1: [简短描述]
**位置**: \`文件路径#L行号-L行号\`
**严重性**: P0 / 阻塞性
**影响**: [说明可能导致的后果]
**原因分析**: [简要说明根本原因]

\`\`\`原代码
[问题代码片段]
\`\`\`

**修复建议**:
\`\`\`改进后
[修复后的代码示例]
\`\`\`

## 🟠 P1 问题（强烈建议）
[同上格式]

## 🟡 P2 问题（建议改进）
[同上格式]

## ⚪ P3 问题（可选优化）
[同上格式]
\`\`\`

## 具体要求

1. **每个问题必须包含**:
   - ✅ 精确位置（文件:行号范围）
   - ✅ 问题分类和优先级
   - ✅ 代码对比（原代码 vs 改进代码）
   - ✅ 可执行的修复方案
   - ✅ 影响分析

2. **输出原则**:
   - 优先报告P0问题（如果存在）
   - 每个优先级最多3个典型问题
   - 避免过度批评，聚焦关键问题
   - 使用专业但友好的语气

3. **避免事项**:
   ❌ 泛泛而谈，如"代码质量待提升"
   ❌ 指出问题但不给解决方案
   ❌ 忽略上下文，盲目套用规则
   ❌ 评审已删除的代码（除非引入重大风险）

4. **特殊场景**:
   - 如果代码完全没问题，明确说明"✅ 未发现明显问题"
   - 如果代码需要更多信息才能判断，说明"⚠️ 需要更多上下文：XXX"
   - 如果发现优秀实践，给予正面反馈"👍 好的实践：XXX"

---

{project_context}

# 待评审代码变更
"""
        # 添加文件变更
        max_files = int(os.getenv('MAX_REVIEW_FILES', '5'))
        for i, change in enumerate(changes[:max_files], 1):
            file_path = change.get('new_path') or change.get('old_path', '未知文件')
            diff = change.get('diff', '')[:20000]  # 增加到20000字符

            prompt += f"""
## 文件 {i}: \`{file_path}\`
\`\`\`diff
{diff}
\`\`\`
"""

        prompt += "\n请按照上述标准进行专业评审："
        return prompt
    
    def call_qwen_api(self, prompt):
        """调用千问API"""
        headers = {"Content-Type": "application/json"}
        data = {
            "model": self.model_name,
            "messages": [
                {
                    "role": "system",
                    "content": """你是代码评审专家，遵循以下原则：
1. 基于事实评审，引用具体代码行
2. 建设性优先，每指出一个问题必给解决方案
3. 区分阻塞性、重要性和建议性三个级别
4. 输出结构化Markdown格式，便于阅读
5. 保持客观，不进行人身攻击"""
                },
                {"role": "user", "content": prompt}
            ],
            "temperature": 0.2,  # 降低随机性，提高输出确定性
            "max_tokens": 4000  # 增加token限制，支持更详细的评审
        }

        try:
            response = requests.post(self.api_url, headers=headers, json=data, timeout=60)
            response.raise_for_status()
            result = response.json()
            return result['choices'][0]['message']['content']
        except Exception as e:
            return f"❌ API调用失败: {str(e)}"
    
    def post_review_to_gitlab(self, review_content):
        """将AI评审结果发布到GitLab MR讨论区"""
        if not all([self.mr_iid, self.project_id, self.gitlab_token]):
            print("❌ 缺少发布到GitLab的必要信息，跳过发布")
            return False
        
        url = f"{self.gitlab_url}/api/v4/projects/{self.project_id}/merge_requests/{self.mr_iid}/notes"
        headers = {
            "PRIVATE-TOKEN": self.gitlab_token,
            "Content-Type": "application/json"
        }
        
        # 格式化评论内容
        ai_comment = f"""## 🤖 千问AI代码评审报告 (Qwen3-32B-AWQ)

{review_content}

---
**评审信息**
- **MR**: !{self.mr_iid}
- **模型**: {self.model_name}
- **时间**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
- **触发**: GitLab CI/CD 流水线

> 💡 本评审由公司内部千问大模型自动生成，旨在辅助代码质量提升。请结合人工评审做出最终决策。"""

        data = {"body": ai_comment}
        
        try:
            print("📤 正在发布评审结果到MR讨论区...")
            response = requests.post(url, headers=headers, json=data, timeout=30)
            response.raise_for_status()
            print("✅ 评审结果已成功发布到MR讨论区！")
            return True
        except requests.exceptions.RequestException as e:
            print(f"❌ 发布评审结果失败: {e}")
            if hasattr(e, 'response') and e.response is not None:
                print(f"HTTP状态码: {e.response.status_code}")
                try:
                    error_detail = e.response.json()
                    print(f"错误详情: {error_detail}")
                except:
                    print(f"错误响应: {e.response.text}")
            return False
    
    def run(self):
        """主执行流程"""
        print("🚀 启动千问AI代码评审...")
        
        # 1. 尝试获取MR变更
        changes = self.get_mr_changes()
        
        if changes is None:
            print("🔧 切换到演示模式（MR环境不可用）")
            prompt = "请用一段话介绍你的代码评审能力"
            is_mr_mode = False
        elif not changes:
            print("🔧 切换到演示模式（无代码变更）")
            prompt = "MR中没有代码变更，请介绍你的代码评审能力"
            is_mr_mode = False
        else:
            print(f"🎯 MR模式：分析 {len(changes)} 个文件变更")
            prompt = self.create_review_prompt(changes)
            is_mr_mode = True
        
        print(f"📝 提示词长度: {len(prompt)} 字符")
        
        # 2. 调用AI
        result = self.call_qwen_api(prompt)
        print(f"🤖 AI回复长度: {len(result)} 字符")
        print("=" * 50)
        print(result)
        print("=" * 50)
        
        # 3. 如果是MR模式，发布到GitLab
        if is_mr_mode and changes:
            success = self.post_review_to_gitlab(result)
            if success:
                print("🎉 AI代码评审流程完成！结果已发布到MR讨论区。")
            else:
                print("⚠️  AI评审完成，但发布到MR失败。请查看日志手动复制结果。")
        else:
            print("ℹ️  演示模式完成，结果仅输出到日志。")
        
        return True

if __name__ == "__main__":
    reviewer = QwenCodeReviewer()
    reviewer.run()