# tests/test_spellbook_wizard.py

import unittest
from io import StringIO
from unittest.mock import patch, MagicMock

from django.test import TestCase
from django.core.management import call_command
from django.core.management.base import CommandError

from django_spellbook.management.commands.wizard_utils import show_menu, confirm_action


class TestWizardUtils(TestCase):
    """Test utility functions for the wizard."""

    @patch('builtins.input', return_value='1')
    def test_show_menu_valid_choice(self, mock_input):
        """Valid menu choice is accepted."""
        choice = show_menu(
            'Test Menu',
            [('1', 'Option One'), ('2', 'Option Two')],
            allow_back=False
        )
        self.assertEqual(choice, '1')

    @patch('builtins.input', side_effect=['invalid', '', '2'])
    def test_show_menu_invalid_then_valid(self, mock_input):
        """Invalid input is rejected, then valid input accepted."""
        with patch('builtins.print'):  # Suppress print output
            with patch('django_spellbook.management.commands.wizard_utils.clear_screen'):  # Suppress clear
                choice = show_menu(
                    'Test Menu',
                    [('1', 'Option One'), ('2', 'Option Two')],
                    allow_back=False
                )
        self.assertEqual(choice, '2')

    @patch('builtins.input', return_value='0')
    def test_show_menu_exit(self, mock_input):
        """Exit option (0) works."""
        choice = show_menu(
            'Test Menu',
            [('1', 'Option One')],
            allow_back=False
        )
        self.assertEqual(choice, '0')

    @patch('builtins.input', return_value='0')
    def test_show_menu_back(self, mock_input):
        """Back option works in submenu."""
        choice = show_menu(
            'Test Menu',
            [('1', 'Option One')],
            allow_back=True
        )
        self.assertEqual(choice, '0')

    @patch('builtins.input', side_effect=KeyboardInterrupt)
    def test_show_menu_keyboard_interrupt(self, mock_input):
        """Ctrl+C gracefully exits."""
        with patch('builtins.print'):
            choice = show_menu(
                'Test Menu',
                [('1', 'Option One')],
                allow_back=False
            )
        self.assertEqual(choice, '0')

    @patch('builtins.input', return_value='y')
    def test_confirm_action_yes(self, mock_input):
        """Confirm action returns True for 'y'."""
        result = confirm_action("Continue?", default=True)
        self.assertTrue(result)

    @patch('builtins.input', return_value='n')
    def test_confirm_action_no(self, mock_input):
        """Confirm action returns False for 'n'."""
        result = confirm_action("Continue?", default=True)
        self.assertFalse(result)

    @patch('builtins.input', return_value='')
    def test_confirm_action_default_true(self, mock_input):
        """Empty input uses default (True)."""
        result = confirm_action("Continue?", default=True)
        self.assertTrue(result)

    @patch('builtins.input', return_value='')
    def test_confirm_action_default_false(self, mock_input):
        """Empty input uses default (False)."""
        result = confirm_action("Continue?", default=False)
        self.assertFalse(result)


class TestSpellbookWizardCommand(TestCase):
    """Test the spellbook_wizard management command."""

    @patch('django_spellbook.management.commands.spellbook_wizard.show_menu')
    @patch('django_spellbook.management.commands.spellbook_wizard.call_command')
    def test_wizard_exits_on_zero_from_main(self, mock_call_command, mock_show_menu):
        """Wizard exits when user selects 0 from main menu."""
        # Simulate user selecting exit (0)
        mock_show_menu.return_value = '0'

        out = StringIO()
        call_command('spellbook_wizard', stdout=out)

        output = out.getvalue()
        self.assertIn('Goodbye', output)
        mock_call_command.assert_not_called()

    @patch('django_spellbook.management.commands.spellbook_wizard.show_menu')
    @patch('django_spellbook.management.commands.spellbook_wizard.call_command')
    @patch('builtins.input', return_value='')  # Mock "Press Enter"
    def test_wizard_navigates_to_batch_process(self, mock_input, mock_call_command, mock_show_menu):
        """Wizard navigates to batch process submenu."""
        # Simulate: main menu → batch (1) → back (0) → exit (0)
        mock_show_menu.side_effect = ['1', '0', '0']

        out = StringIO()
        call_command('spellbook_wizard', stdout=out)

        # Should have called show_menu 3 times (main, batch, main again)
        self.assertEqual(mock_show_menu.call_count, 3)

    @patch('django_spellbook.management.commands.spellbook_wizard.show_menu')
    @patch('django_spellbook.management.commands.spellbook_wizard.call_command')
    @patch('builtins.input', return_value='')  # Mock "Press Enter"
    def test_wizard_runs_spellbook_md(self, mock_input, mock_call_command, mock_show_menu):
        """Wizard executes spellbook_md when selected."""
        # Simulate: main → batch (1) → spellbook_md (1) → back (0) → exit (0)
        mock_show_menu.side_effect = ['1', '1', '0', '0']

        out = StringIO()
        call_command('spellbook_wizard', stdout=out)

        # Should have called spellbook_md
        mock_call_command.assert_any_call('spellbook_md')

    @patch('django_spellbook.management.commands.spellbook_wizard.show_menu')
    @patch('django_spellbook.management.commands.spellbook_wizard.call_command')
    @patch('builtins.input', return_value='')
    def test_wizard_handles_spellbook_md_error(self, mock_input, mock_call_command, mock_show_menu):
        """Wizard gracefully handles errors from spellbook_md."""
        # Simulate: main → batch (1) → spellbook_md (1) → back (0) → exit (0)
        mock_show_menu.side_effect = ['1', '1', '0', '0']

        # Make spellbook_md raise an error
        def raise_error(cmd):
            if cmd == 'spellbook_md':
                raise CommandError("Test error")

        mock_call_command.side_effect = raise_error

        out = StringIO()
        call_command('spellbook_wizard', stdout=out)

        output = out.getvalue()
        self.assertIn('Error running spellbook_md', output)

    @patch('django_spellbook.management.commands.spellbook_wizard.show_menu')
    @patch('builtins.input', return_value='')
    def test_wizard_shows_coming_soon_for_validate(self, mock_input, mock_show_menu):
        """Wizard shows 'coming soon' for validate option."""
        # Simulate: main → validate (2) → exit (0)
        mock_show_menu.side_effect = ['2', '0']

        out = StringIO()
        call_command('spellbook_wizard', stdout=out)

        output = out.getvalue()
        self.assertIn('coming soon', output.lower())

    @patch('django_spellbook.management.commands.spellbook_wizard.show_menu')
    @patch('django_spellbook.management.commands.spellbook_wizard.call_command')
    @patch('builtins.input', return_value='')
    def test_wizard_multiple_operations(self, mock_input, mock_call_command, mock_show_menu):
        """Wizard can run multiple operations in one session."""
        # Simulate: main → batch → spellbook_md → back → batch → spellbook_md → back → exit
        mock_show_menu.side_effect = ['1', '1', '0', '1', '1', '0', '0']

        out = StringIO()
        call_command('spellbook_wizard', stdout=out)

        # Should have called spellbook_md twice
        spellbook_md_calls = [
            call for call in mock_call_command.call_args_list
            if call[0][0] == 'spellbook_md'
        ]
        self.assertEqual(len(spellbook_md_calls), 2)
