"""
PM-Agent v1.2.0 Final Coverage Tests

用于将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, timedelta

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


class TestGitSyncFinalCoverage:
    """GitSyncService 最终覆盖率测试"""

    @pytest.fixture
    def temp_dir(self):
        temp = tempfile.mkdtemp()
        yield temp
        shutil.rmtree(temp, ignore_errors=True)

    @patch('backend.services.git_sync_service.Repo')
    def test_clone_with_gitpython_error(self, mock_repo, temp_dir):
        """测试GitPython错误"""
        from backend.services.git_sync_service import GitSyncService, SyncResult
        from git import GitCommandError
        
        mock_repo.clone_from.side_effect = GitCommandError("cmd", "error")
        
        service = GitSyncService(base_path=temp_dir)
        
        result = service._clone_repo(
            "https://github.com/test/repo.git",
            os.path.join(temp_dir, "test"),
            "test",
            datetime.now()
        )
        
        assert result.success is False
        assert result.error_message is not None

    @patch('backend.services.git_sync_service.Repo')
    def test_pull_without_git_dir(self, mock_repo, temp_dir):
        """测试拉取非Git仓库"""
        from backend.services.git_sync_service import GitSyncService
        
        project_dir = os.path.join(temp_dir, "test")
        os.makedirs(project_dir)
        
        service = GitSyncService(base_path=temp_dir)
        
        result = service._pull_changes(project_dir, "test", datetime.now())
        
        assert result.success is False
        assert "不是有效的Git仓库" in result.error_message

    @patch('backend.services.git_sync_service.Repo')
    def test_pull_detached_head(self, mock_repo, temp_dir):
        """测试detached HEAD状态"""
        from backend.services.git_sync_service import GitSyncService
        
        project_dir = os.path.join(temp_dir, "test")
        os.makedirs(os.path.join(project_dir, ".git"))
        
        mock_git_repo = Mock()
        mock_git_repo.head.is_detached = True
        mock_git_repo.remotes.origin.fetch.return_value = []
        mock_git_repo.index.diff.return_value = []
        mock_git_repo.untracked_files = []
        
        mock_active_branch = Mock()
        mock_active_branch.tracking_branch.return_value = None
        mock_git_repo.active_branch = mock_active_branch
        
        mock_repo.return_value = mock_git_repo
        
        service = GitSyncService(base_path=temp_dir)
        
        result = service._pull_changes(project_dir, "test", datetime.now())
        
        assert result is not None

    @patch('backend.services.git_sync_service.Repo')
    def test_pull_tracking_branch(self, mock_repo, temp_dir):
        """测试追踪分支"""
        from backend.services.git_sync_service import GitSyncService
        
        project_dir = os.path.join(temp_dir, "test")
        os.makedirs(os.path.join(project_dir, ".git"))
        
        mock_git_repo = Mock()
        mock_git_repo.head.is_detached = False
        
        mock_tracking = Mock()
        mock_tracking.commit.hexsha = "abc123"
        mock_tracking.commit.return_value.hexsha = "abc123"
        
        mock_active_branch = Mock()
        mock_active_branch.tracking_branch.return_value = mock_tracking
        mock_git_repo.active_branch = mock_active_branch
        
        mock_git_repo.remotes.origin.fetch.return_value = []
        mock_git_repo.index.diff.return_value = []
        mock_git_repo.untracked_files = []
        
        mock_repo.return_value = mock_git_repo
        
        service = GitSyncService(base_path=temp_dir)
        
        result = service._pull_changes(project_dir, "test", datetime.now())
        
        assert result is not None

    @patch('backend.services.git_sync_service.Repo')
    def test_pull_git_command_error(self, mock_repo, temp_dir):
        """测试Git命令错误"""
        from backend.services.git_sync_service import GitSyncService
        from git import GitCommandError
        
        project_dir = os.path.join(temp_dir, "test")
        os.makedirs(os.path.join(project_dir, ".git"))
        
        mock_repo.side_effect = GitCommandError("cmd", "error")
        
        service = GitSyncService(base_path=temp_dir)
        
        result = service._pull_changes(project_dir, "test", datetime.now())
        
        assert result.success is False

    @patch('backend.services.git_sync_service.Repo')
    def test_sync_project_with_gitpython_not_installed(self, mock_repo, temp_dir):
        """测试GitPython未安装"""
        from backend.services.git_sync_service import GitSyncService
        
        service = GitSyncService(base_path=temp_dir)
        
        with patch('backend.services.git_sync_service.Repo', None):
            result = service.sync_project("test", "https://github.com/test/repo.git")
        
        assert result.error_message == "GitPython未安装"

    def test_count_files_actual(self, temp_dir):
        """测试实际文件计数"""
        from backend.services.git_sync_service import GitSyncService
        
        for i in range(5):
            with open(os.path.join(temp_dir, f"file{i}.txt"), "w") as f:
                f.write("test")
        
        subdir = os.path.join(temp_dir, "subdir")
        os.makedirs(subdir)
        with open(os.path.join(subdir, "nested.txt"), "w") as f:
            f.write("nested")
        
        service = GitSyncService()
        count = service._count_files(temp_dir)
        
        assert count >= 6

    @patch('backend.services.git_sync_service.Repo')
    def test_get_repo_info_success(self, mock_repo, temp_dir):
        """测试获取仓库信息成功"""
        from backend.services.git_sync_service import GitSyncService
        
        project_dir = os.path.join(temp_dir, "test")
        os.makedirs(os.path.join(project_dir, ".git"))
        
        mock_git_repo = Mock()
        mock_git_repo.active_branch.name = "main"
        mock_git_repo.remotes = []
        
        mock_repo.return_value = mock_git_repo
        
        service = GitSyncService(base_path=temp_dir)
        
        info = service.get_repo_info(project_dir)
        
        assert info is not None

    def test_sync_result_with_times(self):
        """测试同步结果包含时间"""
        from backend.services.git_sync_service import SyncResult
        
        result = SyncResult(
            success=True,
            project_name="test",
            started_at=datetime.now()
        )
        
        assert result.success is True
        
        result.completed_at = datetime.now()
        assert result.completed_at is not None


class TestProgressFinalCoverage:
    """ProgressService 最终覆盖率测试"""

    def test_get_project_progress_with_client(self):
        """测试有客户端获取进度"""
        from backend.services.progress_service import ProgressService
        from backend.services.oc_collab_client import OcCollabClient
        
        mock_client = Mock(spec=OcCollabClient)
        mock_client.get_project_progress.return_value = Mock(
            requirements_total=10,
            requirements_completed=5,
            requirements_in_progress=3,
            bugs_total=5,
            bugs_resolved=3,
            todos_total=20,
            todos_completed=10
        )
        
        service = ProgressService(client=mock_client)
        
        progress = service.get_project_progress("test-project")
        
        assert progress is not None

    def test_calculate_progress_with_zero_totals(self):
        """测试零总量进度计算"""
        from backend.services.progress_service import ProgressService, ProjectProgress, RequirementsProgress, BugsProgress, TodosProgress
        
        service = ProgressService()
        
        progress = ProjectProgress(
            project_name="test",
            requirements=RequirementsProgress(total=0, completed=0),
            bugs=BugsProgress(total=0, resolved=0),
            todos=TodosProgress(total=0, completed=0)
        )
        
        result = service.calculate_overall_progress(progress)
        
        assert result == 0

    def test_calculate_progress_partial(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 == 50

    def test_get_all_projects_summary_empty(self):
        """测试空项目汇总"""
        from backend.services.progress_service import ProgressService
        
        service = ProgressService()
        
        summary = service.get_all_projects_summary()
        
        assert summary.total_projects == 0

    @patch('backend.services.progress_service.ProgressService.get_project_progress')
    def test_get_all_projects_summary_with_data(self, mock_progress):
        """测试有数据的项目汇总"""
        from backend.services.progress_service import ProgressService, ProjectsSummary, ProjectProgress
        
        mock_progress.return_value = ProjectProgress(project_name="test")
        
        service = ProgressService()
        
        summary = service.get_all_projects_summary(project_names=["test"])
        
        assert summary.total_projects >= 0

    def test_set_weights_sum_not_one(self):
        """测试权重和不为1"""
        from backend.services.progress_service import ProgressService
        
        service = ProgressService()
        
        original = service._requirements_weight
        service.set_weights(requirements=0.6, bugs=0.3, todos=0.3)
        
        assert service._requirements_weight > 0

    def test_progress_data_classes(self):
        """测试进度数据类"""
        from backend.services.progress_service import RequirementsProgress, BugsProgress, TodosProgress
        
        req = RequirementsProgress(total=10, completed=5, in_progress=3, pending=2)
        assert req.total == 10
        
        bugs = BugsProgress(total=5, resolved=3, open=2)
        assert bugs.total == 5
        
        todos = TodosProgress(total=20, completed=10, pending=10)
        assert todos.total == 20


class TestIssueSyncFinalCoverage:
    """IssueSyncService 最终覆盖率测试"""

    def test_sync_bugs_with_data(self):
        """测试同步BUG数据"""
        from backend.services.issue_sync_service import IssueSyncService, SyncBug
        from unittest.mock import MagicMock
        
        mock_client = Mock()
        mock_client.get_project_bugs.return_value = [
            Mock(id="BUG-001", title="Test bug", status="open", severity="high")
        ]
        
        service = IssueSyncService(client=mock_client)
        
        bugs = service.sync_bugs("test-project")
        
        assert len(bugs) > 0

    def test_sync_requirements_with_data(self):
        """测试同步需求数据"""
        from backend.services.issue_sync_service import IssueSyncService
        from unittest.mock import Mock
        
        mock_client = Mock()
        mock_client.get_project_requirements.return_value = [
            Mock(id="REQ-001", title="Test req", status="draft", priority="high")
        ]
        
        service = IssueSyncService(client=mock_client)
        
        requirements = service.sync_requirements("test-project")
        
        assert len(requirements) > 0

    def test_update_bug_status_no_client(self):
        """测试无客户端更新BUG状态"""
        from backend.services.issue_sync_service import IssueSyncService
        
        service = IssueSyncService()
        
        result = service.update_bug_status("test-project", "BUG-001", "closed")
        
        assert result is False

    def test_update_requirement_status_no_client(self):
        """测试无客户端更新需求状态"""
        from backend.services.issue_sync_service import IssueSyncService
        
        service = IssueSyncService()
        
        result = service.update_requirement_status("test-project", "REQ-001", "implemented")
        
        assert result is False

    def test_save_bugs_to_db_with_session(self):
        """测试保存BUG到数据库"""
        from backend.services.issue_sync_service import IssueSyncService, SyncBug
        
        mock_db = MagicMock()
        
        service = IssueSyncService()
        
        bugs = [SyncBug(id="BUG-001", title="Test", status="open", severity="high")]
        
        service.save_bugs_to_db("test-project", bugs, mock_db)
        
        mock_db.commit.assert_called()

    def test_save_requirements_to_db(self):
        """测试保存需求到数据库"""
        from backend.services.issue_sync_service import IssueSyncService, SyncRequirement
        
        mock_db = MagicMock()
        
        service = IssueSyncService()
        
        requirements = [SyncRequirement(id="REQ-001", title="Test", status="draft", priority="high")]
        
        service.save_requirements_to_db("test-project", requirements, mock_db)
        
        mock_db.commit.assert_called()


class TestStatusFeedbackFinalCoverage:
    """StatusFeedbackService 最终覆盖率测试"""

    def test_parse_change_with_obj(self):
        """测试解析对象变更"""
        from backend.services.status_feedback_service import StatusFeedbackService, ChangeType
        
        service = StatusFeedbackService()
        
        mock_change = Mock()
        mock_change.id = "1"
        mock_change.title = "Test"
        mock_change.change_type = "bug"
        mock_change.old_status = "open"
        mock_change.new_status = "closed"
        mock_change.old_value = None
        mock_change.new_value = None
        mock_change.description = None
        mock_change.changed_at = None
        
        event = service._parse_change("test-project", mock_change)
        
        assert event.change_type == ChangeType.BUG

    def test_parse_change_dict(self):
        """测试解析字典变更"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        service = StatusFeedbackService()
        
        change = {
            'id': '1',
            'title': 'Test',
            'change_type': 'requirement',
            'old_status': 'draft',
            'new_status': 'proposed'
        }
        
        event = service._parse_change("test-project", change)
        
        assert event.change_id == '1'

    def test_parse_change_invalid_type(self):
        """测试解析无效类型"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        service = StatusFeedbackService()
        
        change = {'id': '1', 'title': 'Test', 'change_type': 'invalid_type'}
        
        event = service._parse_change("test-project", change)
        
        assert event.change_type.value == 'progress'

    def test_poll_changes_with_error(self):
        """测试轮询错误"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        mock_client = Mock()
        mock_client.get_changes.side_effect = Exception("Error")
        
        service = StatusFeedbackService(client=mock_client)
        
        changes = service.poll_changes("test-project")
        
        assert changes == []

    def test_start_polling(self):
        """测试启动轮询"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        service = StatusFeedbackService()
        
        service.start_polling(interval=1)
        
        assert service._polling is True
        
        service.stop_polling()

    def test_process_change_with_storage(self):
        """测试处理变更有存储"""
        from backend.services.status_feedback_service import StatusFeedbackService, ChangeEvent, ChangeType
        
        mock_storage = Mock()
        
        service = StatusFeedbackService(storage=mock_storage)
        
        event = ChangeEvent(
            project_name="test",
            change_type=ChangeType.BUG,
            change_id="BUG-001",
            title="Test",
            old_status="open",
            new_status="closed"
        )
        
        result = service.process_change(event)
        
        assert result is True

    def test_get_change_history_with_storage(self):
        """测试获取历史有存储"""
        from backend.services.status_feedback_service import StatusFeedbackService, ChangeEvent, ChangeType
        
        mock_storage = Mock()
        mock_storage.get_change_history.return_value = []
        
        service = StatusFeedbackService(storage=mock_storage)
        
        history = service.get_change_history("test-project", limit=50)
        
        assert history == []

    def test_check_status_changes_with_client(self):
        """测试检查状态有客户端"""
        from backend.services.status_feedback_service import StatusFeedbackService
        
        mock_client = Mock()
        mock_client.get_project_status.return_value = Mock(status="active", progress=75)
        mock_client.get_project_progress.return_value = Mock(
            requirements_total=10,
            requirements_completed=5,
            requirements_in_progress=3,
            bugs_total=5,
            bugs_resolved=3,
            todos_total=20,
            todos_completed=10
        )
        
        service = StatusFeedbackService(client=mock_client)
        
        result = service.check_status_changes("test-project")
        
        assert 'status' in result


class TestSyncPermissionFinalCoverage:
    """SyncPermissionService 最终覆盖率测试"""

    def test_check_sync_safety_with_checks(self):
        """测试安全检查"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        result = service.check_sync_safety("test-project")
        
        assert 'is_safe' in result

    def test_get_sync_recommendation_no_projects(self):
        """测试无项目建议"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        result = service.get_sync_recommendation()
        
        assert 'auto_sync_allowed' in result

    def test_sync_permission_service_init(self):
        """测试服务初始化"""
        from backend.services.sync_permission_service import SyncPermissionService
        
        service = SyncPermissionService()
        
        assert service is not None


class TestDocumentFetcherFinalCoverage:
    """DocumentFetcher 最终覆盖率测试"""

    @pytest.fixture
    def temp_project(self):
        temp = tempfile.mkdtemp()
        yield temp
        shutil.rmtree(temp, ignore_errors=True)

    def test_create_document_large_file(self, temp_project):
        """测试创建大文件文档"""
        from backend.services.document_fetcher import DocumentFetcher
        
        large_file = os.path.join(temp_project, "large.bin")
        with open(large_file, "wb") as f:
            f.write(b"x" * (2 * 1024 * 1024))
        
        fetcher = DocumentFetcher()
        
        doc = fetcher._create_document(large_file, temp_project)
        
        assert doc.content is None

    def test_get_category_with_docs_subdir(self, temp_project):
        """测试docs子目录分类"""
        from backend.services.document_fetcher import DocumentFetcher
        
        fetcher = DocumentFetcher()
        
        category = fetcher._get_category("docs/api/v1.md")
        
        assert category in ["文档", "api"]

    def test_search_content_match(self, temp_project):
        """测试搜索内容匹配"""
        from backend.services.document_fetcher import DocumentFetcher
        
        with open(os.path.join(temp_project, "test.txt"), "w") as f:
            f.write("This is a test file")
        
        fetcher = DocumentFetcher(base_path=temp_project)
        
        results = fetcher.search("test", project_name=os.path.basename(temp_project))
        
        assert isinstance(results, list)


class TestConfidentialCheckerFinalCoverage:
    """ConfidentialChecker 最终覆盖率测试"""

    def test_find_keywords_multiple(self):
        """测试查找多个关键词"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        keywords = checker._find_keywords("password and secret key")
        
        assert len(keywords) >= 2

    def test_find_line_numbers(self):
        """测试查找行号"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        temp = tempfile.mkdtemp()
        try:
            file_path = os.path.join(temp, "test.txt")
            with open(file_path, "w") as f:
                f.write("line1\npassword\nline3\nsecret\n")
            
            lines = checker._find_line_numbers(file_path, "password\nsecret\n", ["password", "secret"])
            
            assert len(lines) >= 2
        finally:
            shutil.rmtree(temp, ignore_errors=True)

    def test_get_suggestion(self):
        """测试获取建议"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        suggestion = checker._get_suggestion(["password", "secret"])
        
        assert suggestion is not None

    def test_is_excluded_dir(self):
        """测试排除目录"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        assert checker._is_excluded_dir(".git") is True
        assert checker._is_excluded_dir("src") is False

    def test_is_excluded_file(self):
        """测试排除文件"""
        from backend.services.confidential_checker import SensitiveContentChecker
        
        checker = SensitiveContentChecker()
        
        assert checker._is_excluded_file("package-lock.json") is True
        assert checker._is_excluded_file("test.py") is False


class TestOcCollabClientFinalCoverage:
    """OcCollabClient 最终覆盖率测试"""

    @patch('backend.services.oc_collab_client.shutil.which')
    @patch('backend.services.oc_collab_client.subprocess.run')
    def test_get_project_status_parsed(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 = '{"status": "active", "progress": 50}'
        mock_result.stderr = ''
        mock_run.return_value = mock_result
        
        client = OcCollabClient()
        
        status = client.get_project_status("test-project")
        
        assert status.status == "active"

    @patch('backend.services.oc_collab_client.shutil.which')
    @patch('backend.services.oc_collab_client.subprocess.run')
    def test_get_project_todos_with_status(self, mock_run, mock_which):
        """测试带状态获取TODO"""
        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 = '{"todos": [{"id": "1", "status": "completed"}]}'
        mock_result.stderr = ''
        mock_run.return_value = mock_result
        
        client = OcCollabClient()
        
        todos = client.get_project_todos("test-project", status="completed")
        
        assert isinstance(todos, list)

    @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-project", since="2024-01-01")
        
        assert isinstance(changes, list)

    @patch('backend.services.oc_collab_client.shutil.which')
    @patch('backend.services.oc_collab_client.subprocess.run')
    def test_error_handling(self, mock_run, mock_which):
        """测试错误处理"""
        from backend.services.oc_collab_client import OcCollabClient, OcCollabError
        
        mock_which.return_value = "/usr/bin/oc-collab"
        
        mock_result = Mock()
        mock_result.returncode = 1
        mock_result.stdout = ''
        mock_result.stderr = 'Error occurred'
        mock_run.return_value = mock_result
        
        client = OcCollabClient()
        
        with pytest.raises(OcCollabError):
            client._call_cli(['project', 'test'])


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