"""
coding:utf-8
@Software: PyCharm
@Time: 2026/2/4
@Author: xingyun
@Desc: GitLab分支查询服务 - 根据产品线获取最近创建的分支
"""

import json
import requests
from datetime import datetime, timedelta, timezone
from typing import List, Dict, Optional, Union
from concurrent.futures import ThreadPoolExecutor, as_completed

from minimax_qa_mcp.utils.logger import logger
from minimax_qa_mcp.utils.utils import Utils


class GitlabBranchService:
    """
    GitLab分支查询服务
    功能：
    1. 从Apollo获取产品线对应的GitLab Group ID
    2. 自动获取该Group下的所有项目
    3. 查询这些项目最近N天内新创建的分支
    4. 返回仓库地址+分支名称
    """

    def __init__(self, product_line: str):
        """
        初始化GitLab分支查询服务
        
        Args:
            product_line: 产品线名称，如 xingye, talkie, hailuo
        """
        self.product_line = product_line
        
        # 从配置文件读取GitLab相关配置
        try:
            self.gitlab_url = Utils.get_conf('git_info', 'gitlab_url')
        except KeyError:
            self.gitlab_url = "https://gitlab.xaminim.com"
            logger.warning(f"未找到gitlab_url配置，使用默认值: {self.gitlab_url}")
        
        try:
            self.access_token = Utils.get_conf('git_info', 'access_token')
        except KeyError:
            logger.error("未找到GitLab access_token配置，请在conf.ini中配置[git_info] access_token")
            raise ValueError("缺少GitLab access_token配置")
        
        self.headers = {'PRIVATE-TOKEN': self.access_token}
        
        # Apollo配置
        try:
            self.apollo_base_url = Utils.get_conf('apollo_info', 'apollo_base_url')
        except KeyError:
            self.apollo_base_url = "http://swing-babel-ali-prod.xaminim.com/swing/api/get_apollo_value_by_key"
            logger.info(f"使用默认Apollo URL: {self.apollo_base_url}")
        
        try:
            self.apollo_key = Utils.get_conf('apollo_info', 'product_line_gitlab_key')
        except KeyError:
            self.apollo_key = "product_line_gitlab_projects"
            logger.info(f"使用默认Apollo key: {self.apollo_key}")
        
        # 并发配置
        self.max_workers = 5
        
        logger.info(f"初始化GitlabBranchService: product_line={product_line}, gitlab_url={self.gitlab_url}")

    def get_projects_from_group(self, group_id: int) -> List[Dict]:
        """
        从GitLab Group获取所有项目
        
        Args:
            group_id: GitLab Group ID
            
        Returns:
            list: 项目列表，每项包含 project_id, project_name, project_path
        """
        logger.info(f"从GitLab Group {group_id} 获取所有项目")
        
        projects = []
        page = 1
        per_page = 100
        
        try:
            while True:
                # GitLab API: 获取Group下的所有项目
                group_projects_url = f"{self.gitlab_url}/api/v4/groups/{group_id}/projects"
                params = {
                    'per_page': per_page,
                    'page': page,
                    'include_subgroups': True,  # 包含子Group的项目
                    'archived': False  # 排除已归档的项目
                }
                
                response = requests.get(group_projects_url, headers=self.headers, params=params, timeout=30)
                
                if response.status_code != 200:
                    logger.error(f"获取Group项目列表失败: {response.status_code} - {response.text}")
                    break
                
                page_projects = response.json()
                
                if not page_projects:
                    break
                
                for proj in page_projects:
                    projects.append({
                        'project_id': proj.get('id'),
                        'project_name': proj.get('name', ''),
                        'project_path': proj.get('path_with_namespace', ''),
                        'web_url': proj.get('web_url', ''),
                        'default_branch': proj.get('default_branch', 'main')
                    })
                
                # 检查是否还有下一页
                if len(page_projects) < per_page:
                    break
                
                page += 1
            
            logger.info(f"Group {group_id} 共找到 {len(projects)} 个项目")
            return projects
            
        except requests.RequestException as e:
            logger.error(f"请求GitLab Group项目列表失败: {str(e)}")
            return []
        except Exception as e:
            logger.error(f"处理Group项目数据失败: {str(e)}")
            return []

    def get_group_config_from_apollo(self) -> Union[List[int], None]:
        """
        从Apollo获取产品线对应的GitLab Group ID列表
        
        Apollo配置格式示例:
        {
            "qa_group": [{"group_id": 1117, "group_name": "qa"}],
            "xingye": [{"group_id": 123, "group_name": "xingye-server"}],
            "talkie": [{"group_id": 456, "group_name": "talkie"}, {"group_id": 789, "group_name": "talkie-common"}]
        }
        
        Returns:
            list: Group ID列表，如果未找到返回None
        """
        logger.info(f"从Apollo获取产品线 {self.product_line} 的GitLab Group配置")
        
        try:
            # 构建Apollo请求URL
            apollo_url = f"{self.apollo_base_url}?key={self.apollo_key}"
            logger.info(f"请求Apollo URL: {apollo_url}")
            
            response = requests.get(apollo_url, timeout=10)
            
            if response.status_code != 200:
                logger.error(f"Apollo请求失败: {response.status_code} - {response.text}")
                return None
            
            # 解析响应
            result = response.json()
            logger.info(f"Apollo响应: {result}")
            
            # Apollo返回的数据格式: {"value": "...", "base_resp": {...}}
            if isinstance(result, dict):
                if 'value' in result:
                    config_data = result['value']
                elif 'data' in result:
                    config_data = result['data']
                else:
                    config_data = result
            else:
                config_data = result
            
            # 如果config_data是字符串，尝试解析为JSON
            if isinstance(config_data, str):
                try:
                    # 移除可能存在的尾随逗号（非标准JSON）
                    import re
                    cleaned_data = re.sub(r',\s*}', '}', config_data)
                    cleaned_data = re.sub(r',\s*\]', ']', cleaned_data)
                    config_data = json.loads(cleaned_data)
                except json.JSONDecodeError as e:
                    logger.error(f"Apollo返回的数据不是有效的JSON: {config_data}, 错误: {e}")
                    return None
            
            # 获取对应产品线的Group配置
            if self.product_line in config_data:
                group_config = config_data[self.product_line]
                group_ids = []
                
                # 格式: [{"group_id": 1117, "group_name": "qa"}, ...]
                if isinstance(group_config, list):
                    for item in group_config:
                        if isinstance(item, dict) and 'group_id' in item:
                            group_ids.append(item['group_id'])
                        elif isinstance(item, int):
                            # 兼容直接配置数字的情况
                            group_ids.append(item)
                    
                    if group_ids:
                        logger.info(f"产品线 {self.product_line} 配置了 {len(group_ids)} 个Group: {group_ids}")
                        return group_ids
                    else:
                        logger.warning(f"产品线 {self.product_line} 的Group配置为空")
                        return None
                elif isinstance(group_config, int):
                    # 兼容直接配置单个group_id的情况
                    return [group_config]
                else:
                    logger.warning(f"产品线 {self.product_line} 的Group配置格式不正确: {group_config}")
                    return None
            else:
                logger.warning(f"未找到产品线 {self.product_line} 的配置，可用的产品线: {list(config_data.keys())}")
                return None
                
        except requests.RequestException as e:
            logger.error(f"请求Apollo失败: {str(e)}")
            return None
        except Exception as e:
            logger.error(f"解析Apollo响应失败: {str(e)}")
            return None

    def get_all_projects(self) -> List[Dict]:
        """
        获取产品线对应的所有GitLab项目
        
        通过Apollo获取Group ID列表，然后自动获取这些Group下的所有项目
        
        Returns:
            list: 项目列表，每项包含 project_id, project_name, project_path
        """
        logger.info(f"获取产品线 {self.product_line} 的所有GitLab项目")
        
        # 从Apollo获取Group ID列表
        group_ids = self.get_group_config_from_apollo()
        
        if not group_ids:
            logger.warning(f"产品线 {self.product_line} 未配置任何GitLab Group")
            return []
        
        all_projects = []
        
        # 遍历所有Group，获取其下的项目
        for group_id in group_ids:
            projects = self.get_projects_from_group(group_id)
            all_projects.extend(projects)
        
        # 去重（按project_id）
        seen_ids = set()
        unique_projects = []
        for proj in all_projects:
            if proj['project_id'] not in seen_ids:
                seen_ids.add(proj['project_id'])
                unique_projects.append(proj)
        
        logger.info(f"产品线 {self.product_line} 共找到 {len(unique_projects)} 个唯一项目")
        return unique_projects

    def get_recent_branches(self, project_id: int, project_name: str, 
                           project_path: str, days: int = 3) -> List[Dict]:
        """
        获取项目最近N天内新创建的分支
        
        Args:
            project_id: GitLab项目ID
            project_name: 项目名称
            project_path: 项目路径
            days: 查询最近多少天的分支，默认3天
            
        Returns:
            list: 分支列表，每项包含 repo_url, project_name, branch_name, created_at, author
        """
        logger.info(f"获取项目 {project_name} (ID: {project_id}) 最近 {days} 天的分支")
        
        recent_branches = []
        
        try:
            # 计算时间阈值
            now = datetime.now(timezone(timedelta(hours=8)))
            threshold = now - timedelta(days=days)
            
            # 获取分支列表
            branches_url = f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/branches"
            params = {
                'per_page': 100  # 获取更多分支
            }
            
            response = requests.get(branches_url, headers=self.headers, params=params, timeout=30)
            
            if response.status_code != 200:
                logger.error(f"获取分支列表失败: {response.status_code} - {response.text}")
                return []
            
            branches = response.json()
            logger.info(f"项目 {project_name} 共有 {len(branches)} 个分支")
            
            # 筛选最近N天创建的分支
            for branch in branches:
                branch_name = branch.get('name', '')
                commit = branch.get('commit', {})
                committed_date_str = commit.get('committed_date', '')
                author_name = commit.get('author_name', 'unknown')
                
                if not committed_date_str:
                    continue
                
                # 解析提交时间
                try:
                    # GitLab返回的时间格式: 2026-02-03T10:30:00.000+08:00
                    committed_date = datetime.fromisoformat(committed_date_str.replace('Z', '+00:00'))
                    
                    # 检查是否在时间范围内
                    if committed_date >= threshold:
                        # 排除主分支
                        if branch_name not in ['main', 'master', 'develop', 'release']:
                            repo_url = f"{self.gitlab_url}/{project_path}"
                            recent_branches.append({
                                'repo_url': repo_url,
                                'project_name': project_name,
                                'branch_name': branch_name,
                                'created_at': committed_date_str,
                                'author': author_name
                            })
                            logger.info(f"找到新分支: {branch_name} (创建于 {committed_date_str})")
                except (ValueError, TypeError) as e:
                    logger.warning(f"解析分支 {branch_name} 的时间失败: {e}")
                    continue
            
            logger.info(f"项目 {project_name} 最近 {days} 天有 {len(recent_branches)} 个新分支")
            return recent_branches
            
        except requests.RequestException as e:
            logger.error(f"请求GitLab分支列表失败: {str(e)}")
            return []
        except Exception as e:
            logger.error(f"处理分支数据失败: {str(e)}")
            return []

    def analyze_all_projects(self, days: int = 3) -> List[Dict]:
        """
        分析所有项目的新分支
        
        Args:
            days: 查询最近多少天的分支，默认3天
            
        Returns:
            list: 所有新分支的列表
        """
        logger.info(f"开始分析产品线 {self.product_line} 所有项目最近 {days} 天的新分支")
        
        # 从Apollo获取Group配置，然后自动获取Group下所有项目
        projects = self.get_all_projects()
        
        if not projects:
            logger.warning(f"产品线 {self.product_line} 没有找到任何GitLab项目")
            return []
        
        all_branches = []
        
        # 使用线程池并发查询各项目的分支
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            future_to_project = {}
            
            for project in projects:
                project_id = project.get('project_id')
                project_name = project.get('project_name', '')
                project_path = project.get('project_path', '')
                
                if not project_id:
                    logger.warning(f"项目配置缺少project_id: {project}")
                    continue
                
                future = executor.submit(
                    self.get_recent_branches,
                    project_id,
                    project_name,
                    project_path,
                    days
                )
                future_to_project[future] = project_name
            
            # 收集结果
            for future in as_completed(future_to_project):
                project_name = future_to_project[future]
                try:
                    branches = future.result()
                    all_branches.extend(branches)
                except Exception as e:
                    logger.error(f"获取项目 {project_name} 分支失败: {str(e)}")
        
        # 按创建时间排序（最新的在前）
        all_branches.sort(key=lambda x: x.get('created_at', ''), reverse=True)
        
        logger.info(f"产品线 {self.product_line} 共找到 {len(all_branches)} 个新分支")
        return all_branches


def get_recent_branches_by_product_line(product_line: str, days: int = 3) -> List[Dict]:
    """
    获取产品线最近N天新创建的分支列表（供外部调用的便捷函数）
    
    Args:
        product_line: 产品线名称，如 xingye, talkie, hailuo
        days: 查询最近多少天的分支，默认3天
    
    Returns:
        列表，包含 [{"repo_url": "xxx", "branch_name": "xxx", "created_at": "xxx", "author": "xxx"}, ...]
    """
    service = GitlabBranchService(product_line)
    return service.analyze_all_projects(days)


class GitlabRepoTypeDetector:
    """
    GitLab仓库类型检测器
    通过分析仓库的文件结构判断是 Server 还是 FE 项目
    """
    
    # Server 项目特征文件和目录
    SERVER_INDICATORS = {
        'go': {
            'files': ['go.mod', 'go.sum'],
            'dirs': ['cmd/', 'pkg/', 'internal/', 'api/'],
            'weight': 1.0
        },
        'java': {
            'files': ['pom.xml', 'build.gradle', 'build.gradle.kts'],
            'dirs': ['src/main/java/', 'src/main/resources/'],
            'weight': 1.0
        },
        'python': {
            'files': ['requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'],
            'dirs': ['src/', 'app/', 'api/'],
            'weight': 0.8  # Python 也可能是脚本项目
        },
        'rust': {
            'files': ['Cargo.toml', 'Cargo.lock'],
            'dirs': ['src/'],
            'weight': 1.0
        },
        'csharp': {
            'files': ['*.csproj', '*.sln'],
            'dirs': ['Controllers/', 'Services/'],
            'weight': 1.0
        }
    }
    
    # FE 项目特征文件和目录
    FE_INDICATORS = {
        'react': {
            'files': ['package.json'],
            'dirs': ['src/components/', 'src/pages/', 'public/'],
            'package_deps': ['react', 'react-dom'],
            'weight': 1.0
        },
        'vue': {
            'files': ['package.json', 'vue.config.js', 'vite.config.ts'],
            'dirs': ['src/components/', 'src/views/', 'public/'],
            'package_deps': ['vue'],
            'weight': 1.0
        },
        'angular': {
            'files': ['package.json', 'angular.json'],
            'dirs': ['src/app/', 'src/assets/'],
            'package_deps': ['@angular/core'],
            'weight': 1.0
        },
        'nextjs': {
            'files': ['package.json', 'next.config.js', 'next.config.mjs'],
            'dirs': ['pages/', 'app/', 'components/'],
            'package_deps': ['next'],
            'weight': 1.0
        }
    }
    
    def __init__(self):
        """初始化检测器"""
        try:
            self.gitlab_url = Utils.get_conf('git_info', 'gitlab_url')
        except KeyError:
            self.gitlab_url = "https://gitlab.xaminim.com"
        
        try:
            self.access_token = Utils.get_conf('git_info', 'access_token')
        except KeyError:
            raise ValueError("缺少GitLab access_token配置")
        
        self.headers = {'PRIVATE-TOKEN': self.access_token}
    
    def get_repo_tree(self, project_id: int, branch: str = None, path: str = '', recursive: bool = False) -> List[Dict]:
        """
        获取仓库文件树
        
        Args:
            project_id: GitLab 项目 ID
            branch: 分支名，默认使用默认分支
            path: 子路径
            recursive: 是否递归获取
            
        Returns:
            文件树列表
        """
        url = f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/tree"
        params = {
            'per_page': 100,
            'recursive': recursive
        }
        if branch:
            params['ref'] = branch
        if path:
            params['path'] = path
        
        try:
            response = requests.get(url, headers=self.headers, params=params, timeout=30)
            if response.status_code == 200:
                return response.json()
            else:
                logger.warning(f"获取仓库文件树失败: {response.status_code}")
                return []
        except Exception as e:
            logger.error(f"请求GitLab文件树失败: {e}")
            return []
    
    def get_file_content(self, project_id: int, file_path: str, branch: str = None) -> Optional[str]:
        """
        获取文件内容
        
        Args:
            project_id: GitLab 项目 ID
            file_path: 文件路径
            branch: 分支名
            
        Returns:
            文件内容字符串
        """
        import urllib.parse
        encoded_path = urllib.parse.quote(file_path, safe='')
        url = f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/files/{encoded_path}/raw"
        params = {}
        if branch:
            params['ref'] = branch
        
        try:
            response = requests.get(url, headers=self.headers, params=params, timeout=30)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except Exception as e:
            logger.error(f"获取文件内容失败: {e}")
            return None
    
    def detect_repo_type(self, project_id: int, branch: str = None) -> Dict:
        """
        检测仓库类型
        
        Args:
            project_id: GitLab 项目 ID
            branch: 分支名，默认使用默认分支
            
        Returns:
            {
                "type": "server" | "fe" | "mixed" | "unknown",
                "language": "go" | "java" | "python" | "react" | "vue" | ...,
                "framework": 框架名称,
                "confidence": 0.0-1.0,
                "indicators": ["go.mod", "cmd/", ...]
            }
        """
        logger.info(f"检测项目 {project_id} 的仓库类型")
        
        # 获取根目录文件列表
        tree = self.get_repo_tree(project_id, branch)
        if not tree:
            return {
                "type": "unknown",
                "language": None,
                "framework": None,
                "confidence": 0.0,
                "indicators": [],
                "error": "无法获取仓库文件树"
            }
        
        # 提取文件名和目录名
        files = set()
        dirs = set()
        for item in tree:
            name = item.get('name', '')
            item_type = item.get('type', '')
            if item_type == 'blob':
                files.add(name)
            elif item_type == 'tree':
                dirs.add(name + '/')
        
        logger.info(f"根目录文件: {files}")
        logger.info(f"根目录目录: {dirs}")
        
        # 检测 Server 特征
        server_score = 0.0
        server_lang = None
        server_indicators = []
        
        for lang, indicators in self.SERVER_INDICATORS.items():
            lang_score = 0
            lang_indicators = []
            
            # 检查特征文件
            for f in indicators.get('files', []):
                if f in files:
                    lang_score += 1
                    lang_indicators.append(f)
            
            # 检查特征目录
            for d in indicators.get('dirs', []):
                dir_name = d.rstrip('/')
                if dir_name + '/' in dirs:
                    lang_score += 0.5
                    lang_indicators.append(d)
            
            if lang_score > server_score:
                server_score = lang_score * indicators.get('weight', 1.0)
                server_lang = lang
                server_indicators = lang_indicators
        
        # 检测 FE 特征
        fe_score = 0.0
        fe_framework = None
        fe_indicators = []
        
        # 先检查是否有 package.json
        has_package_json = 'package.json' in files
        
        if has_package_json:
            # 读取 package.json 分析依赖
            package_content = self.get_file_content(project_id, 'package.json', branch)
            deps = set()
            if package_content:
                try:
                    package_json = json.loads(package_content)
                    deps.update(package_json.get('dependencies', {}).keys())
                    deps.update(package_json.get('devDependencies', {}).keys())
                except json.JSONDecodeError:
                    pass
            
            for framework, indicators in self.FE_INDICATORS.items():
                framework_score = 0
                framework_indicators = []
                
                # 检查 package.json 依赖
                for dep in indicators.get('package_deps', []):
                    if dep in deps:
                        framework_score += 2
                        framework_indicators.append(f"dep:{dep}")
                
                # 检查特征文件
                for f in indicators.get('files', []):
                    if f in files and f != 'package.json':
                        framework_score += 0.5
                        framework_indicators.append(f)
                
                # 检查特征目录
                for d in indicators.get('dirs', []):
                    dir_name = d.rstrip('/')
                    if dir_name + '/' in dirs:
                        framework_score += 0.5
                        framework_indicators.append(d)
                
                if framework_score > fe_score:
                    fe_score = framework_score * indicators.get('weight', 1.0)
                    fe_framework = framework
                    fe_indicators = framework_indicators
        
        # 判断类型
        logger.info(f"Server 得分: {server_score}, FE 得分: {fe_score}")
        
        if server_score > 0 and fe_score > 0:
            # 混合项目
            if server_score > fe_score * 1.5:
                repo_type = "server"
                language = server_lang
                framework = None
                confidence = min(server_score / 3, 1.0)
                indicators = server_indicators
            elif fe_score > server_score * 1.5:
                repo_type = "fe"
                language = fe_framework
                framework = fe_framework
                confidence = min(fe_score / 3, 1.0)
                indicators = fe_indicators
            else:
                repo_type = "mixed"
                language = f"{server_lang}+{fe_framework}"
                framework = fe_framework
                confidence = min((server_score + fe_score) / 5, 1.0)
                indicators = server_indicators + fe_indicators
        elif server_score > 0:
            repo_type = "server"
            language = server_lang
            framework = None
            confidence = min(server_score / 2, 1.0)
            indicators = server_indicators
        elif fe_score > 0:
            repo_type = "fe"
            language = fe_framework
            framework = fe_framework
            confidence = min(fe_score / 2, 1.0)
            indicators = fe_indicators
        else:
            repo_type = "unknown"
            language = None
            framework = None
            confidence = 0.0
            indicators = []
        
        result = {
            "type": repo_type,
            "language": language,
            "framework": framework,
            "confidence": round(confidence, 2),
            "indicators": indicators
        }
        
        logger.info(f"检测结果: {result}")
        return result


def detect_repo_type(project_id: int, branch: str = None) -> Dict:
    """
    检测仓库类型（供外部调用的便捷函数）
    
    Args:
        project_id: GitLab 项目 ID
        branch: 分支名，默认使用默认分支
    
    Returns:
        {
            "type": "server" | "fe" | "mixed" | "unknown",
            "language": 语言/框架名称,
            "framework": 框架名称（FE 项目）,
            "confidence": 置信度 0.0-1.0,
            "indicators": 检测到的特征列表
        }
    """
    detector = GitlabRepoTypeDetector()
    return detector.detect_repo_type(project_id, branch)


class GitlabBedrockResolver:
    """
    GitLab 仓库与 Bedrock 服务关联解析器
    
    通过解析仓库的 CI 配置文件来获取 Bedrock 部署信息
    """
    
    def __init__(self):
        """初始化解析器"""
        try:
            self.gitlab_url = Utils.get_conf('git_info', 'gitlab_url')
        except KeyError:
            self.gitlab_url = "https://gitlab.xaminim.com"
        
        try:
            self.access_token = Utils.get_conf('git_info', 'access_token')
        except KeyError:
            raise ValueError("缺少GitLab access_token配置")
        
        self.headers = {'PRIVATE-TOKEN': self.access_token}
    
    def get_file_content(self, project_id: int, file_path: str, branch: str = None) -> Optional[str]:
        """
        获取文件内容
        
        Args:
            project_id: GitLab 项目 ID
            file_path: 文件路径
            branch: 分支名
            
        Returns:
            文件内容字符串
        """
        import urllib.parse
        encoded_path = urllib.parse.quote(file_path, safe='')
        url = f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/files/{encoded_path}/raw"
        params = {}
        if branch:
            params['ref'] = branch
        
        try:
            response = requests.get(url, headers=self.headers, params=params, timeout=30)
            if response.status_code == 200:
                return response.text
            else:
                logger.debug(f"获取文件 {file_path} 失败: {response.status_code}")
                return None
        except Exception as e:
            logger.error(f"获取文件内容失败: {e}")
            return None
    
    def get_project_info(self, project_id: int) -> Optional[Dict]:
        """
        获取项目基本信息
        
        Args:
            project_id: GitLab 项目 ID
            
        Returns:
            项目信息字典
        """
        url = f"{self.gitlab_url}/api/v4/projects/{project_id}"
        
        try:
            response = requests.get(url, headers=self.headers, timeout=30)
            if response.status_code == 200:
                return response.json()
            else:
                logger.warning(f"获取项目信息失败: {response.status_code}")
                return None
        except Exception as e:
            logger.error(f"请求项目信息失败: {e}")
            return None
    
    def parse_gitlab_ci(self, ci_content: str) -> Dict:
        """
        解析 .gitlab-ci.yml 内容，提取 Bedrock 相关配置
        
        Args:
            ci_content: .gitlab-ci.yml 文件内容
            
        Returns:
            Bedrock 配置信息
        """
        import re
        import yaml
        
        result = {
            'psm': None,
            'bedrock_project': None,
            'bedrock_app': None,
            'image_repo': None,
            'deploy_stages': [],
            'raw_variables': {}
        }
        
        try:
            # 尝试解析 YAML
            ci_config = yaml.safe_load(ci_content)
            
            if not ci_config:
                return result
            
            # 1. 检查全局 variables
            global_vars = ci_config.get('variables', {})
            if global_vars:
                result['raw_variables'].update(global_vars)
                
                # 提取常见的 Bedrock 相关变量
                for key, value in global_vars.items():
                    key_upper = key.upper()
                    if key_upper == 'PSM' or key_upper.endswith('_PSM'):
                        result['psm'] = value
                    elif key_upper == 'BEDROCK_PROJECT' or key_upper == 'PROJECT':
                        result['bedrock_project'] = value
                    elif key_upper == 'BEDROCK_APP' or key_upper == 'APP_NAME':
                        result['bedrock_app'] = value
                    elif 'IMAGE' in key_upper or 'REGISTRY' in key_upper:
                        if isinstance(value, str) and 'harbor' in value.lower():
                            result['image_repo'] = value
            
            # 2. 检查各个 job 中的 variables
            for job_name, job_config in ci_config.items():
                if isinstance(job_config, dict):
                    job_vars = job_config.get('variables', {})
                    if job_vars:
                        for key, value in job_vars.items():
                            key_upper = key.upper()
                            if key_upper == 'PSM' and not result['psm']:
                                result['psm'] = value
                            elif key_upper == 'BEDROCK_PROJECT' and not result['bedrock_project']:
                                result['bedrock_project'] = value
                            elif key_upper == 'BEDROCK_APP' and not result['bedrock_app']:
                                result['bedrock_app'] = value
                    
                    # 检查是否有 deploy 相关的 stage
                    stage = job_config.get('stage', '')
                    script = job_config.get('script', [])
                    if isinstance(stage, str) and 'deploy' in stage.lower():
                        result['deploy_stages'].append({
                            'job_name': job_name,
                            'stage': stage
                        })
                    # 检查 script 中是否有 bedrock 相关命令
                    if isinstance(script, list):
                        for cmd in script:
                            if isinstance(cmd, str) and 'bedrock' in cmd.lower():
                                if job_name not in [s.get('job_name') for s in result['deploy_stages']]:
                                    result['deploy_stages'].append({
                                        'job_name': job_name,
                                        'stage': stage or 'unknown'
                                    })
                                break
            
        except yaml.YAMLError as e:
            logger.warning(f"YAML 解析失败，尝试正则提取: {e}")
            # 降级：使用正则表达式提取
            
            # 提取 PSM
            psm_match = re.search(r'PSM[:\s]*["\']?([a-zA-Z0-9_.]+)["\']?', ci_content, re.IGNORECASE)
            if psm_match:
                result['psm'] = psm_match.group(1)
            
            # 提取 BEDROCK_PROJECT
            project_match = re.search(r'BEDROCK_PROJECT[:\s]*["\']?([a-zA-Z0-9_-]+)["\']?', ci_content, re.IGNORECASE)
            if project_match:
                result['bedrock_project'] = project_match.group(1)
            
            # 提取镜像仓库
            image_match = re.search(r'(txharbor\.xaminim\.com/[a-zA-Z0-9_/-]+)', ci_content)
            if image_match:
                result['image_repo'] = image_match.group(1)
        
        return result
    
    def parse_makefile(self, makefile_content: str) -> Dict:
        """
        解析 Makefile 内容，提取 Bedrock 相关配置
        
        Args:
            makefile_content: Makefile 文件内容
            
        Returns:
            Bedrock 配置信息
        """
        import re
        
        result = {
            'psm': None,
            'bedrock_app': None,
            'image_repo': None
        }
        
        # 提取 PSM
        psm_match = re.search(r'PSM\s*[:?]?=\s*([a-zA-Z0-9_.]+)', makefile_content)
        if psm_match:
            result['psm'] = psm_match.group(1)
        
        # 提取 APP_NAME
        app_match = re.search(r'APP_NAME\s*[:?]?=\s*([a-zA-Z0-9_-]+)', makefile_content)
        if app_match:
            result['bedrock_app'] = app_match.group(1)
        
        # 提取镜像仓库
        image_match = re.search(r'(txharbor\.xaminim\.com/[a-zA-Z0-9_/-]+)', makefile_content)
        if image_match:
            result['image_repo'] = image_match.group(1)
        
        return result
    
    def infer_from_project_name(self, project_name: str, project_path: str) -> Dict:
        """
        根据项目名称推断 Bedrock 配置
        
        Args:
            project_name: 项目名称
            project_path: 项目路径 (如 qa/apicase_generate_tool)
            
        Returns:
            推断的 Bedrock 配置
        """
        import re
        
        result = {
            'psm_guess': None,
            'app_guess': None,
            'confidence': 'low'
        }
        
        # 从路径中提取 group 和 project
        path_parts = project_path.split('/')
        if len(path_parts) >= 2:
            group = path_parts[-2]
            project = path_parts[-1]
            
            # 转换项目名为 PSM 格式 (下划线转点号)
            # 例如: qa/apicase_generate_tool -> qa.apicase.generate_tool
            psm_project = project.replace('-', '_').lower()
            result['psm_guess'] = f"{group.lower()}.{psm_project}"
            
            # 应用名通常是项目名的 kebab-case
            result['app_guess'] = project.replace('_', '-').lower()
            
            result['confidence'] = 'medium'
        
        return result
    
    def get_bedrock_info(self, project_id: int, branch: str = None) -> Dict:
        """
        获取仓库对应的 Bedrock 部署信息
        
        Args:
            project_id: GitLab 项目 ID
            branch: 分支名，默认使用默认分支
            
        Returns:
            {
                "found": True/False,
                "source": "gitlab-ci" | "makefile" | "inferred",
                "psm": "xxx.yyy.zzz",
                "bedrock_project": "project-name",
                "bedrock_app": "app-name",
                "image_repo": "txharbor.xaminim.com/...",
                "deploy_stages": [...],
                "confidence": "high" | "medium" | "low",
                "project_info": {...}
            }
        """
        logger.info(f"获取项目 {project_id} 的 Bedrock 部署信息")
        
        result = {
            'found': False,
            'source': None,
            'psm': None,
            'bedrock_project': None,
            'bedrock_app': None,
            'image_repo': None,
            'deploy_stages': [],
            'confidence': 'none',
            'project_info': None
        }
        
        # 1. 获取项目基本信息
        project_info = self.get_project_info(project_id)
        if project_info:
            result['project_info'] = {
                'name': project_info.get('name'),
                'path': project_info.get('path_with_namespace'),
                'web_url': project_info.get('web_url'),
                'default_branch': project_info.get('default_branch', 'main')
            }
            
            if not branch:
                branch = project_info.get('default_branch', 'main')
        
        # 2. 尝试读取 .gitlab-ci.yml
        ci_content = self.get_file_content(project_id, '.gitlab-ci.yml', branch)
        if ci_content:
            ci_info = self.parse_gitlab_ci(ci_content)
            
            if ci_info.get('psm') or ci_info.get('bedrock_project') or ci_info.get('bedrock_app'):
                result['found'] = True
                result['source'] = 'gitlab-ci'
                result['psm'] = ci_info.get('psm')
                result['bedrock_project'] = ci_info.get('bedrock_project')
                result['bedrock_app'] = ci_info.get('bedrock_app')
                result['image_repo'] = ci_info.get('image_repo')
                result['deploy_stages'] = ci_info.get('deploy_stages', [])
                result['confidence'] = 'high'
                
                logger.info(f"从 .gitlab-ci.yml 解析到 Bedrock 配置: {result}")
                return result
        
        # 3. 尝试读取 Makefile
        makefile_content = self.get_file_content(project_id, 'Makefile', branch)
        if makefile_content:
            makefile_info = self.parse_makefile(makefile_content)
            
            if makefile_info.get('psm') or makefile_info.get('bedrock_app'):
                result['found'] = True
                result['source'] = 'makefile'
                result['psm'] = makefile_info.get('psm')
                result['bedrock_app'] = makefile_info.get('bedrock_app')
                result['image_repo'] = makefile_info.get('image_repo')
                result['confidence'] = 'medium'
                
                logger.info(f"从 Makefile 解析到 Bedrock 配置: {result}")
                return result
        
        # 4. 尝试读取 deploy.yaml 或 bedrock.yaml
        for config_file in ['deploy.yaml', 'bedrock.yaml', '.bedrock.yaml', 'deploy/bedrock.yaml']:
            config_content = self.get_file_content(project_id, config_file, branch)
            if config_content:
                try:
                    import yaml
                    config = yaml.safe_load(config_content)
                    if config:
                        result['found'] = True
                        result['source'] = config_file
                        result['psm'] = config.get('psm') or config.get('PSM')
                        result['bedrock_project'] = config.get('project') or config.get('bedrock_project')
                        result['bedrock_app'] = config.get('app') or config.get('app_name')
                        result['confidence'] = 'high'
                        
                        logger.info(f"从 {config_file} 解析到 Bedrock 配置: {result}")
                        return result
                except Exception:
                    continue
        
        # 5. 降级：根据项目名推断
        if project_info:
            inferred = self.infer_from_project_name(
                project_info.get('name', ''),
                project_info.get('path_with_namespace', '')
            )
            
            result['found'] = True
            result['source'] = 'inferred'
            result['psm'] = inferred.get('psm_guess')
            result['bedrock_app'] = inferred.get('app_guess')
            result['confidence'] = 'low'
            
            logger.info(f"通过项目名推断 Bedrock 配置: {result}")
        
        return result


def get_bedrock_info_by_repo(project_id: int, branch: str = None) -> Dict:
    """
    获取 Git 仓库对应的 Bedrock 部署信息（供外部调用的便捷函数）
    
    通过解析仓库的 .gitlab-ci.yml、Makefile 等配置文件，
    提取 Bedrock 部署相关信息，包括 PSM、项目名、应用名等。
    
    Args:
        project_id: GitLab 项目 ID
        branch: 分支名，默认使用默认分支
    
    Returns:
        {
            "found": True/False,
            "source": "gitlab-ci" | "makefile" | "inferred",
            "psm": "xxx.yyy.zzz",
            "bedrock_project": "project-name",
            "bedrock_app": "app-name",
            "image_repo": "txharbor.xaminim.com/...",
            "deploy_stages": [...],
            "confidence": "high" | "medium" | "low",
            "project_info": {...}
        }
    """
    resolver = GitlabBedrockResolver()
    return resolver.get_bedrock_info(project_id, branch)


if __name__ == '__main__':
    # 测试代码
    result = get_recent_branches_by_product_line("qa_group", 3)
    print(f"找到 {len(result)} 个新分支:")
    for branch in result:
        print(f"  - {branch['project_name']}/{branch['branch_name']} ({branch['created_at']})")
