"""
Tests for the AddEntryModal with DNS name support.

This module tests the enhanced AddEntryModal functionality including
DNS name entries, validation, and mutual exclusion logic.
"""

import pytest
from unittest.mock import Mock
from textual.widgets import Input, Checkbox, RadioSet, Static

from src.hosts.tui.add_entry_modal import AddEntryModal
from src.hosts.core.models import HostEntry


class TestAddEntryModalDNSSupport:
    """Test cases for AddEntryModal DNS name support."""

    def setup_method(self):
        """Set up test fixtures."""
        self.modal = AddEntryModal()

    def test_modal_initialization(self):
        """Test that the modal initializes correctly."""
        assert isinstance(self.modal, AddEntryModal)

    def test_compose_method_creates_dns_components(self):
        """Test that compose method creates DNS-related components."""
        # Test that the compose method exists and can be called
        # We can't test the actual widget creation without mounting the modal
        # in a Textual app context, so we just verify the method exists
        assert hasattr(self.modal, 'compose')
        assert callable(self.modal.compose)

    def test_validate_input_ip_entry_valid(self):
        """Test validation for valid IP entry."""
        # Test valid IP entry
        result = self.modal._validate_input(
            ip_address="192.168.1.1",
            dns_name="",
            hostnames_str="example.com",
            is_dns_entry=False
        )
        assert result is True

    def test_validate_input_ip_entry_missing_ip(self):
        """Test validation for IP entry with missing IP address."""
        # Mock the error display method
        self.modal._show_error = Mock()
        
        result = self.modal._validate_input(
            ip_address="",
            dns_name="",
            hostnames_str="example.com",
            is_dns_entry=False
        )
        assert result is False
        self.modal._show_error.assert_called_with("ip-error", "IP address is required")

    def test_validate_input_dns_entry_valid(self):
        """Test validation for valid DNS entry."""
        result = self.modal._validate_input(
            ip_address="",
            dns_name="example.com",
            hostnames_str="www.example.com",
            is_dns_entry=True
        )
        assert result is True

    def test_validate_input_dns_entry_missing_dns_name(self):
        """Test validation for DNS entry with missing DNS name."""
        # Mock the error display method
        self.modal._show_error = Mock()
        
        result = self.modal._validate_input(
            ip_address="",
            dns_name="",
            hostnames_str="example.com",
            is_dns_entry=True
        )
        assert result is False
        self.modal._show_error.assert_called_with("dns-error", "DNS name is required")

    def test_validate_input_dns_entry_invalid_format(self):
        """Test validation for DNS entry with invalid DNS name format."""
        # Mock the error display method
        self.modal._show_error = Mock()
        
        # Test various invalid DNS name formats
        invalid_dns_names = [
            "example .com",  # Contains space
            ".example.com",  # Starts with dot
            "example.com.",  # Ends with dot
            "example..com",  # Double dots
            "ex@mple.com",   # Invalid characters
        ]
        
        for invalid_dns in invalid_dns_names:
            result = self.modal._validate_input(
                ip_address="",
                dns_name=invalid_dns,
                hostnames_str="example.com",
                is_dns_entry=True
            )
            assert result is False
            self.modal._show_error.assert_called_with("dns-error", "Invalid DNS name format")

    def test_validate_input_missing_hostnames(self):
        """Test validation for entries with missing hostnames."""
        # Mock the error display method
        self.modal._show_error = Mock()
        
        # Test IP entry without hostnames
        result = self.modal._validate_input(
            ip_address="192.168.1.1",
            dns_name="",
            hostnames_str="",
            is_dns_entry=False
        )
        assert result is False
        self.modal._show_error.assert_called_with("hostnames-error", "At least one hostname is required")

    def test_validate_input_invalid_hostnames(self):
        """Test validation for entries with invalid hostnames."""
        # Mock the error display method
        self.modal._show_error = Mock()
        
        # Test with invalid hostname containing spaces
        result = self.modal._validate_input(
            ip_address="192.168.1.1",
            dns_name="",
            hostnames_str="invalid hostname",
            is_dns_entry=False
        )
        assert result is False
        self.modal._show_error.assert_called_with("hostnames-error", "Invalid hostname format: invalid hostname")

    def test_clear_errors_includes_dns_error(self):
        """Test that clear_errors method includes DNS error clearing."""
        # Mock the query_one method to return mock widgets
        mock_ip_error = Mock(spec=Static)
        mock_dns_error = Mock(spec=Static)
        mock_hostnames_error = Mock(spec=Static)
        
        def mock_query_one(selector, widget_type):
            if selector == "#ip-error":
                return mock_ip_error
            elif selector == "#dns-error":
                return mock_dns_error
            elif selector == "#hostnames-error":
                return mock_hostnames_error
            return Mock()
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Call clear_errors
        self.modal._clear_errors()
        
        # Verify all error widgets were cleared
        mock_ip_error.update.assert_called_with("")
        mock_dns_error.update.assert_called_with("")
        mock_hostnames_error.update.assert_called_with("")

    def test_show_error_displays_message(self):
        """Test that show_error method displays error messages correctly."""
        # Mock the query_one method to return a mock widget
        mock_error_widget = Mock(spec=Static)
        self.modal.query_one = Mock(return_value=mock_error_widget)
        
        # Test showing an error
        self.modal._show_error("dns-error", "Test error message")
        
        # Verify the error widget was updated
        self.modal.query_one.assert_called_with("#dns-error", Static)
        mock_error_widget.update.assert_called_with("Test error message")

    def test_show_error_handles_missing_widget(self):
        """Test that show_error handles missing widgets gracefully."""
        # Mock query_one to raise an exception
        self.modal.query_one = Mock(side_effect=Exception("Widget not found"))
        
        # This should not raise an exception
        try:
            self.modal._show_error("dns-error", "Test error message")
        except Exception:
            pytest.fail("_show_error should handle missing widgets gracefully")


class TestAddEntryModalRadioButtonLogic:
    """Test cases for radio button logic in AddEntryModal."""

    def setup_method(self):
        """Set up test fixtures."""
        self.modal = AddEntryModal()

    def test_radio_button_change_to_ip_entry(self):
        """Test radio button change to IP entry mode."""
        # Mock the query_one method for sections and inputs
        mock_ip_section = Mock()
        mock_dns_section = Mock()
        mock_ip_input = Mock(spec=Input)
        
        def mock_query_one(selector, widget_type=None):
            if selector == "#ip-section":
                return mock_ip_section
            elif selector == "#dns-section":
                return mock_dns_section
            elif selector == "#ip-address-input":
                return mock_ip_input
            return Mock()
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Create mock event
        mock_radio = Mock()
        mock_radio.id = "ip-entry-radio"
        mock_radio_set = Mock()
        mock_radio_set.id = "entry-type-radio"
        
        class MockEvent:
            def __init__(self):
                self.radio_set = mock_radio_set
                self.pressed = mock_radio
        
        event = MockEvent()
        
        # Call the event handler
        self.modal.on_radio_set_changed(event)
        
        # Verify IP section is shown and DNS section is hidden
        mock_ip_section.remove_class.assert_called_with("hidden")
        mock_dns_section.add_class.assert_called_with("hidden")
        mock_ip_input.focus.assert_called_once()

    def test_radio_button_change_to_dns_entry(self):
        """Test radio button change to DNS entry mode."""
        # Mock the query_one method for sections and inputs
        mock_ip_section = Mock()
        mock_dns_section = Mock()
        mock_dns_input = Mock(spec=Input)
        
        def mock_query_one(selector, widget_type=None):
            if selector == "#ip-section":
                return mock_ip_section
            elif selector == "#dns-section":
                return mock_dns_section
            elif selector == "#dns-name-input":
                return mock_dns_input
            return Mock()
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Create mock event
        mock_radio = Mock()
        mock_radio.id = "dns-entry-radio"
        mock_radio_set = Mock()
        mock_radio_set.id = "entry-type-radio"
        
        class MockEvent:
            def __init__(self):
                self.radio_set = mock_radio_set
                self.pressed = mock_radio
        
        event = MockEvent()
        
        # Call the event handler
        self.modal.on_radio_set_changed(event)
        
        # Verify DNS section is shown and IP section is hidden
        mock_ip_section.add_class.assert_called_with("hidden")
        mock_dns_section.remove_class.assert_called_with("hidden")
        mock_dns_input.focus.assert_called_once()


class TestAddEntryModalSaveLogic:
    """Test cases for save logic in AddEntryModal."""

    def setup_method(self):
        """Set up test fixtures."""
        self.modal = AddEntryModal()

    def test_action_save_ip_entry_creation(self):
        """Test saving a valid IP entry."""
        # Mock validation to return True (not None)
        self.modal._validate_input = Mock(return_value=True)
        self.modal._clear_errors = Mock()
        self.modal.dismiss = Mock()
        
        # Mock form widgets
        mock_radio_set = Mock(spec=RadioSet)
        mock_radio_set.pressed_button = None  # IP entry mode
        
        mock_ip_input = Mock(spec=Input)
        mock_ip_input.value = "192.168.1.1"
        
        mock_dns_input = Mock(spec=Input)
        mock_dns_input.value = ""
        
        mock_hostnames_input = Mock(spec=Input)
        mock_hostnames_input.value = "example.com, www.example.com"
        
        mock_comment_input = Mock(spec=Input)
        mock_comment_input.value = "Test comment"
        
        mock_active_checkbox = Mock(spec=Checkbox)
        mock_active_checkbox.value = True
        
        def mock_query_one(selector, widget_type):
            if selector == "#entry-type-radio":
                return mock_radio_set
            elif selector == "#ip-address-input":
                return mock_ip_input
            elif selector == "#dns-name-input":
                return mock_dns_input
            elif selector == "#hostnames-input":
                return mock_hostnames_input
            elif selector == "#comment-input":
                return mock_comment_input
            elif selector == "#active-checkbox":
                return mock_active_checkbox
            return Mock()
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Call action_save
        self.modal.action_save()
        
        # Verify validation was called
        self.modal._validate_input.assert_called_once_with(
            "192.168.1.1", "", "example.com, www.example.com", None
        )
        
        # Verify modal was dismissed with a HostEntry
        self.modal.dismiss.assert_called_once()
        created_entry = self.modal.dismiss.call_args[0][0]
        assert isinstance(created_entry, HostEntry)
        assert created_entry.ip_address == "192.168.1.1"
        assert created_entry.hostnames == ["example.com", "www.example.com"]
        assert created_entry.comment == "Test comment"
        assert created_entry.is_active is True

    def test_action_save_dns_entry_creation(self):
        """Test saving a valid DNS entry."""
        # Mock validation to return True
        self.modal._validate_input = Mock(return_value=True)
        self.modal._clear_errors = Mock()
        self.modal.dismiss = Mock()
        
        # Mock form widgets
        mock_radio_button = Mock()
        mock_radio_button.id = "dns-entry-radio"
        mock_radio_set = Mock(spec=RadioSet)
        mock_radio_set.pressed_button = mock_radio_button
        
        mock_ip_input = Mock(spec=Input)
        mock_ip_input.value = ""
        
        mock_dns_input = Mock(spec=Input)
        mock_dns_input.value = "example.com"
        
        mock_hostnames_input = Mock(spec=Input)
        mock_hostnames_input.value = "www.example.com"
        
        mock_comment_input = Mock(spec=Input)
        mock_comment_input.value = ""
        
        mock_active_checkbox = Mock(spec=Checkbox)
        mock_active_checkbox.value = True
        
        def mock_query_one(selector, widget_type):
            if selector == "#entry-type-radio":
                return mock_radio_set
            elif selector == "#ip-address-input":
                return mock_ip_input
            elif selector == "#dns-name-input":
                return mock_dns_input
            elif selector == "#hostnames-input":
                return mock_hostnames_input
            elif selector == "#comment-input":
                return mock_comment_input
            elif selector == "#active-checkbox":
                return mock_active_checkbox
            return Mock()
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Call action_save
        self.modal.action_save()
        
        # Verify validation was called
        self.modal._validate_input.assert_called_once_with(
            "", "example.com", "www.example.com", True
        )
        
        # Verify modal was dismissed with a DNS HostEntry
        self.modal.dismiss.assert_called_once()
        created_entry = self.modal.dismiss.call_args[0][0]
        assert isinstance(created_entry, HostEntry)
        assert created_entry.ip_address == "0.0.0.0"  # Placeholder IP for DNS entries
        assert hasattr(created_entry, 'dns_name')
        assert created_entry.dns_name == "example.com"
        assert created_entry.hostnames == ["www.example.com"]
        assert created_entry.comment is None
        assert created_entry.is_active is False  # Inactive until DNS resolution

    def test_action_save_validation_failure(self):
        """Test save action when validation fails."""
        # Mock validation to return False
        self.modal._validate_input = Mock(return_value=False)
        self.modal._clear_errors = Mock()
        self.modal.dismiss = Mock()
        
        # Mock form widgets (minimal setup since validation fails)
        mock_radio_set = Mock(spec=RadioSet)
        mock_radio_set.pressed_button = None
        
        def mock_query_one(selector, widget_type):
            if selector == "#entry-type-radio":
                return mock_radio_set
            return Mock(spec=Input, value="")
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Call action_save
        self.modal.action_save()
        
        # Verify validation was called and modal was not dismissed
        self.modal._validate_input.assert_called_once()
        self.modal.dismiss.assert_not_called()

    def test_action_save_exception_handling(self):
        """Test save action exception handling."""
        # Mock validation to return True
        self.modal._validate_input = Mock(return_value=True)
        self.modal._clear_errors = Mock()
        self.modal._show_error = Mock()
        
        # Mock form widgets
        mock_radio_set = Mock(spec=RadioSet)
        mock_radio_set.pressed_button = None
        
        mock_input = Mock(spec=Input)
        mock_input.value = "invalid"
        
        def mock_query_one(selector, widget_type):
            if selector == "#entry-type-radio":
                return mock_radio_set
            return mock_input
        
        self.modal.query_one = Mock(side_effect=mock_query_one)
        
        # Mock HostEntry to raise ValueError
        with pytest.MonkeyPatch.context() as m:
            def mock_host_entry(*args, **kwargs):
                raise ValueError("Invalid IP address")
            
            m.setattr("src.hosts.tui.add_entry_modal.HostEntry", mock_host_entry)
            
            # Call action_save
            self.modal.action_save()
            
            # Verify error was shown
            self.modal._show_error.assert_called_once_with("hostnames-error", "Invalid IP address")
