"""
敏感信息检查服务 - F030

功能: 保密信息同步检查

"""

import os
import re
import logging
from typing import List, Dict, Optional, Set
from dataclasses import dataclass


logger = logging.getLogger(__name__)


@dataclass
class SensitiveFile:
    """包含敏感信息的文件"""
    path: str
    matched_keywords: List[str]
    line_numbers: List[int]
    suggestion: str = None


@dataclass
class SensitiveCheckResult:
    """敏感信息检查结果"""
    has_sensitive: bool
    files_count: int
    files: List[SensitiveFile]
    warning_message: str = None
    blocked: bool = False


class SensitiveContentChecker:
    """敏感内容检查器"""
    
    def __init__(self, config: Dict = None):
        """
        初始化
        
        Args:
            config: 检查配置
        """
        self.config = config or self._default_config()
        self.keywords = self._load_keywords()
        self.exclude_patterns = self.config.get('exclude_patterns', [])
        self.exclude_dirs = self.config.get('exclude_dirs', [])
    
    def _default_config(self) -> Dict:
        """默认配置"""
        return {
            'sensitive_keywords': [
                '保密', '内部', 'confidential', 'secret', 'private',
                'password', 'password', '密钥', 'key', 'token',
                'secret', 'credential', 'api_key', 'API_KEY'
            ],
            'exclude_patterns': [
                '*.lock', 'package-lock.json', 'yarn.lock',
                '*.min.js', '*.min.css'
            ],
            'exclude_dirs': [
                '.git', 'node_modules', '.venv', 'venv',
                '__pycache__', '.pytest_cache'
            ]
        }
    
    def _load_keywords(self) -> Set[str]:
        """加载敏感关键词"""
        keywords = set()
        for keyword in self.config.get('sensitive_keywords', []):
            keywords.add(keyword.lower())
        return keywords
    
    def check_files(self, project_path: str, recursive: bool = True) -> SensitiveCheckResult:
        """
        检查项目文件
        
        Args:
            project_path: 项目路径
            recursive: 是否递归检查
            
        Returns:
            SensitiveCheckResult: 检查结果
        """
        sensitive_files = []
        
        if not os.path.exists(project_path):
            return SensitiveCheckResult(
                has_sensitive=False,
                files_count=0,
                files=[],
                warning_message=f"项目路径不存在: {project_path}"
            )
        
        for root, dirs, files in os.walk(project_path):
            if not recursive:
                break
            
            dirs[:] = [d for d in dirs if not self._is_excluded_dir(d)]
            
            for file in files:
                if self._is_excluded_file(file):
                    continue
                
                file_path = os.path.join(root, file)
                result = self._check_file(file_path)
                
                if result:
                    sensitive_files.append(result)
        
        has_sensitive = len(sensitive_files) > 0
        
        warning_message = None
        blocked = False
        
        if has_sensitive:
            warning_message = f"发现 {len(sensitive_files)} 个可能包含敏感信息的文件"
            blocked = self.config.get('block_on_sensitive', False)
        
        return SensitiveCheckResult(
            has_sensitive=has_sensitive,
            files_count=len(sensitive_files),
            files=sensitive_files,
            warning_message=warning_message,
            blocked=blocked
        )
    
    def _check_file(self, file_path: str) -> Optional[SensitiveFile]:
        """检查单个文件"""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            matched_keywords = self._find_keywords(content)
            
            if not matched_keywords:
                return None
            
            line_numbers = self._find_line_numbers(file_path, content, matched_keywords)
            
            return SensitiveFile(
                path=file_path,
                matched_keywords=matched_keywords,
                line_numbers=line_numbers,
                suggestion=self._get_suggestion(matched_keywords)
            )
        except Exception as e:
            logger.debug(f"检查文件失败: {file_path}, {e}")
            return None
    
    def _find_keywords(self, content: str) -> List[str]:
        """找出匹配的关键词"""
        matched = []
        content_lower = content.lower()
        for keyword in self.keywords:
            if keyword in content_lower:
                matched.append(keyword)
        return matched
    
    def _find_line_numbers(self, file_path: str, content: str, keywords: List[str]) -> List[int]:
        """找出包含关键词的行号"""
        line_numbers = []
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                for i, line in enumerate(f, 1):
                    line_lower = line.lower()
                    for keyword in keywords:
                        if keyword in line_lower:
                            line_numbers.append(i)
                            break
        except Exception:
            pass
        return line_numbers
    
    def _get_suggestion(self, keywords: List[str]) -> str:
        """获取处理建议"""
        if any(k in keywords for k in ['password', 'password', '密钥', 'key', 'token']):
            return "此文件可能包含密钥或密码，建议从同步中排除或使用环境变量"
        elif any(k in keywords for k in ['保密', '内部', 'confidential', 'secret', 'private']):
            return "此文件标记为保密/内部，请确认是否需要同步"
        else:
            return "此文件包含敏感关键词，请检查内容是否适合同步"
    
    def _is_excluded_dir(self, dir_name: str) -> bool:
        """判断是否应该排除目录"""
        return dir_name in self.exclude_dirs or dir_name.startswith('.')
    
    def _is_excluded_file(self, file_name: str) -> bool:
        """判断是否应该排除文件"""
        for pattern in self.exclude_patterns:
            if pattern.startswith('*'):
                if file_name.endswith(pattern[1:]):
                    return True
            elif file_name == pattern:
                return True
        return False
    
    def check_content(self, content: str) -> bool:
        """检查内容是否包含敏感信息"""
        content_lower = content.lower()
        for keyword in self.keywords:
            if keyword in content_lower:
                return True
        return False
    
    def check_diff(self, diff_content: str) -> bool:
        """检查diff内容"""
        return self.check_content(diff_content)
    
    def add_keyword(self, keyword: str):
        """添加敏感关键词"""
        self.keywords.add(keyword.lower())
    
    def remove_keyword(self, keyword: str):
        """移除敏感关键词"""
        self.keywords.discard(keyword.lower())
    
    def get_keywords(self) -> List[str]:
        """获取所有敏感关键词"""
        return list(self.keywords)
    
    def validate_before_sync(self, project_path: str) -> Dict:
        """
        同步前验证
        
        Args:
            project_path: 项目路径
            
        Returns:
            Dict: 验证结果
        """
        result = self.check_files(project_path)
        
        return {
            'can_sync': not result.blocked,
            'warning': result.warning_message,
            'sensitive_files_count': result.files_count,
            'files': [
                {
                    'path': f.path,
                    'keywords': f.matched_keywords,
                    'suggestion': f.suggestion
                }
                for f in result.files[:10]
            ]
        }


class SensitiveContentScanner:
    """敏感内容扫描器 - 增强版"""
    
    def __init__(self):
        self.patterns = self._load_patterns()
        self.checker = SensitiveContentChecker()
    
    def _load_patterns(self) -> List[Dict]:
        """加载正则模式"""
        return [
            {
                'name': 'API Key',
                'pattern': r'(?i)(api[_-]?key|apikey)[\s=:"]*\S{20,}',
                'severity': 'high'
            },
            {
                'name': 'Password',
                'pattern': r'(?i)(password|passwd|pwd)[\s=:"]*\S{4,}',
                'severity': 'high'
            },
            {
                'name': 'Private Key',
                'pattern': r'-----BEGIN\s+PRIVATE\s+KEY-----',
                'severity': 'critical'
            },
            {
                'name': 'Database URL',
                'pattern': r'(?i)(mongodb(\+srv)?|postgres|mysql)://\S+',
                'severity': 'high'
            },
            {
                'name': 'JWT Token',
                'pattern': r'(?i)eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*',
                'severity': 'high'
            }
        ]
    
    def scan_content(self, content: str) -> List[Dict]:
        """扫描内容"""
        matches = []
        for pattern_info in self.patterns:
            matches.extend(self._find_matches(content, pattern_info))
        return matches
    
    def _find_matches(self, content: str, pattern_info: Dict) -> List[Dict]:
        """查找匹配"""
        matches = []
        try:
            regex = re.compile(pattern_info['pattern'])
            for match in regex.finditer(content):
                matches.append({
                    'type': pattern_info['name'],
                    'severity': pattern_info['severity'],
                    'matched': match.group()[:50] + ('...' if len(match.group()) > 50 else '')
                })
        except re.error as e:
            logger.error(f"正则匹配错误: {e}")
        return matches
    
    def scan_file(self, file_path: str) -> List[Dict]:
        """扫描文件"""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            return self.scan_content(content)
        except Exception as e:
            logger.debug(f"扫描文件失败: {file_path}, {e}")
            return []
    
    def scan_project(self, project_path: str) -> Dict:
        """扫描整个项目"""
        all_matches = []
        file_count = 0
        sensitive_count = 0
        
        for root, dirs, files in os.walk(project_path):
            dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__']]
            
            for file in files:
                if file.endswith(('.py', '.js', '.ts', '.json', '.yaml', '.yml', '.txt', '.md')):
                    file_count += 1
                    file_path = os.path.join(root, file)
                    matches = self.scan_file(file_path)
                    
                    if matches:
                        sensitive_count += 1
                        all_matches.append({
                            'file': file_path,
                            'matches': matches
                        })
        
        return {
            'total_files_scanned': file_count,
            'sensitive_files_count': sensitive_count,
            'sensitive_files': all_matches[:20],
            'can_proceed': sensitive_count == 0 or not self.checker.config.get('block_on_sensitive', False)
        }
