"""
ConfManClient 单元测试 - 使用类注入达到80%覆盖
PM-Agent v2.0 - F-031/F-032
"""
import pytest
import sys
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import subprocess

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


class MockConfManProvider:
    """Mock ConfManProvider"""
    def __init__(self):
        pass
    
    def get_data_dir(self):
        return "/test/data"
    
    def get_config_dir(self):
        return "/test/config"
    
    def get_log_dir(self):
        return "/test/logs"
    
    def get_temp_dir(self):
        return "/test/tmp"
    
    def get_todo_db_path(self):
        return "/test/todos.db"
    
    def get_state_file_path(self):
        return "/test/state.yaml"
    
    def get_lock_file_path(self, name):
        return f"/test/lock/{name}"
    
    def get_agent_status_db_path(self):
        return "/test/agent_status.db"
    
    def get_session_dir(self):
        return "/test/sessions"
    
    def get_adhoc_todos_path(self):
        return "/test/adhoc_todos.yaml"
    
    def get_identity_file_path(self):
        return "/test/identity.yaml"
    
    def get_pid_file_path(self):
        return "/test/app.pid"
    
    def get_opencode_db_path(self):
        return "/test/opencode.db"
    
    def get_agents_config_path(self):
        return "/test/agents.yaml"
    
    def get_git_sync_config_path(self):
        return "/test/git_sync.yaml"
    
    def get_notification_config_path(self):
        return "/test/notification.yaml"
    
    def get_skill_index_path(self):
        return "/test/skills_index.yaml"
    
    def get_file_owners_path(self):
        return "/test/file_owners.yaml"
    
    def get_full_path(self, relative_path):
        return Path(f"/test/{relative_path}")
    
    def get_version(self):
        return "1.0.0"


class MockEnvironmentConfig:
    """Mock EnvironmentConfig"""
    def __init__(self):
        pass
    
    def get_environment(self):
        return "test"
    
    def is_test_mode(self):
        return True


@pytest.fixture(autouse=True)
def inject_mock_classes():
    """自动注入mock类"""
    from backend.integrations.conf_man_client import _set_conf_man_classes
    _set_conf_man_classes(MockConfManProvider, MockEnvironmentConfig)
    yield
    _set_conf_man_classes(None, None)


class TestConfManClientUnit:
    """ConfManClient 单元测试"""

    def test_import_client(self):
        from backend.integrations.conf_man_client import ConfManClient
        assert ConfManClient is not None

    def test_init(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client is not None
        assert client._provider is not None
        assert client._env_config is not None

    def test_is_available(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.is_available() == True

    def test_get_data_dir(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_data_dir() == "/test/data"

    def test_get_config_dir(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_config_dir() == "/test/config"

    def test_get_log_dir(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_log_dir() == "/test/logs"

    def test_get_temp_dir(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_temp_dir() == "/test/tmp"

    def test_get_todo_db_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_todo_db_path() == "/test/todos.db"

    def test_get_state_file_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_state_file_path() == "/test/state.yaml"

    def test_get_lock_file_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_lock_file_path("test") == "/test/lock/test"

    def test_get_agent_status_db_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_agent_status_db_path() == "/test/agent_status.db"

    def test_get_session_dir(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_session_dir() == "/test/sessions"

    def test_get_adhoc_todos_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_adhoc_todos_path() == "/test/adhoc_todos.yaml"

    def test_get_identity_file_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_identity_file_path() == "/test/identity.yaml"

    def test_get_pid_file_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_pid_file_path() == "/test/app.pid"

    def test_get_opencode_db_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_opencode_db_path() == "/test/opencode.db"

    def test_get_agents_config_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_agents_config_path() == "/test/agents.yaml"

    def test_get_git_sync_config_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_git_sync_config_path() == "/test/git_sync.yaml"

    def test_get_notification_config_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_notification_config_path() == "/test/notification.yaml"

    def test_get_skill_index_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_skill_index_path() == "/test/skills_index.yaml"

    def test_get_file_owners_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_file_owners_path() == "/test/file_owners.yaml"

    def test_get_full_path(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        result = client.get_full_path("test.yaml")
        assert str(result) == "/test/test.yaml"

    def test_get_version(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_version() == "1.0.0"

    def test_get_environment(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.get_environment() == "test"

    def test_is_test_mode(self):
        from backend.integrations.conf_man_client import ConfManClient
        client = ConfManClient()
        assert client.is_test_mode() == True


class TestConfManClientUnavailable:
    """测试conf-man不可用的情况（需要真实环境）"""
    
    def test_unavailable_placeholder(self):
        """占位测试 - 需要真实conf-man环境才能测试"""
        pass


class TestVersionClientUnit:
    """VersionClient 单元测试"""

    def test_import_client(self):
        from backend.services.version_service import VersionClient
        assert VersionClient is not None

    def test_init(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        assert client.timeout == 30

    def test_init_custom_timeout(self):
        from backend.services.version_service import VersionClient
        client = VersionClient(timeout=60)
        assert client.timeout == 60

    def test_cli_available_true(self):
        from backend.services.version_service import VersionClient
        
        with patch('backend.services.version_service.shutil.which', return_value='/usr/bin/conf-man'):
            client = VersionClient()
            assert client._is_cli_available() == True

    def test_cli_available_false(self):
        from backend.services.version_service import VersionClient
        
        with patch('backend.services.version_service.shutil.which', return_value=None):
            client = VersionClient()
            assert client._is_cli_available() == False

    def test_check_cli_available_raises(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        with patch.object(client, '_is_cli_available', return_value=False):
            with pytest.raises(RuntimeError):
                client._check_cli_available()


class TestVersionClientCommands:
    """VersionClient CLI命令测试"""

    def test_list_versions_empty(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = "[]"
        mock_result.stderr = ""
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.list_versions()
                assert result == []

    def test_list_versions_with_data(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '[{"version": "v1.0.0", "status": "released"}]'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.list_versions()
                assert len(result) == 1

    def test_show_version(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"version": "v1.0.0", "status": "released"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.show_version("v1.0.0")
                assert result["version"] == "v1.0.0"

    def test_register_version(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"status": "success"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.register_version("v1.2.0", "manifest.yaml", "report.yaml")
                assert result["status"] == "success"

    def test_release_version(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"status": "success"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.release_version("v1.0.0")
                assert result["status"] == "success"

    def test_rollback_version(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"status": "success"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.rollback_version("v1.0.0")
                assert result["status"] == "success"

    def test_get_dependencies(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"manifest": {"dependencies": [{"name": "pyyaml"}]}}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.get_dependencies("v1.0.0")
                assert len(result) == 1

    def test_list_projects(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '[{"name": "pm-agent"}]'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.list_projects()
                assert len(result) == 1

    def test_show_project(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"name": "pm-agent", "version": "v1.2.0"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.show_project("pm-agent")
                assert result["name"] == "pm-agent"

    def test_export_manifest(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"status": "success"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.export_manifest("v1.0.0", "output.yaml")
                assert result["status"] == "success"

    def test_import_manifest(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"status": "success"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.import_manifest("input.yaml")
                assert result["status"] == "success"

    def test_get_current_version(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = '{"version": "v1.2.0"}'
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.get_current_version()
                assert result == "v1.2.0"

    def test_get_current_version_exception(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        with patch('backend.services.version_service.subprocess.run', side_effect=Exception("error")):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client.get_current_version()
                assert result is None


class TestVersionClientErrors:
    """VersionClient 错误处理测试"""

    def test_run_command_error(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 1
        mock_result.stderr = "Error message"
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                with pytest.raises(RuntimeError) as exc:
                    client._run_command(['test'])
                assert "Error message" in str(exc.value)

    def test_run_command_timeout(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        with patch('backend.services.version_service.subprocess.run', side_effect=subprocess.TimeoutExpired("cmd", 30)):
            with patch.object(client, '_is_cli_available', return_value=True):
                with pytest.raises(TimeoutError):
                    client._run_command(['test'])

    def test_run_command_json_error(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = "not valid json"
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result):
            with patch.object(client, '_is_cli_available', return_value=True):
                result = client._run_command(['test'])
                assert "message" in result

    def test_list_versions_with_status(self):
        from backend.services.version_service import VersionClient
        client = VersionClient()
        
        mock_result = Mock()
        mock_result.returncode = 0
        mock_result.stdout = "[]"
        
        with patch('backend.services.version_service.subprocess.run', return_value=mock_result) as mock_run:
            with patch.object(client, '_is_cli_available', return_value=True):
                client.list_versions(status="released")
                mock_run.assert_called_with(
                    ["conf-man", "list", "--status", "released"],
                    capture_output=True, text=True, timeout=30
                )
