"""
PM-Agent v1.2.0 错误场景测试
覆盖: CLI超时、Git失败、认证、项目不存在、敏感信息阻止同步
"""
import pytest
import os
from unittest.mock import Mock, patch, MagicMock
import time


class TestCLITimeoutError:
    """ERR-001: CLI调用超时"""
    
    def test_cli_timeout_handling(self):
        """测试CLI超时的错误处理"""
        class OcCollabTimeoutError(Exception):
            def __init__(self, message):
                super().__init__(message)
                self.error_code = "1002"
        
        timeout_count = 0
        max_retries = 3
        
        def mock_cli_with_timeout():
            nonlocal timeout_count
            timeout_count += 1
            if timeout_count < max_retries:
                raise TimeoutError("Command timed out")
            raise OcCollabTimeoutError("请求超时，请重试")
        
        for _ in range(max_retries - 1):
            try:
                mock_cli_with_timeout()
            except TimeoutError:
                pass
        
        with pytest.raises(OcCollabTimeoutError) as exc_info:
            mock_cli_with_timeout()
        
        assert exc_info.value.error_code == "1002"
    
    def test_cli_timeout_retry(self):
        """测试CLI超时重试3次"""
        retry_count = 0
        max_retries = 3
        
        def mock_cli_retry():
            nonlocal retry_count
            retry_count += 1
            if retry_count < max_retries:
                raise TimeoutError()
            return {"status": "success"}
        
        result = None
        for _ in range(max_retries):
            try:
                result = mock_cli_retry()
                break
            except TimeoutError:
                continue
        
        assert result is not None
        assert result["status"] == "success"
        assert retry_count == max_retries
    
    def test_cli_timeout_error_message(self):
        """测试CLI超时错误消息"""
        error = {
            "error": "timeout",
            "error_code": "1002",
            "message": "请求超时，请重试"
        }
        
        assert error["error"] == "timeout"
        assert error["error_code"] == "1002"


class TestGitAccessError:
    """ERR-002: Git仓库访问失败"""
    
    def test_git_repo_not_exists(self):
        """测试Git仓库不存在"""
        class GitRepoNotFoundError(Exception):
            def __init__(self, message):
                super().__init__(message)
                self.error_code = "2001"
        
        def mock_clone_nonexistent():
            repo_path = "/nonexistent/path"
            if not os.path.exists(repo_path):
                raise GitRepoNotFoundError("Git仓库路径不存在")
        
        with pytest.raises(GitRepoNotFoundError) as exc_info:
            mock_clone_nonexistent()
        
        assert exc_info.value.error_code == "2001"
    
    def test_git_permission_denied(self):
        """测试Git权限不足"""
        class GitPermissionError(Exception):
            def __init__(self, message):
                super().__init__(message)
                self.error_code = "2002"
        
        def mock_clone_permission():
            raise GitPermissionError("无仓库访问权限")
        
        with pytest.raises(GitPermissionError) as exc_info:
            mock_clone_permission()
        
        assert exc_info.value.error_code == "2002"
    
    def test_git_sync_failed_error_code(self):
        """测试Git同步失败错误码"""
        error_codes = {
            "仓库不存在": "2001",
            "权限不足": "2002",
            "同步冲突": "2003",
            "网络错误": "2004"
        }
        
        assert error_codes["仓库不存在"] == "2001"
        assert error_codes["权限不足"] == "2002"


class TestAuthenticationError:
    """ERR-003: 认证失败"""
    
    def test_oc_collab_auth_failure(self):
        """测试oc-collab认证失败"""
        class OcCollabAuthError(Exception):
            def __init__(self, message):
                super().__init__(message)
                self.error_code = "1003"
        
        def mock_auth_check():
            raise OcCollabAuthError("请检查oc-collab登录状态")
        
        with pytest.raises(OcCollabAuthError) as exc_info:
            mock_auth_check()
        
        assert exc_info.value.error_code == "1003"
    
    def test_auth_error_message(self):
        """测试认证错误消息"""
        error = {
            "error": "unauthorized",
            "error_code": "1003",
            "message": "请检查oc-collab登录状态"
        }
        
        assert error["error_code"] == "1003"
        assert "认证" in error["message"] or "登录" in error["message"]


class TestProjectNotFoundError:
    """ERR-004: 项目不存在"""
    
    def test_query_nonexistent_project(self):
        """测试查询不存在的项目"""
        class ProjectNotFoundError(Exception):
            def __init__(self, message):
                super().__init__(message)
                self.error_code = "1004"
        
        def mock_query_project(project_name):
            existing_projects = ["测试项目A", "测试项目B"]
            if project_name not in existing_projects:
                raise ProjectNotFoundError("项目不存在")
        
        with pytest.raises(ProjectNotFoundError) as exc_info:
            mock_query_project("不存在的项目")
        
        assert exc_info.value.error_code == "1004"
    
    def test_project_not_found_404(self):
        """测试项目不存在返回404"""
        error_response = {
            "error": "not_found",
            "error_code": "1004",
            "message": "项目不存在"
        }
        
        assert error_response["error_code"] == "1004"


class TestSensitiveInfoBlockSync:
    """ERR-005: 敏感信息阻止同步"""
    
    def test_sensitive_info_detection(self, temp_dir):
        """测试敏感信息检测"""
        sensitive_file = f"{temp_dir}/secret.md"
        with open(sensitive_file, "w") as f:
            f.write("这是保密文件")
        
        sensitive_keywords = ["保密", "内部", "confidential"]
        
        def check_sensitive(file_path):
            try:
                with open(file_path, "r", encoding="utf-8") as f:
                    content = f.read()
                    return any(kw in content for kw in sensitive_keywords)
            except:
                return False
        
        result = check_sensitive(sensitive_file)
        assert result is True
    
    def test_block_sync_on_sensitive_detected(self, temp_dir):
        """测试检测到敏感信息时阻止同步"""
        sensitive_file = f"{temp_dir}/confidential.txt"
        with open(sensitive_file, "w") as f:
            f.write("内部机密内容")
        
        sensitive_keywords = ["保密", "内部", "confidential"]
        
        with open(sensitive_file, "r") as f:
            content = f.read()
            has_sensitive = any(kw in content for kw in sensitive_keywords)
        
        sync_result = {
            "status": "blocked" if has_sensitive else "success",
            "reason": "包含敏感信息" if has_sensitive else None,
            "matched_files": [sensitive_file] if has_sensitive else []
        }
        
        assert sync_result["status"] == "blocked"
        assert len(sync_result["matched_files"]) > 0
    
    def test_sensitive_alert_format(self, temp_dir):
        """测试敏感信息告警格式"""
        sensitive_file = f"{temp_dir}/secret.md"
        
        alert = {
            "status": "blocked",
            "reason": "包含敏感信息",
            "matched_files": [sensitive_file],
            "matched_keywords": ["保密"],
            "suggestions": ["移除敏感文件", "忽略并继续", "强制同步"]
        }
        
        assert "status" in alert
        assert "reason" in alert
        assert "matched_files" in alert
        assert len(alert["suggestions"]) > 0


class TestErrorRecovery:
    """错误恢复测试"""
    
    def test_retry_on_network_error(self):
        """测试网络错误重试"""
        attempt = 0
        max_attempts = 2
        
        def mock_network_operation():
            nonlocal attempt
            attempt += 1
            if attempt < max_attempts:
                raise ConnectionError("网络异常")
            return {"status": "success"}
        
        result = None
        for _ in range(max_attempts):
            try:
                result = mock_network_operation()
                break
            except ConnectionError:
                continue
        
        assert result is not None
        assert result["status"] == "success"
        assert attempt == max_attempts
    
    def test_fallback_on_cli_failure(self):
        """测试CLI失败时使用缓存"""
        cache_data = {
            "project": "测试项目A",
            "status": "active",
            "cached_at": "2026-02-19T10:00:00Z"
        }
        
        def mock_get_data(use_cache=False):
            if use_cache:
                return cache_data
            raise Exception("CLI调用失败")
        
        result = mock_get_data(use_cache=True)
        assert result["project"] == "测试项目A"
    
    def test_graceful_degradation(self):
        """测试优雅降级"""
        def get_status_with_fallback(primary_func, fallback_func):
            try:
                return primary_func()
            except Exception as e:
                return fallback_func()
        
        def primary():
            raise Exception("Primary service unavailable")
        
        def fallback():
            return {"status": "degraded", "message": "使用降级模式"}
        
        result = get_status_with_fallback(primary, fallback)
        assert result["status"] == "degraded"


class TestErrorLogging:
    """错误日志测试"""
    
    def test_error_log_format(self):
        """测试错误日志格式"""
        error_log = {
            "timestamp": "2026-02-19T10:00:00Z",
            "error_code": "1002",
            "error_type": "timeout",
            "message": "请求超时",
            "context": {
                "project": "测试项目A",
                "operation": "sync"
            }
        }
        
        assert "timestamp" in error_log
        assert "error_code" in error_log
        assert "context" in error_log
    
    def test_error_log_persistence(self):
        """测试错误日志持久化"""
        error_logs = []
        
        def log_error(error):
            error_logs.append(error)
        
        log_error({"code": "1001", "message": "CLI不存在"})
        log_error({"code": "1002", "message": "超时"})
        
        assert len(error_logs) == 2


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
