"""
Tests for the hosts file parser.

This module contains unit tests for the HostsParser class,
validating file parsing and serialization functionality.
"""

import pytest
import tempfile
import os
from pathlib import Path
from hosts.core.parser import HostsParser
from hosts.core.models import HostEntry, HostsFile


class TestHostsParser:
    """Test cases for the HostsParser class."""

    def test_parser_initialization(self):
        """Test parser initialization with default and custom paths."""
        # Default path
        parser = HostsParser()
        assert str(parser.file_path) == "/etc/hosts"

        # Custom path
        custom_path = "/tmp/test_hosts"
        parser = HostsParser(custom_path)
        assert str(parser.file_path) == custom_path

    def test_parse_simple_hosts_file(self):
        """Test parsing a simple hosts file."""
        content = """127.0.0.1 localhost
192.168.1.1 router
"""

        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write(content)
            f.flush()

            parser = HostsParser(f.name)
            hosts_file = parser.parse()

            assert len(hosts_file.entries) == 2

            # Check first entry
            entry1 = hosts_file.entries[0]
            assert entry1.ip_address == "127.0.0.1"
            assert entry1.hostnames == ["localhost"]
            assert entry1.is_active is True
            assert entry1.comment is None

            # Check second entry
            entry2 = hosts_file.entries[1]
            assert entry2.ip_address == "192.168.1.1"
            assert entry2.hostnames == ["router"]
            assert entry2.is_active is True
            assert entry2.comment is None

        os.unlink(f.name)

    def test_parse_hosts_file_with_comments(self):
        """Test parsing hosts file with comments and inactive entries."""
        content = """# This is a header comment
# Another header comment

127.0.0.1 localhost loopback # Loopback address
192.168.1.1 router gateway # Local router
# 10.0.0.1 test.local # Disabled test entry

# Footer comment
"""

        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write(content)
            f.flush()

            parser = HostsParser(f.name)
            hosts_file = parser.parse()

            # Check header comments
            assert len(hosts_file.header_comments) == 2
            assert hosts_file.header_comments[0] == "This is a header comment"
            assert hosts_file.header_comments[1] == "Another header comment"

            # Check entries
            assert len(hosts_file.entries) == 3

            # Active entry with comment
            entry1 = hosts_file.entries[0]
            assert entry1.ip_address == "127.0.0.1"
            assert entry1.hostnames == ["localhost", "loopback"]
            assert entry1.comment == "Loopback address"
            assert entry1.is_active is True

            # Another active entry
            entry2 = hosts_file.entries[1]
            assert entry2.ip_address == "192.168.1.1"
            assert entry2.hostnames == ["router", "gateway"]
            assert entry2.comment == "Local router"
            assert entry2.is_active is True

            # Inactive entry
            entry3 = hosts_file.entries[2]
            assert entry3.ip_address == "10.0.0.1"
            assert entry3.hostnames == ["test.local"]
            assert entry3.comment == "Disabled test entry"
            assert entry3.is_active is False

            # Check footer comments
            assert len(hosts_file.footer_comments) == 1
            assert hosts_file.footer_comments[0] == "Footer comment"

        os.unlink(f.name)

    def test_parse_empty_file(self):
        """Test parsing an empty hosts file."""
        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write("")
            f.flush()

            parser = HostsParser(f.name)
            hosts_file = parser.parse()

            assert len(hosts_file.entries) == 0
            assert len(hosts_file.header_comments) == 0
            assert len(hosts_file.footer_comments) == 0

        os.unlink(f.name)

    def test_parse_comments_only_file(self):
        """Test parsing a file with only comments."""
        content = """# This is a comment
# Another comment
# Yet another comment
"""

        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write(content)
            f.flush()

            parser = HostsParser(f.name)
            hosts_file = parser.parse()

            assert len(hosts_file.entries) == 0
            assert len(hosts_file.header_comments) == 3
            assert hosts_file.header_comments[0] == "This is a comment"
            assert hosts_file.header_comments[1] == "Another comment"
            assert hosts_file.header_comments[2] == "Yet another comment"

        os.unlink(f.name)

    def test_parse_nonexistent_file(self):
        """Test parsing a nonexistent file raises FileNotFoundError."""
        parser = HostsParser("/nonexistent/path/hosts")

        with pytest.raises(FileNotFoundError):
            parser.parse()

    def test_serialize_simple_hosts_file(self):
        """Test serializing a simple 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)

        parser = HostsParser()
        content = parser.serialize(hosts_file)

        expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
127.0.0.1\tlocalhost
192.168.1.1\trouter
"""
        assert content == expected

    def test_serialize_hosts_file_with_comments(self):
        """Test serializing hosts file with comments."""
        hosts_file = HostsFile()
        hosts_file.header_comments = ["Header comment 1", "Header comment 2"]
        hosts_file.footer_comments = ["Footer comment"]

        entry1 = HostEntry(
            ip_address="127.0.0.1", hostnames=["localhost"], comment="Loopback"
        )
        entry2 = HostEntry(ip_address="10.0.0.1", hostnames=["test"], is_active=False)

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

        parser = HostsParser()
        content = parser.serialize(hosts_file)

        expected = """# Header comment 1
# Header comment 2
# Managed by hosts - https://git.s1q.dev/phg/hosts
127.0.0.1\tlocalhost\t# Loopback
# 10.0.0.1\ttest

# Footer comment
"""
        assert content == expected

    def test_serialize_empty_hosts_file(self):
        """Test serializing an empty hosts file."""
        hosts_file = HostsFile()
        parser = HostsParser()
        content = parser.serialize(hosts_file)

        expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
"""
        assert content == expected

    def test_write_hosts_file(self):
        """Test writing hosts file to disk."""
        hosts_file = HostsFile()
        entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
        hosts_file.add_entry(entry)

        with tempfile.NamedTemporaryFile(delete=False) as f:
            parser = HostsParser(f.name)
            parser.write(hosts_file, backup=False)

            # Read back and verify
            with open(f.name, "r") as read_file:
                content = read_file.read()
                expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
127.0.0.1\tlocalhost
"""
                assert content == expected

        os.unlink(f.name)

    def test_write_hosts_file_with_backup(self):
        """Test writing hosts file with backup creation."""
        # Create initial file
        initial_content = "192.168.1.1 router\n"

        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write(initial_content)
            f.flush()

            # Create new hosts file to write
            hosts_file = HostsFile()
            entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"])
            hosts_file.add_entry(entry)

            parser = HostsParser(f.name)
            parser.write(hosts_file, backup=True)

            # Check that backup was created
            backup_path = Path(f.name).with_suffix(".bak")
            assert backup_path.exists()

            # Check backup content
            with open(backup_path, "r") as backup_file:
                backup_content = backup_file.read()
                assert backup_content == initial_content

            # Check new content
            with open(f.name, "r") as new_file:
                new_content = new_file.read()
                expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
127.0.0.1\tlocalhost
"""
                assert new_content == expected

            # Cleanup
            os.unlink(backup_path)

        os.unlink(f.name)

    def test_validate_write_permissions(self):
        """Test write permission validation."""
        # Test with a temporary file (should be writable)
        with tempfile.NamedTemporaryFile() as f:
            parser = HostsParser(f.name)
            assert parser.validate_write_permissions() is True

        # Test with a nonexistent file in /tmp (should be writable)
        parser = HostsParser("/tmp/test_hosts_nonexistent")
        assert parser.validate_write_permissions() is True

        # Test with a path that likely doesn't have write permissions
        parser = HostsParser("/root/test_hosts")
        # This might be True if running as root, so we can't assert False
        result = parser.validate_write_permissions()
        assert isinstance(result, bool)

    def test_get_file_info(self):
        """Test getting file information."""
        content = "127.0.0.1 localhost\n"

        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write(content)
            f.flush()

            parser = HostsParser(f.name)
            info = parser.get_file_info()

            assert info["path"] == f.name
            assert info["exists"] is True
            assert info["readable"] is True
            assert info["size"] == len(content)
            assert info["modified"] is not None
            assert isinstance(info["modified"], float)

        os.unlink(f.name)

    def test_get_file_info_nonexistent(self):
        """Test getting file information for nonexistent file."""
        parser = HostsParser("/nonexistent/path")
        info = parser.get_file_info()

        assert info["path"] == "/nonexistent/path"
        assert info["exists"] is False
        assert info["readable"] is False
        assert info["writable"] is False
        assert info["size"] == 0
        assert info["modified"] is None

    def test_round_trip_parsing(self):
        """Test that parsing and serializing preserves content."""
        original_content = """# System hosts file
# Do not edit manually

127.0.0.1 localhost loopback # Local loopback
::1 localhost # IPv6 loopback
192.168.1.1 router gateway # Local router
# 10.0.0.1 test.local # Test entry (disabled)

# End of file
"""

        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write(original_content)
            f.flush()

            # Parse and serialize
            parser = HostsParser(f.name)
            hosts_file = parser.parse()

            # Write back and read
            parser.write(hosts_file, backup=False)

            with open(f.name, "r") as read_file:
                final_content = read_file.read()

            # The content should be functionally equivalent
            # (though formatting might differ slightly with tabs)
            assert "127.0.0.1\tlocalhost\tloopback\t# Local loopback" in final_content
            assert "::1\t\tlocalhost\t# IPv6 loopback" in final_content
            assert "192.168.1.1\trouter\t\tgateway\t# Local router" in final_content
            assert "# 10.0.0.1\ttest.local\t# Test entry (disabled)" in final_content

        os.unlink(f.name)
