"""
PM-Agent v1.2.0 80% Coverage Push - Final Sprint

用于将v1.2.0新服务代码覆盖率提升至80%以上
"""
import os
import sys
import pytest
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime

sys.path.insert(0, str(Path(__file__).parent.parent))


class TestSyncPermission80:
    """SyncPermissionService 80%冲刺"""

    def test_can_sync_project_not_exists(self):
        """测试项目不存在"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        result = service.can_sync_project("nonexistent")
        
        assert result.allowed is False

    def test_can_sync_confidential_project(self):
        """测试保密项目"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        result = service.can_sync_project("confidential-project")
        
        assert result is not None

    def test_can_sync_normal_project(self):
        """测试普通项目"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        result = service.can_sync_project("test-project")
        
        assert result is not None

    def test_should_warn_with_project(self):
        """测试带项目检查警告"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        should_warn, reason = service.should_warn_before_manual_sync("test-project")
        
        assert isinstance(should_warn, bool)

    def test_should_warn_without_project(self):
        """测试不带项目检查警告"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        should_warn, reason = service.should_warn_before_manual_sync()
        
        assert isinstance(should_warn, bool)

    def test_generate_recommendations(self):
        """测试生成建议"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        recs = service._generate_recommendations()
        
        assert isinstance(recs, list)


class TestProgress80:
    """ProgressService 80%冲刺"""

    def test_get_progress_history_empty(self):
        """测试空进度历史"""
        from backend.services.progress_service import ProgressService
        
        service = ProgressService()
        
        history = service.get_progress_history("test-project", days=0)
        
        assert isinstance(history, list)

    def test_calculate_progress_weighted_average(self):
        """测试加权平均进度"""
        from backend.services.progress_service import ProgressService, ProjectProgress, RequirementsProgress, BugsProgress, TodosProgress
        
        service = ProgressService()
        
        progress = ProjectProgress(
            project_name="test",
            requirements=RequirementsProgress(total=100, completed=50),
            bugs=BugsProgress(total=100, resolved=50),
            todos=TodosProgress(total=100, completed=50)
        )
        
        result = service.calculate_overall_progress(progress)
        
        assert result >= 0

    def test_calculate_progress_with_valid_data(self):
        """测试有效数据进度"""
        from backend.services.progress_service import ProgressService, ProjectProgress, RequirementsProgress, BugsProgress, TodosProgress
        
        service = ProgressService()
        
        progress = ProjectProgress(
            project_name="test",
            requirements=RequirementsProgress(total=10, completed=5),
            bugs=BugsProgress(total=10, resolved=5),
            todos=TodosProgress(total=10, completed=5)
        )
        
        result = service.calculate_overall_progress(progress)
        
        assert result >= 0


class TestStatusFeedback80:
    """StatusFeedbackService 80%冲刺"""

    def test_start_polling_with_interval(self):
        """测试带间隔启动轮询"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        service = StatusFeedbackService()
        
        service.start_polling(interval=5)
        
        assert service._polling is True
        assert service.polling_interval == 5
        
        service.stop_polling()

    def test_stop_polling_already_stopped(self):
        """测试停止已停止的轮询"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        service = StatusFeedbackService()
        
        service.stop_polling()

    def test_polling_loop(self):
        """测试轮询循环"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        service = StatusFeedbackService()
        
        service._polling = False
        
        service._polling_loop()
        
        assert True

    def test_check_status_changes_no_changes(self):
        """测试无变更检查"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        mock_client = Mock()
        mock_client.get_project_status.return_value = Mock(status="active", progress=50)
        mock_client.get_project_progress.return_value = None
        
        service = StatusFeedbackService(client=mock_client)
        
        result = service.check_status_changes("test-project")
        
        assert 'status' in result or 'error' in result


class TestConfidentialChecker80:
    """ConfidentialChecker 80%冲刺"""

    def test_exclude_patterns(self):
        """测试排除模式"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        assert checker._is_excluded_file("test.lock") is True
        assert checker._is_excluded_file("yarn.lock") is True
        assert checker._is_excluded_file("app.min.js") is True

    def test_exclude_dirs(self):
        """测试排除目录"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        assert checker._is_excluded_dir(".venv") is True
        assert checker._is_excluded_dir("venv") is True
        assert checker._is_excluded_dir("__pycache__") is True

    def test_check_files_with_sensitive(self):
        """测试检查含敏感文件"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        temp = tempfile.mkdtemp()
        try:
            with open(os.path.join(temp, "secret.md"), "w") as f:
                f.write("# Secret\n\npassword: 123456")
            
            checker = SensitiveContentChecker()
            result = checker.check_files(temp)
            
            assert result.has_sensitive is True
        finally:
            shutil.rmtree(temp, ignore_errors=True)

    def test_get_keywords(self):
        """测试获取关键词"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        keywords = checker.get_keywords()
        
        assert isinstance(keywords, list)


class TestDocumentFetcher80:
    """DocumentFetcher 80%冲刺"""

    def test_get_category_backend(self):
        """测试后端分类"""
        from backend.services.document_fetcher import DocumentFetcher
        
        fetcher = DocumentFetcher()
        
        category = fetcher._get_category("backend/main.py")
        
        assert category == "后端"

    def test_get_category_frontend_folder(self):
        """测试前端文件夹分类"""
        from backend.services.document_fetcher import DocumentFetcher
        
        fetcher = DocumentFetcher()
        
        category = fetcher._get_category("frontend/app.js")
        
        assert category == "前端"

    def test_get_category_scripts(self):
        """测试脚本分类"""
        from backend.services.document_fetcher import DocumentFetcher
        
        fetcher = DocumentFetcher()
        
        category = fetcher._get_category("scripts/deploy.sh")
        
        assert category == "脚本"

    def test_list_api_docs_nonexistent(self):
        """测试列出不存在API文档"""
        from backend.services.document_fetcher import DocumentFetcher
        
        fetcher = DocumentFetcher()
        
        docs = fetcher.list_api_docs("/nonexistent")
        
        assert docs == []


class TestGitSync80:
    """GitSyncService 80%冲刺"""

    def test_sync_result_error(self):
        """测试错误同步结果"""
        from backend.services.git_sync_service import SyncResult
        
        result = SyncResult(
            success=False,
            project_name="test",
            started_at=datetime.now(),
            error_message="Test error"
        )
        
        assert result.success is False
        assert result.error_message == "Test error"

    def test_pull_no_tracking_branch(self):
        """测试无追踪分支"""
        from backend.services.git_sync_service import GitSyncService
        
        temp = tempfile.mkdtemp()
        try:
            project_dir = os.path.join(temp, "test")
            os.makedirs(os.path.join(project_dir, ".git"))
            
            with patch('backend.services.git_sync_service.Repo') as mock_repo:
                mock_git = Mock()
                mock_git.head.is_detached = False
                mock_git.remotes.origin.fetch.return_value = []
                
                mock_branch = Mock()
                mock_branch.tracking_branch.return_value = None
                mock_git.active_branch = mock_branch
                
                mock_git.index.diff.return_value = []
                mock_git.untracked_files = []
                
                mock_repo.return_value = mock_git
                
                service = GitSyncService(base_path=temp)
                result = service._pull_changes(project_dir, "test", datetime.now())
                
                assert result is not None
        finally:
            shutil.rmtree(temp, ignore_errors=True)


class TestIssueSync80:
    """IssueSyncService 80%冲刺"""

    def test_sync_all_issues_with_data(self):
        """测试同步所有问题带数据"""
        from backend.services.issue_sync_service import IssueSyncService
        
        mock_client = Mock()
        mock_client.get_project_bugs.return_value = [Mock(id="1")]
        mock_client.get_project_requirements.return_value = [Mock(id="1")]
        
        service = IssueSyncService(client=mock_client)
        
        result = service.sync_all_issues("test-project")
        
        assert result['bugs_synced'] >= 0

    def test_save_bugs_exception(self):
        """测试保存BUG异常"""
        from backend.services.issue_sync_service import IssueSyncService
        
        mock_db = Mock()
        mock_db.commit.side_effect = Exception("DB Error")
        
        service = IssueSyncService()
        
        service.save_bugs_to_db("test", [], mock_db)


class TestOcCollabClient80:
    """OcCollabClient 80%冲刺"""

    @patch('backend.services.oc_collab_client.shutil.which')
    @patch('backend.services.oc_collab_client.subprocess.run')
    def test_get_changes_with_since(self, mock_run, mock_which):
        """测试带时间获取变更"""
        from backend.services.oc_collab_client import OcCollabClient
        
        mock_which.return_value = "/usr/bin/oc-collab"
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"changes": []}'
        mock_result.stderr = ''
        mock_run.return_value = mock_result
        
        client = OcCollabClient()
        
        changes = client.get_changes("test", since="2024-01-01")
        
        assert isinstance(changes, list)


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