"""
PM-Agent v1.2.0 后端单元测试
覆盖: M21, M22, M23, M24, F030
"""
import pytest
from unittest.mock import Mock, patch, MagicMock


class TestGitSyncService:
    """M21: Git同步服务测试"""
    
    def test_sync_project_success(self, mock_git_sync_service):
        """测试同步单个项目成功"""
        result = mock_git_sync_service.sync_project("测试项目A", "/path/to/test_repo")
        assert result.status == "success"
        assert result.files_synced > 0
    
    def test_sync_project_with_path(self, mock_git_sync_service, temp_dir):
        """测试带路径的项目同步"""
        service = mock_git_sync_service.__class__(base_path=temp_dir)
        result = service.sync_project("新项目", temp_dir)
        assert result.status == "success"
    
    def test_sync_all_projects(self, mock_git_sync_service):
        """测试同步所有项目"""
        results = mock_git_sync_service.sync_all()
        assert isinstance(results, dict)
        assert len(results) > 0
    
    def test_get_last_sync_time(self, mock_git_sync_service):
        """测试获取最后同步时间"""
        sync_time = mock_git_sync_service.get_last_sync_time("测试项目A")
        assert sync_time is not None
        assert "2026" in sync_time
    
    def test_get_sync_status(self, mock_git_sync_service):
        """测试获取同步状态"""
        status = mock_git_sync_service.get_sync_status("测试项目A")
        assert "last_sync" in status
        assert "status" in status
        assert status["status"] == "ok"


class TestOcCollabClient:
    """M22: oc-collab CLI封装测试"""
    
    def test_get_project_status(self, mock_oc_collab_client):
        """测试获取项目状态"""
        status = mock_oc_collab_client.get_project_status("测试项目A")
        assert "project_name" in status
        assert "status" in status
        assert status["project_name"] == "测试项目A"
    
    def test_get_project_todos(self, mock_oc_collab_client):
        """测试获取TODO列表"""
        todos = mock_oc_collab_client.get_project_todos("测试项目A")
        assert isinstance(todos, list)
        assert len(todos) > 0
        assert "id" in todos[0]
        assert "content" in todos[0]
        assert "status" in todos[0]
    
    def test_get_project_todos_with_status_filter(self, mock_oc_collab_client):
        """测试带状态过滤的TODO查询"""
        todos = mock_oc_collab_client.get_project_todos("测试项目A", status="pending")
        assert isinstance(todos, list)
    
    def test_get_project_progress(self, mock_oc_collab_client):
        """测试获取项目进度"""
        progress = mock_oc_collab_client.get_project_progress("测试项目A")
        assert "requirements" in progress
        assert "bugs" in progress
        assert "todos" in progress
        assert progress["requirements"]["total"] == 10
    
    def test_get_changes(self, mock_oc_collab_client):
        """测试获取变更记录"""
        changes = mock_oc_collab_client.get_changes("测试项目A", "2026-02-19T10:00:00Z")
        assert isinstance(changes, list)
    
    def test_is_cli_available(self, mock_oc_collab_client):
        """测试CLI可用性检查"""
        available = mock_oc_collab_client.is_cli_available()
        assert available is True


class TestOcCollabErrorHandling:
    """M22: oc-collab错误处理测试"""
    
    def test_cli_timeout_error(self):
        """测试CLI超时错误"""
        class OcCollabTimeoutError(Exception):
            pass
        
        def mock_call_cli_timeout():
            for attempt in range(3):
                pass
            raise OcCollabTimeoutError("CLI调用超时")
        
        with pytest.raises(OcCollabTimeoutError):
            mock_call_cli_timeout()
    
    def test_json_parse_error(self):
        """测试JSON解析错误"""
        class OcCollabParseError(Exception):
            pass
        
        def mock_json_error():
            import json
            raise OcCollabParseError("JSON解析失败")
        
        with pytest.raises(OcCollabParseError):
            mock_json_error()
    
    def test_project_not_found_error(self):
        """测试项目不存在错误"""
        class OcCollabNotFoundError(Exception):
            pass
        
        def mock_project_not_found():
            raise OcCollabNotFoundError("项目不存在")
        
        with pytest.raises(OcCollabNotFoundError):
            mock_project_not_found()


class TestStatusFeedbackService:
    """M23: 状态反馈服务测试"""
    
    def test_poll_changes(self, mock_oc_collab_client):
        """测试轮询状态变更"""
        from unittest.mock import MagicMock
        storage = MagicMock()
        
        class MockStatusFeedbackService:
            def __init__(self, client, storage):
                self.client = client
                self.storage = storage
                self.last_check_time = None
                
            def poll_changes(self):
                return self.client.get_changes("测试项目", "2026-02-19T10:00:00Z")
        
        service = MockStatusFeedbackService(mock_oc_collab_client, storage)
        changes = service.poll_changes()
        assert isinstance(changes, list)
    
    def test_update_local_status(self, mock_oc_collab_client):
        """测试更新本地状态"""
        storage = MagicMock()
        
        class MockStatusFeedbackService:
            def __init__(self, client, storage):
                self.client = client
                self.storage = storage
                
            def update_local_status(self, changes):
                return len(changes)
        
        service = MockStatusFeedbackService(mock_oc_collab_client, storage)
        changes = [{"type": "todo", "id": "TODO-001", "new_status": "completed"}]
        count = service.update_local_status(changes)
        assert count == 1


class TestProgressService:
    """M24: Dashboard进度服务测试"""
    
    def test_calculate_overall_progress(self, mock_oc_collab_client):
        """测试进度计算公式"""
        class ProgressItem:
            def __init__(self, total, completed=0, in_progress=0, resolved=0):
                self.total = total
                self.completed = completed
                self.in_progress = in_progress
                self.resolved = resolved if resolved else completed
                
        class ProjectProgress:
            def __init__(self, requirements, bugs, todos):
                self.requirements = requirements
                self.bugs = bugs
                self.todos = todos
        
        class ProgressService:
            def calculate_overall_progress(self, progress):
                req_progress = progress.requirements.completed / progress.requirements.total if progress.requirements.total > 0 else 0
                bug_progress = progress.bugs.resolved / progress.bugs.total if progress.bugs.total > 0 else 0
                todo_progress = progress.todos.completed / progress.todos.total if progress.todos.total > 0 else 0
                
                return int((req_progress * 0.4 + bug_progress * 0.3 + todo_progress * 0.3) * 100)
        
        progress = ProjectProgress(
            requirements=ProgressItem(total=10, completed=5),
            bugs=ProgressItem(total=5, resolved=3, completed=3),
            todos=ProgressItem(total=8, completed=4)
        )
        
        service = ProgressService()
        result = service.calculate_overall_progress(progress)
        
        expected = int((5/10*0.4 + 3/5*0.3 + 4/8*0.3) * 100)
        assert result == expected
    
    def test_calculate_progress_with_zero(self):
        """测试零值进度计算"""
        class ProgressItem:
            def __init__(self, total, completed=0, resolved=0):
                self.total = total
                self.completed = completed
                self.resolved = resolved
        
        class ProjectProgress:
            def __init__(self, requirements, bugs, todos):
                self.requirements = requirements
                self.bugs = bugs
                self.todos = todos
        
        class ProgressService:
            def calculate_overall_progress(self, progress):
                req_progress = progress.requirements.completed / progress.requirements.total if progress.requirements.total > 0 else 0
                bug_progress = progress.bugs.resolved / progress.bugs.total if progress.bugs.total > 0 else 0
                todo_progress = progress.todos.completed / progress.todos.total if progress.todos.total > 0 else 0
                
                return int((req_progress * 0.4 + bug_progress * 0.3 + todo_progress * 0.3) * 100)
        
        progress = ProjectProgress(
            requirements=ProgressItem(total=0, completed=0),
            bugs=ProgressItem(total=0, completed=0, resolved=0),
            todos=ProgressItem(total=0, completed=0)
        )
        
        service = ProgressService()
        result = service.calculate_overall_progress(progress)
        assert result == 0


class TestSensitiveContentChecker:
    """F030: 敏感内容检查器测试"""
    
    def test_check_files_empty(self, sensitive_content_checker):
        """测试空项目检查"""
        results = sensitive_content_checker.check_files("/path/to/empty_project")
        assert isinstance(results, list)
    
    def test_contains_sensitive_false(self, sensitive_content_checker, temp_dir):
        """测试不包含敏感词"""
        test_file = f"{temp_dir}/clean_file.txt"
        with open(test_file, "w") as f:
            f.write("这是正常内容")
        
        result = sensitive_content_checker._contains_sensitive(test_file)
        assert result is False
    
    def test_check_files_with_keywords(self, sensitive_content_checker, temp_dir):
        """测试包含敏感词的文件"""
        test_file = f"{temp_dir}/secret_file.txt"
        with open(test_file, "w") as f:
            f.write("这是保密内容")
        
        result = sensitive_content_checker._contains_sensitive(test_file)
        assert result is True
    
    def test_sensitive_keywords_config(self):
        """测试敏感词配置"""
        custom_keywords = ["机密", "内部资料", "secret"]
        
        class CustomChecker:
            def __init__(self, keywords):
                self.keywords = keywords
                
            def _contains_sensitive(self, file_path):
                try:
                    with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                        content = f.read()
                        return any(kw in content for kw in self.keywords)
                except:
                    return False
        
        checker = CustomChecker(custom_keywords)
        assert checker.keywords == custom_keywords


class TestProjectConfidentiality:
    """F027: 项目保密级别测试"""
    
    def test_confidentiality_field(self, test_project_config):
        """测试保密级别字段"""
        projects = test_project_config["projects"]
        confidential_project = [p for p in projects if p["confidentiality"] == "confidential"]
        normal_project = [p for p in projects if p["confidentiality"] == "normal"]
        
        assert len(confidential_project) == 1
        assert len(normal_project) == 1
    
    def test_confidential_project_query(self, test_project_config):
        """测试保密项目查询"""
        projects = test_project_config["projects"]
        confidential_projects = [
            p["name"] for p in projects 
            if p.get("confidentiality") == "confidential" and p.get("enabled", True)
        ]
        
        assert "测试项目B" in confidential_projects


class TestSyncPermissionService:
    """F029: 同步权限服务测试"""
    
    def test_can_auto_sync_with_confidential(self, test_project_config):
        """测试存在保密项目时禁用自动同步"""
        confidential_projects = [
            p for p in test_project_config["projects"]
            if p.get("confidentiality") == "confidential" and p.get("enabled", True)
        ]
        
        can_sync = len(confidential_projects) == 0
        assert can_sync is False
    
    def test_can_auto_sync_without_confidential(self):
        """测试无保密项目时允许自动同步"""
        projects = [
            {"name": "测试项目A", "confidentiality": "normal", "enabled": True}
        ]
        
        confidential_projects = [
            p for p in projects
            if p.get("confidentiality") == "confidential" and p.get("enabled", True)
        ]
        
        can_sync = len(confidential_projects) == 0
        assert can_sync is True


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