"""Unit tests for utility functions."""

from pathlib import Path
from unittest.mock import MagicMock, patch

from electron_angle_patcher import (
    can_write_to_path,
    extract_app_name,
    get_network_mounts,
    is_running_as_root,
    resign_application,
    strip_extended_attributes,
)


class TestIsRunningAsRoot:
    """Tests for is_running_as_root function."""

    @patch("os.geteuid", return_value=0)
    def test_running_as_root(self, mock_geteuid: MagicMock) -> None:
        """Test returns True when running as root."""
        assert is_running_as_root() is True

    @patch("os.geteuid", return_value=1000)
    def test_running_as_user(self, mock_geteuid: MagicMock) -> None:
        """Test returns False when running as regular user."""
        assert is_running_as_root() is False


class TestCanWriteToPath:
    """Tests for can_write_to_path function."""

    @patch("os.access", return_value=True)
    def test_writable_path(self, mock_access: MagicMock, temp_dir: Path) -> None:
        """Test returns True for writable paths."""
        test_path = temp_dir / "test.txt"
        assert can_write_to_path(test_path) is True
        mock_access.assert_called_once()

    @patch("os.access", return_value=False)
    def test_non_writable_path(self, mock_access: MagicMock, temp_dir: Path) -> None:
        """Test returns False for non-writable paths."""
        test_path = temp_dir / "test.txt"
        assert can_write_to_path(test_path) is False
        mock_access.assert_called_once()


class TestGetNetworkMounts:
    """Tests for get_network_mounts function."""

    @patch("subprocess.run")
    def test_empty_mount_output(self, mock_run: MagicMock) -> None:
        """Test with empty mount output."""
        mock_run.return_value.stdout = ""
        assert get_network_mounts() == set()

    @patch("subprocess.run")
    def test_nfs_mount_detected(self, mock_run: MagicMock) -> None:
        """Test detection of NFS mounts."""
        mock_run.return_value.stdout = "server:/export on /mnt type nfs (rw)\n"
        mounts = get_network_mounts()
        assert len(mounts) > 0
        assert any("mnt" in str(m) for m in mounts)

    @patch("subprocess.run")
    def test_smbfs_mount_detected(self, mock_run: MagicMock) -> None:
        """Test detection of SMBFS mounts."""
        mock_run.return_value.stdout = "//smb/share on /Volumes/share type smbfs (rw)\n"
        mounts = get_network_mounts()
        assert len(mounts) > 0

    @patch("subprocess.run")
    def test_local_mount_ignored(self, mock_run: MagicMock) -> None:
        """Test that local mounts are not included."""
        mock_run.return_value.stdout = "/dev/disk1s1 on / type apfs (rw)\n"
        assert get_network_mounts() == set()

    @patch("subprocess.run", side_effect=FileNotFoundError)
    def test_subprocess_error(self, mock_run: MagicMock) -> None:
        """Test graceful handling of subprocess errors."""
        assert get_network_mounts() == set()


class TestStripExtendedAttributes:
    """Tests for strip_extended_attributes function."""

    @patch("subprocess.run", return_value=MagicMock(returncode=0))
    def test_success(self, mock_run: MagicMock, temp_dir: Path) -> None:
        """Test successful attribute stripping."""
        result = strip_extended_attributes(temp_dir)
        assert result is True

    @patch("subprocess.run", side_effect=FileNotFoundError)
    def test_command_not_found(self, mock_run: MagicMock, temp_dir: Path) -> None:
        """Test when xattr command is not found."""
        result = strip_extended_attributes(temp_dir)
        assert result is False


class TestResignApplication:
    """Tests for resign_application function."""

    @patch("subprocess.run", return_value=MagicMock(returncode=0))
    def test_success(self, mock_run: MagicMock, temp_dir: Path) -> None:
        """Test successful application re-signing."""
        result = resign_application(temp_dir)
        assert result is True

    @patch("subprocess.run", return_value=MagicMock(returncode=1))
    def test_codesign_failure(self, mock_run: MagicMock, temp_dir: Path) -> None:
        """Test when codesign returns non-zero exit code."""
        result = resign_application(temp_dir)
        assert result is False


class TestExtractAppName:
    """Tests for extract_app_name function."""

    def test_simple_path(self, temp_dir: Path) -> None:
        """Test extraction from simple path."""
        file_path = temp_dir / "Library/Application Support/MyApp/Local State"
        name = extract_app_name(file_path, temp_dir)
        assert name == "MyApp"

    def test_nested_generic_folders(self, temp_dir: Path) -> None:
        """Test extraction skips generic folder names."""
        file_path = temp_dir / "Library/Application Support/EBWebView/Local State"
        name = extract_app_name(file_path, temp_dir)
        # Should skip EBWebView
        assert name != "EBWebView"

    def test_path_outside_start_dir(self, temp_dir: Path) -> None:
        """Test handling of path outside start directory."""
        other_dir = Path("/tmp/other")
        file_path = temp_dir / "Local State"
        name = extract_app_name(file_path, other_dir)
        assert name  # Should return something, not fail

    def test_trivial_path(self, temp_dir: Path) -> None:
        """Test extraction from minimal path."""
        file_path = temp_dir / "AppName/Local State"
        name = extract_app_name(file_path, temp_dir)
        assert name == "AppName"


class TestKillAppByName:
    """Tests for kill_app_by_name function."""

    @patch("subprocess.run")
    def test_kill_app_success(self, mock_run: MagicMock) -> None:
        """Test successful app kill."""
        from electron_angle_patcher.utils import kill_app_by_name

        mock_run.return_value.returncode = 0

        result = kill_app_by_name("TestApp")
        assert result is True
        mock_run.assert_called_once_with(
            ["pkill", "-9", "TestApp"],
            capture_output=True,
            text=True,
            check=False,
        )

    @patch("subprocess.run")
    def test_kill_app_not_running(self, mock_run: MagicMock) -> None:
        """Test killing app that is not running."""
        from electron_angle_patcher.utils import kill_app_by_name

        mock_run.return_value.returncode = 1

        result = kill_app_by_name("TestApp")
        assert result is False

    @patch("subprocess.run", side_effect=FileNotFoundError)
    def test_kill_app_command_not_found(self, mock_run: MagicMock) -> None:
        """Test pkill command not found."""
        from electron_angle_patcher.utils import kill_app_by_name

        result = kill_app_by_name("TestApp")
        assert result is False
