"""
Tests for Issue #59 - CLI Interface

This module contains tests for the unified CLI interface that provides
consistent commands for issue management across different backends.
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
from click.testing import CliRunner
from typing import List

# Import CLI commands we'll implement
# Note: These imports will fail initially (RED phase)
from markitect.cli import cli
from markitect.issues.commands import issues_group
from markitect.issues.manager import IssuePluginManager
from domain.issues.models import Issue


class TestIssuesCLIGroup:
    """Test suite for the main issues CLI group."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    def test_issues_group_exists_in_main_cli(self):
        """Test that issues group is properly registered in main CLI."""
        result = self.runner.invoke(cli, ['--help'])

        assert result.exit_code == 0
        assert 'issues' in result.output

    def test_issues_group_shows_help(self):
        """Test that issues group displays help information."""
        result = self.runner.invoke(cli, ['issues', '--help'])

        assert result.exit_code == 0
        assert 'Issue management' in result.output
        assert 'list' in result.output
        assert 'show' in result.output
        assert 'create' in result.output

    def test_issues_group_description(self):
        """Test that issues group has appropriate description."""
        result = self.runner.invoke(cli, ['issues', '--help'])

        assert 'multiple backend support' in result.output.lower()


class TestIssuesListCommand:
    """Test suite for the issues list command."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    def test_list_all_issues_default(self):
        """Test listing all issues with default parameters."""
        with patch('markitect.issues.commands.IssuePluginManager') as mock_manager_class:
            # Mock the plugin manager and backend
            mock_manager = Mock()
            mock_backend = Mock()

            # Create more realistic mock issues with proper attributes
            from datetime import datetime
            mock_datetime = Mock()
            mock_datetime.strftime.return_value = "2023-01-01"

            mock_issue1 = Mock(spec=Issue)
            mock_issue1.number = 1
            mock_issue1.title = "Test Issue 1"
            mock_issue1.state = "open"
            mock_issue1.labels = []
            mock_issue1.body = "Test body 1"
            mock_issue1.created_at = mock_datetime
            mock_issue1.updated_at = mock_datetime

            mock_issue2 = Mock(spec=Issue)
            mock_issue2.number = 2
            mock_issue2.title = "Test Issue 2"
            mock_issue2.state = "closed"
            mock_issue2.labels = []
            mock_issue2.body = "Test body 2"
            mock_issue2.created_at = mock_datetime
            mock_issue2.updated_at = mock_datetime

            mock_issues = [mock_issue1, mock_issue2]

            mock_manager_class.return_value = mock_manager
            mock_manager.get_backend.return_value = mock_backend
            mock_backend.list_issues.return_value = mock_issues

            result = self.runner.invoke(cli, ['issues', 'list'])

            assert result.exit_code == 0
            mock_manager.get_backend.assert_called_once_with(None)
            mock_backend.list_issues.assert_called_once_with(state='all')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_list_open_issues_only(self, mock_manager_class):
        """Test listing only open issues."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.list_issues.return_value = []

        result = self.runner.invoke(cli, ['issues', 'list', '--state', 'open'])

        assert result.exit_code == 0
        mock_backend.list_issues.assert_called_once_with(state='open')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_list_closed_issues_only(self, mock_manager_class):
        """Test listing only closed issues."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.list_issues.return_value = []

        result = self.runner.invoke(cli, ['issues', 'list', '--state', 'closed'])

        assert result.exit_code == 0
        mock_backend.list_issues.assert_called_once_with(state='closed')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_list_with_backend_override(self, mock_manager_class):
        """Test listing issues with backend override."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.list_issues.return_value = []

        result = self.runner.invoke(cli, ['issues', 'list', '--backend', 'local'])

        assert result.exit_code == 0
        mock_manager.get_backend.assert_called_once_with('local')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_list_displays_issues_in_table_format(self, mock_manager_class):
        """Test that list command displays issues in readable table format."""
        mock_manager = Mock()
        mock_backend = Mock()
        from datetime import datetime
        mock_datetime = Mock()
        mock_datetime.strftime.return_value = "2023-01-01"

        mock_issue = Mock()
        mock_issue.number = 59
        mock_issue.title = "Test Issue"
        mock_issue.state = "open"
        mock_issue.labels = []
        mock_issue.body = "Test issue body"
        mock_issue.created_at = mock_datetime
        mock_issue.updated_at = mock_datetime

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.list_issues.return_value = [mock_issue]

        result = self.runner.invoke(cli, ['issues', 'list'])

        assert result.exit_code == 0
        assert '59' in result.output
        assert 'Test Issue' in result.output


class TestIssuesShowCommand:
    """Test suite for the issues show command."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_show_specific_issue(self, mock_manager_class):
        """Test showing a specific issue by ID."""
        mock_manager = Mock()
        mock_backend = Mock()
        from datetime import datetime
        mock_datetime = Mock()
        mock_datetime.strftime.return_value = "2023-01-01 00:00"

        mock_issue = Mock()
        mock_issue.number = 59
        mock_issue.title = "Test Issue"
        mock_issue._body = "Test issue body"
        mock_issue.state = Mock()
        mock_issue.state.value = "open"
        mock_issue.created_at = mock_datetime
        mock_issue.updated_at = mock_datetime
        mock_issue.labels = []
        mock_issue.assignee = None
        mock_issue.milestone = None
        mock_issue.state_label = "OPEN"
        mock_issue.priority_label = "Normal"
        mock_issue.type_labels = []
        mock_issue.other_labels = []
        mock_issue.html_url = ""

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.get_issue.return_value = mock_issue

        result = self.runner.invoke(cli, ['issues', 'show', '59'])

        assert result.exit_code == 0
        mock_backend.get_issue.assert_called_once_with('59')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_show_displays_issue_details(self, mock_manager_class):
        """Test that show command displays comprehensive issue details."""
        mock_manager = Mock()
        mock_backend = Mock()
        from datetime import datetime
        mock_datetime = Mock()
        mock_datetime.strftime.return_value = "2023-01-01 00:00"

        mock_issue = Mock()
        mock_issue.number = 59
        mock_issue.title = "Test Issue"
        mock_issue._body = "Detailed issue description"
        mock_issue.state = Mock()
        mock_issue.state.value = "open"
        mock_issue.created_at = mock_datetime
        mock_issue.updated_at = mock_datetime
        mock_issue.labels = []
        mock_issue.assignee = None
        mock_issue.milestone = None
        mock_issue.state_label = "OPEN"
        mock_issue.priority_label = "Normal"
        mock_issue.type_labels = []
        mock_issue.other_labels = []
        mock_issue.html_url = ""
        mock_issue.kanban_column = "To Do"

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.get_issue.return_value = mock_issue

        result = self.runner.invoke(cli, ['issues', 'show', '59'])

        assert result.exit_code == 0
        assert 'Test Issue' in result.output
        assert 'Detailed issue description' in result.output

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_show_with_backend_override(self, mock_manager_class):
        """Test showing issue with specific backend override."""
        mock_manager = Mock()
        mock_backend = Mock()

        from datetime import datetime
        mock_datetime = Mock()
        mock_datetime.strftime.return_value = "2023-01-01 00:00"

        mock_issue = Mock()
        mock_issue.number = 59
        mock_issue.title = "Test Issue"
        mock_issue._body = "Test issue body"
        mock_issue.state = Mock()
        mock_issue.state.value = "open"
        mock_issue.created_at = mock_datetime
        mock_issue.updated_at = mock_datetime
        mock_issue.labels = []
        mock_issue.assignee = None
        mock_issue.milestone = None
        mock_issue.state_label = "OPEN"
        mock_issue.priority_label = "Normal"
        mock_issue.type_labels = []
        mock_issue.other_labels = []
        mock_issue.html_url = ""
        mock_issue.kanban_column = "To Do"

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.get_issue.return_value = mock_issue

        result = self.runner.invoke(cli, ['issues', 'show', '59', '--backend', 'gitea'])

        assert result.exit_code == 0
        mock_manager.get_backend.assert_called_once_with('gitea')


class TestIssuesCreateCommand:
    """Test suite for the issues create command."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_create_issue_with_title_and_body(self, mock_manager_class):
        """Test creating an issue with title and body."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_created_issue = Mock()
        mock_created_issue.number = 60
        mock_created_issue.title = "New Issue"

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.create_issue.return_value = mock_created_issue

        result = self.runner.invoke(cli, ['issues', 'create', 'New Issue', 'Issue body content'])

        assert result.exit_code == 0
        mock_backend.create_issue.assert_called_once_with('New Issue', 'Issue body content')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_create_displays_success_message(self, mock_manager_class):
        """Test that create command displays success message with issue number."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_created_issue = Mock()
        mock_created_issue.number = 60

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.create_issue.return_value = mock_created_issue

        result = self.runner.invoke(cli, ['issues', 'create', 'Test', 'Body'])

        assert result.exit_code == 0
        assert '60' in result.output
        assert 'created' in result.output.lower()


class TestIssuesCommentCommand:
    """Test suite for the issues comment command."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_add_comment_to_issue(self, mock_manager_class):
        """Test adding a comment to an existing issue."""
        mock_manager = Mock()
        mock_backend = Mock()

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.add_comment.return_value = {}

        result = self.runner.invoke(cli, ['issues', 'comment', '59', 'This is a comment'])

        assert result.exit_code == 0
        mock_backend.add_comment.assert_called_once_with('59', 'This is a comment')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_comment_displays_success_message(self, mock_manager_class):
        """Test that comment command displays success message."""
        mock_manager = Mock()
        mock_backend = Mock()

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.add_comment.return_value = {}

        result = self.runner.invoke(cli, ['issues', 'comment', '59', 'Test comment'])

        assert result.exit_code == 0
        assert 'comment added' in result.output.lower()


class TestIssuesCloseCommand:
    """Test suite for the issues close command."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_close_issue(self, mock_manager_class):
        """Test closing an issue."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_closed_issue = Mock()
        mock_closed_issue.state = "closed"

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.close_issue.return_value = mock_closed_issue

        result = self.runner.invoke(cli, ['issues', 'close', '59'])

        assert result.exit_code == 0
        mock_backend.close_issue.assert_called_once_with('59')

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_close_displays_success_message(self, mock_manager_class):
        """Test that close command displays success message."""
        mock_manager = Mock()
        mock_backend = Mock()

        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.close_issue.return_value = Mock()

        result = self.runner.invoke(cli, ['issues', 'close', '59'])

        assert result.exit_code == 0
        assert 'closed' in result.output.lower()


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

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_backend_error_displays_user_friendly_message(self, mock_manager_class):
        """Test that backend errors are displayed in user-friendly format."""
        mock_manager = Mock()
        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.side_effect = Exception("Backend connection failed")

        result = self.runner.invoke(cli, ['issues', 'list'])

        assert result.exit_code != 0
        assert 'error' in result.output.lower()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_invalid_issue_id_displays_helpful_error(self, mock_manager_class):
        """Test that invalid issue IDs display helpful error messages."""
        mock_manager = Mock()
        mock_backend = Mock()
        mock_manager_class.return_value = mock_manager
        mock_manager.get_backend.return_value = mock_backend
        mock_backend.get_issue.side_effect = Exception("Issue not found")

        result = self.runner.invoke(cli, ['issues', 'show', '999999'])

        assert result.exit_code != 0
        assert 'not found' in result.output.lower()


class TestBackendIntegration:
    """Test suite for backend integration in CLI commands."""

    def setup_method(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_cli_respects_backend_configuration(self, mock_manager_class):
        """Test that CLI commands respect backend configuration."""
        mock_manager = Mock()
        mock_manager_class.return_value = mock_manager

        # Test with different backends
        for backend in ['gitea', 'local']:
            mock_manager.get_backend.return_value = Mock()
            mock_manager.get_backend.return_value.list_issues.return_value = []

            result = self.runner.invoke(cli, ['issues', 'list', '--backend', backend])

            assert result.exit_code == 0
            mock_manager.get_backend.assert_called_with(backend)

    @patch('markitect.issues.commands.IssuePluginManager')
    def test_cli_handles_plugin_switching_gracefully(self, mock_manager_class):
        """Test that CLI handles switching between plugins gracefully."""
        mock_manager = Mock()
        mock_manager_class.return_value = mock_manager

        # First call with gitea
        mock_manager.get_backend.return_value = Mock()
        mock_manager.get_backend.return_value.list_issues.return_value = []
        result1 = self.runner.invoke(cli, ['issues', 'list', '--backend', 'gitea'])

        # Second call with local
        mock_manager.get_backend.return_value = Mock()
        mock_manager.get_backend.return_value.list_issues.return_value = []
        result2 = self.runner.invoke(cli, ['issues', 'list', '--backend', 'local'])

        assert result1.exit_code == 0
        assert result2.exit_code == 0