"""
Tests for Issue #59 - Issue Management Plugin Manager

This module contains tests for the plugin manager that handles
backend discovery, loading, and configuration for the unified
issue management CLI.
"""

import pytest
from unittest.mock import Mock, patch
from pathlib import Path
from typing import Dict, Any

# Import the classes we'll implement
# Note: These imports will fail initially (RED phase)
from markitect.issues.manager import IssuePluginManager
from markitect.issues.base import IssueBackend
from markitect.issues.plugins.gitea import GiteaPlugin
from markitect.issues.plugins.local import LocalPlugin
from markitect.issues.exceptions import PluginNotFoundError, ConfigurationError


class TestIssuePluginManager:
    """Test suite for the issue plugin manager."""

    def test_manager_initialization_with_default_config(self):
        """Test plugin manager initializes with default configuration."""
        manager = IssuePluginManager()

        assert manager is not None
        assert hasattr(manager, 'config')
        assert hasattr(manager, 'plugins')

    def test_manager_initialization_with_custom_config_path(self):
        """Test plugin manager accepts custom config path."""
        config_path = "/custom/path/config.yml"

        with patch.object(IssuePluginManager, '_load_config') as mock_load:
            mock_load.return_value = {'default_backend': 'gitea'}
            manager = IssuePluginManager(config_path)

            mock_load.assert_called_once_with(config_path)

    def test_plugin_discovery_finds_available_backends(self):
        """Test plugin discovery locates all available backend plugins."""
        manager = IssuePluginManager()

        # Should discover at least gitea and local plugins
        assert 'gitea' in manager.plugins
        assert 'local' in manager.plugins
        assert len(manager.plugins) >= 2

    def test_get_default_backend_when_none_specified(self):
        """Test getting backend instance uses default from config."""
        with patch.object(IssuePluginManager, '_load_config') as mock_load:
            mock_load.return_value = {
                'default_backend': 'gitea',
                'backends': {'gitea': {'url': 'http://test.com'}}
            }

            manager = IssuePluginManager()
            backend = manager.get_backend()

            assert isinstance(backend, IssueBackend)

    def test_get_specific_backend_override(self):
        """Test getting specific backend overrides default config."""
        with patch.object(IssuePluginManager, '_load_config') as mock_load:
            mock_load.return_value = {
                'default_backend': 'gitea',
                'backends': {
                    'gitea': {'url': 'http://test.com'},
                    'local': {'directory': '.issues'}
                }
            }

            manager = IssuePluginManager()
            backend = manager.get_backend('local')

            assert isinstance(backend, IssueBackend)

    def test_get_unknown_backend_raises_error(self):
        """Test requesting unknown backend raises appropriate error."""
        manager = IssuePluginManager()

        with pytest.raises(PluginNotFoundError):
            manager.get_backend('nonexistent')

    def test_config_loading_with_missing_file(self):
        """Test configuration loading handles missing config file gracefully."""
        manager = IssuePluginManager()

        # Should have default configuration
        assert manager.config is not None
        assert 'default_backend' in manager.config

    def test_config_loading_with_invalid_yaml(self):
        """Test configuration loading handles invalid YAML gracefully."""
        with patch('builtins.open', side_effect=Exception("Invalid YAML")):
            manager = IssuePluginManager()

            # Should fall back to default configuration
            assert manager.config is not None


class TestPluginInterface:
    """Test suite for the abstract plugin interface."""

    def test_abstract_backend_cannot_be_instantiated(self):
        """Test abstract IssueBackend cannot be instantiated directly."""
        with pytest.raises(TypeError):
            IssueBackend()

    def test_plugin_must_implement_all_abstract_methods(self):
        """Test concrete plugins must implement all abstract methods."""

        class IncompletePlugin(IssueBackend):
            def list_issues(self, state=None):
                return []
            # Missing other required methods

        with pytest.raises(TypeError):
            IncompletePlugin()

    def test_complete_plugin_implementation_works(self):
        """Test properly implemented plugin can be instantiated."""

        class CompletePlugin(IssueBackend):
            def list_issues(self, state=None):
                return []

            def get_issue(self, issue_id):
                return Mock()

            def create_issue(self, title, body, **kwargs):
                return Mock()

            def add_comment(self, issue_id, comment):
                return {}

            def close_issue(self, issue_id):
                return Mock()

            def update_issue(self, issue_id, **kwargs):
                return Mock()

        # Should not raise any errors
        plugin = CompletePlugin({})
        assert isinstance(plugin, IssueBackend)


class TestPluginConfiguration:
    """Test suite for plugin configuration management."""

    def test_backend_receives_configuration_on_initialization(self):
        """Test backend plugins receive their configuration during init."""
        config = {
            'default_backend': 'gitea',
            'backends': {
                'gitea': {'url': 'http://test.com', 'repo': 'test/repo'}
            }
        }

        with patch.object(IssuePluginManager, '_load_config', return_value=config):
            with patch.object(IssuePluginManager, '_discover_plugins') as mock_discover:
                # Mock the plugin class to verify config is passed
                mock_plugin_class = Mock()
                mock_discover.return_value = {'gitea': mock_plugin_class}

                manager = IssuePluginManager()
                manager.get_backend('gitea')

                # Verify plugin was initialized with backend config
                mock_plugin_class.assert_called_once_with({'url': 'http://test.com', 'repo': 'test/repo'})

    def test_missing_backend_config_uses_empty_dict(self):
        """Test backend initialization with missing config uses empty dict."""
        config = {
            'default_backend': 'local',
            'backends': {}  # No local backend config
        }

        with patch.object(IssuePluginManager, '_load_config', return_value=config):
            with patch.object(IssuePluginManager, '_discover_plugins') as mock_discover:
                mock_plugin_class = Mock()
                mock_discover.return_value = {'local': mock_plugin_class}

                manager = IssuePluginManager()
                manager.get_backend('local')

                # Should initialize with empty config
                mock_plugin_class.assert_called_once_with({})

    def test_config_validation_rejects_invalid_backend_names(self):
        """Test configuration validation rejects invalid backend names."""
        config = {
            'default_backend': 'invalid-backend-name',
            'backends': {}
        }

        with patch.object(IssuePluginManager, '_load_config', return_value=config):
            manager = IssuePluginManager()

            with pytest.raises(PluginNotFoundError):
                manager.get_backend()


class TestErrorHandling:
    """Test suite for error handling scenarios."""

    def test_plugin_loading_failure_provides_helpful_error(self):
        """Test plugin loading failures provide helpful error messages."""
        manager = IssuePluginManager()

        with pytest.raises(PluginNotFoundError) as exc_info:
            manager.get_backend('nonexistent')

        assert 'nonexistent' in str(exc_info.value)
        assert 'backend' in str(exc_info.value).lower()

    def test_configuration_error_for_malformed_config(self):
        """Test configuration errors for malformed configuration."""
        # This will be implemented when we add config validation
        pass

    def test_graceful_degradation_on_plugin_import_failure(self):
        """Test system handles plugin import failures gracefully."""
        # Mock import failure for one plugin
        with patch('importlib.import_module', side_effect=ImportError("Mock import failure")):
            manager = IssuePluginManager()

            # Should still work with available plugins
            assert manager.plugins is not None