"""
Tests for the hosts data models.

This module contains unit tests for the HostEntry and HostsFile classes,
validating their functionality and data integrity.
"""

import pytest
from hosts.core.models import HostEntry, HostsFile


class TestHostEntry:
    """Test cases for the HostEntry class."""

    def test_host_entry_creation(self):
        """Test basic host entry creation."""
        entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        assert entry.ip_address == "127.0.0.1"
        assert entry.hostnames == ["localhost"]
        assert entry.is_active is True
        assert entry.comment is None
        assert entry.dns_name is None

    def test_host_entry_with_comment(self):
        """Test host entry creation with comment."""
        entry = HostEntry(
            ip_address="192.168.1.1",
            hostnames=["router", "gateway"],
            comment="Local router",
        )
        assert entry.comment == "Local router"

    def test_host_entry_inactive(self):
        """Test inactive host entry creation."""
        entry = HostEntry(
            ip_address="10.0.0.1", hostnames=["test.local"], is_active=False
        )
        assert entry.is_active is False

    def test_invalid_ip_address(self):
        """Test that invalid IP addresses raise ValueError."""
        with pytest.raises(ValueError, match="Invalid IP address"):
            HostEntry(ip_address="invalid.ip", hostnames=["test"])

    def test_empty_hostnames(self):
        """Test that empty hostnames list raises ValueError."""
        with pytest.raises(ValueError, match="At least one hostname is required"):
            HostEntry(ip_address="127.0.0.1", hostnames=[])

    def test_invalid_hostname(self):
        """Test that invalid hostnames raise ValueError."""
        with pytest.raises(ValueError, match="Invalid hostname"):
            HostEntry(ip_address="127.0.0.1", hostnames=["invalid..hostname"])

    def test_ipv6_address(self):
        """Test IPv6 address support."""
        entry = HostEntry(ip_address="::1", hostnames=["localhost"])
        assert entry.ip_address == "::1"

    def test_to_hosts_line_active(self):
        """Test conversion to hosts file line format for active entry."""
        entry = HostEntry(
            ip_address="127.0.0.1", hostnames=["localhost", "local"], comment="Loopback"
        )
        line = entry.to_hosts_line()
        assert line == "127.0.0.1\tlocalhost\tlocal\t# Loopback"

    def test_to_hosts_line_inactive(self):
        """Test conversion to hosts file line format for inactive entry."""
        entry = HostEntry(
            ip_address="192.168.1.1", hostnames=["router"], is_active=False
        )
        line = entry.to_hosts_line()
        assert line == "# 192.168.1.1\trouter"

    def test_from_hosts_line_simple(self):
        """Test parsing simple hosts file line."""
        line = "127.0.0.1 localhost"
        entry = HostEntry.from_hosts_line(line)

        assert entry is not None
        assert entry.ip_address == "127.0.0.1"
        assert entry.hostnames == ["localhost"]
        assert entry.is_active is True
        assert entry.comment is None

    def test_from_hosts_line_with_comment(self):
        """Test parsing hosts file line with comment."""
        line = "192.168.1.1 router gateway # Local network"
        entry = HostEntry.from_hosts_line(line)

        assert entry is not None
        assert entry.ip_address == "192.168.1.1"
        assert entry.hostnames == ["router", "gateway"]
        assert entry.comment == "Local network"

    def test_from_hosts_line_inactive(self):
        """Test parsing inactive hosts file line."""
        line = "# 10.0.0.1 test.local"
        entry = HostEntry.from_hosts_line(line)

        assert entry is not None
        assert entry.ip_address == "10.0.0.1"
        assert entry.hostnames == ["test.local"]
        assert entry.is_active is False

    def test_from_hosts_line_empty(self):
        """Test parsing empty line returns None."""
        assert HostEntry.from_hosts_line("") is None
        assert HostEntry.from_hosts_line("   ") is None

    def test_from_hosts_line_comment_only(self):
        """Test parsing comment-only line returns None."""
        assert HostEntry.from_hosts_line("# This is just a comment") is None

    def test_from_hosts_line_invalid(self):
        """Test parsing invalid line returns None."""
        assert HostEntry.from_hosts_line("invalid line") is None
        assert HostEntry.from_hosts_line("192.168.1.1") is None  # No hostname


class TestHostsFile:
    """Test cases for the HostsFile class."""

    def test_hosts_file_creation(self):
        """Test basic hosts file creation."""
        hosts_file = HostsFile()
        assert len(hosts_file.entries) == 0
        assert len(hosts_file.header_comments) == 0
        assert len(hosts_file.footer_comments) == 0

    def test_add_entry(self):
        """Test adding entries to hosts file."""
        hosts_file = HostsFile()
        entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])

        hosts_file.add_entry(entry)
        assert len(hosts_file.entries) == 1
        assert hosts_file.entries[0] == entry

    def test_add_invalid_entry(self):
        """Test that adding invalid entry raises ValueError."""
        hosts_file = HostsFile()

        with pytest.raises(ValueError):
            # This will fail validation in add_entry
            invalid_entry = HostEntry.__new__(HostEntry)  # Bypass __init__
            invalid_entry.ip_address = "invalid"
            invalid_entry.hostnames = ["test"]
            hosts_file.add_entry(invalid_entry)

    def test_remove_entry(self):
        """Test removing entries from hosts file."""
        hosts_file = HostsFile()
        entry1 = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        entry2 = HostEntry(ip_address="192.168.1.1", hostnames=["router"])

        hosts_file.add_entry(entry1)
        hosts_file.add_entry(entry2)

        hosts_file.remove_entry(0)
        assert len(hosts_file.entries) == 1
        assert hosts_file.entries[0] == entry2

    def test_remove_entry_invalid_index(self):
        """Test removing entry with invalid index does nothing."""
        hosts_file = HostsFile()
        entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        hosts_file.add_entry(entry)

        hosts_file.remove_entry(10)  # Invalid index
        assert len(hosts_file.entries) == 1

    def test_toggle_entry(self):
        """Test toggling entry active state."""
        hosts_file = HostsFile()
        entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        hosts_file.add_entry(entry)

        assert entry.is_active is True
        hosts_file.toggle_entry(0)
        assert entry.is_active is False
        hosts_file.toggle_entry(0)
        assert entry.is_active is True

    def test_get_active_entries(self):
        """Test getting only active entries."""
        hosts_file = HostsFile()
        active_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        inactive_entry = HostEntry(
            ip_address="192.168.1.1", hostnames=["router"], is_active=False
        )

        hosts_file.add_entry(active_entry)
        hosts_file.add_entry(inactive_entry)

        active_entries = hosts_file.get_active_entries()
        assert len(active_entries) == 1
        assert active_entries[0] == active_entry

    def test_get_inactive_entries(self):
        """Test getting only inactive entries."""
        hosts_file = HostsFile()
        active_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        inactive_entry = HostEntry(
            ip_address="192.168.1.1", hostnames=["router"], is_active=False
        )

        hosts_file.add_entry(active_entry)
        hosts_file.add_entry(inactive_entry)

        inactive_entries = hosts_file.get_inactive_entries()
        assert len(inactive_entries) == 1
        assert inactive_entries[0] == inactive_entry

    def test_sort_by_ip(self):
        """Test sorting entries by IP address with default entries on top."""
        hosts_file = HostsFile()
        entry1 = HostEntry(ip_address="192.168.1.1", hostnames=["router"])
        entry2 = HostEntry(
            ip_address="127.0.0.1", hostnames=["localhost"]
        )  # Default entry
        entry3 = HostEntry(ip_address="10.0.0.1", hostnames=["test"])

        hosts_file.add_entry(entry1)
        hosts_file.add_entry(entry2)
        hosts_file.add_entry(entry3)

        hosts_file.sort_by_ip()

        # Default entries should come first, then sorted non-default entries
        assert hosts_file.entries[0].ip_address == "127.0.0.1"  # Default entry first
        assert (
            hosts_file.entries[1].ip_address == "10.0.0.1"
        )  # Then sorted non-defaults
        assert hosts_file.entries[2].ip_address == "192.168.1.1"

    def test_sort_by_hostname(self):
        """Test sorting entries by hostname."""
        hosts_file = HostsFile()
        entry1 = HostEntry(ip_address="127.0.0.1", hostnames=["zebra"])
        entry2 = HostEntry(ip_address="192.168.1.1", hostnames=["alpha"])
        entry3 = HostEntry(ip_address="10.0.0.1", hostnames=["beta"])

        hosts_file.add_entry(entry1)
        hosts_file.add_entry(entry2)
        hosts_file.add_entry(entry3)

        hosts_file.sort_by_hostname()

        assert hosts_file.entries[0].hostnames[0] == "alpha"
        assert hosts_file.entries[1].hostnames[0] == "beta"
        assert hosts_file.entries[2].hostnames[0] == "zebra"

    def test_find_entries_by_hostname(self):
        """Test finding entries by hostname."""
        hosts_file = HostsFile()
        entry1 = HostEntry(ip_address="127.0.0.1", hostnames=["localhost", "local"])
        entry2 = HostEntry(ip_address="192.168.1.1", hostnames=["router"])
        entry3 = HostEntry(ip_address="10.0.0.1", hostnames=["test", "localhost"])

        hosts_file.add_entry(entry1)
        hosts_file.add_entry(entry2)
        hosts_file.add_entry(entry3)

        indices = hosts_file.find_entries_by_hostname("localhost")
        assert indices == [0, 2]

        indices = hosts_file.find_entries_by_hostname("router")
        assert indices == [1]

        indices = hosts_file.find_entries_by_hostname("nonexistent")
        assert indices == []

    def test_find_entries_by_ip(self):
        """Test finding entries by IP address."""
        hosts_file = HostsFile()
        entry1 = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        entry2 = HostEntry(ip_address="192.168.1.1", hostnames=["router"])
        entry3 = HostEntry(ip_address="127.0.0.1", hostnames=["local"])

        hosts_file.add_entry(entry1)
        hosts_file.add_entry(entry2)
        hosts_file.add_entry(entry3)

        indices = hosts_file.find_entries_by_ip("127.0.0.1")
        assert indices == [0, 2]

        indices = hosts_file.find_entries_by_ip("192.168.1.1")
        assert indices == [1]

        indices = hosts_file.find_entries_by_ip("10.0.0.1")
        assert indices == []
