"""Tests for desktop integration module."""

from __future__ import annotations

import os
import tempfile
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from typer.testing import CliRunner

from gaol.desktop import (
    DesktopCategory,
    DesktopConfig,
    DesktopEntry,
    DesktopEntryExistsError,
    DesktopEntryGenerator,
    DesktopEntryNotFoundError,
    DesktopInstaller,
    InstalledEntry,
)


class TestDesktopModels:
    """Tests for desktop Pydantic models."""

    def test_desktop_entry_creation(self) -> None:
        """Test creating a basic desktop entry."""
        entry = DesktopEntry(
            name="Test App",
            container_name="test-container",
            exec_command="/usr/bin/testapp",
        )
        assert entry.name == "Test App"
        assert entry.container_name == "test-container"
        assert entry.exec_command == "/usr/bin/testapp"
        assert entry.terminal is False
        assert entry.start_container is True
        assert entry.categories == []

    def test_desktop_entry_with_all_fields(self) -> None:
        """Test desktop entry with all optional fields."""
        entry = DesktopEntry(
            name="Firefox",
            container_name="browser",
            exec_command="/usr/bin/firefox",
            icon="firefox",
            comment="Web browser in container",
            categories=[DesktopCategory.NETWORK],
            terminal=False,
            start_container=True,
            working_dir="/home/user",
            mime_types=["text/html", "application/xhtml+xml"],
            keywords=["browser", "web"],
            generic_name="Web Browser",
        )
        assert entry.icon == "firefox"
        assert entry.comment == "Web browser in container"
        assert DesktopCategory.NETWORK in entry.categories
        assert "text/html" in entry.mime_types
        assert "browser" in entry.keywords

    def test_desktop_category_values(self) -> None:
        """Test that category enum values are correct."""
        assert DesktopCategory.NETWORK.value == "Network"
        assert DesktopCategory.DEVELOPMENT.value == "Development"
        assert DesktopCategory.GRAPHICS.value == "Graphics"
        assert DesktopCategory.UTILITY.value == "Utility"

    def test_desktop_config_defaults(self) -> None:
        """Test default configuration values."""
        config = DesktopConfig()
        assert config.applications_dir is None
        assert config.icons_dir is None
        assert config.default_icon == "application-x-executable"
        assert config.prefix == "gaol-"

    def test_installed_entry_creation(self) -> None:
        """Test creating an installed entry record."""
        entry = InstalledEntry(
            container_name="test-container",
            entry_name="Test App",
            desktop_file="/home/user/.local/share/applications/test.desktop",
            exec_command="/usr/bin/testapp",
        )
        assert entry.container_name == "test-container"
        assert entry.entry_name == "Test App"
        assert entry.desktop_file.endswith("test.desktop")
        assert entry.icon_file is None
        assert isinstance(entry.installed_at, datetime)


class TestDesktopEntryGenerator:
    """Tests for desktop entry generator."""

    def test_generate_basic_entry(self) -> None:
        """Test generating a basic .desktop file."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Test App",
            container_name="test-container",
            exec_command="/usr/bin/testapp",
        )
        content = generator.generate(entry)

        assert "[Desktop Entry]" in content
        assert "Type=Application" in content
        assert "Name=Test App" in content
        assert "Terminal=false" in content
        assert "gaol desktop run" in content
        assert "test-container" in content

    def test_generate_with_icon(self) -> None:
        """Test generating entry with custom icon."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Firefox",
            container_name="browser",
            exec_command="/usr/bin/firefox",
            icon="firefox",
        )
        content = generator.generate(entry)

        assert "Icon=firefox" in content

    def test_generate_with_default_icon(self) -> None:
        """Test generating entry uses default icon."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Test App",
            container_name="test",
            exec_command="/usr/bin/test",
        )
        content = generator.generate(entry)

        assert "Icon=application-x-executable" in content

    def test_generate_with_categories(self) -> None:
        """Test generating entry with categories."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Firefox",
            container_name="browser",
            exec_command="/usr/bin/firefox",
            categories=[DesktopCategory.NETWORK, DesktopCategory.UTILITY],
        )
        content = generator.generate(entry)

        assert "Categories=Network;Utility;" in content

    def test_generate_with_comment(self) -> None:
        """Test generating entry with comment."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Test",
            container_name="test",
            exec_command="/bin/test",
            comment="A test application",
        )
        content = generator.generate(entry)

        assert "Comment=A test application" in content

    def test_generate_with_terminal(self) -> None:
        """Test generating terminal entry."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Terminal App",
            container_name="dev",
            exec_command="/usr/bin/vim",
            terminal=True,
        )
        content = generator.generate(entry)

        assert "Terminal=true" in content

    def test_generate_with_keywords(self) -> None:
        """Test generating entry with keywords."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Editor",
            container_name="dev",
            exec_command="/usr/bin/vim",
            keywords=["editor", "text", "vim"],
        )
        content = generator.generate(entry)

        assert "Keywords=editor;text;vim;" in content

    def test_generate_with_mime_types(self) -> None:
        """Test generating entry with MIME types."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Image Viewer",
            container_name="graphics",
            exec_command="/usr/bin/feh",
            mime_types=["image/png", "image/jpeg"],
        )
        content = generator.generate(entry)

        assert "MimeType=image/png;image/jpeg;" in content

    def test_generate_with_start_flag(self) -> None:
        """Test that --start flag is added for auto-start."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="App",
            container_name="test",
            exec_command="/bin/app",
            start_container=True,
        )
        content = generator.generate(entry)

        assert "--start" in content

    def test_generate_without_start_flag(self) -> None:
        """Test that --start is omitted when disabled."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="App",
            container_name="test",
            exec_command="/bin/app",
            start_container=False,
        )
        content = generator.generate(entry)

        assert "--start" not in content

    def test_generate_includes_gaol_metadata(self) -> None:
        """Test that X-Gaol extensions are included."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Test",
            container_name="my-container",
            exec_command="/usr/bin/myapp",
        )
        content = generator.generate(entry)

        assert "X-Gaol-Container=my-container" in content
        assert "X-Gaol-Command=/usr/bin/myapp" in content

    def test_generate_escapes_special_characters(self) -> None:
        """Test that special characters are escaped."""
        generator = DesktopEntryGenerator()
        entry = DesktopEntry(
            name="Test\nApp",
            container_name="test",
            exec_command="/bin/test",
            comment="Line1\nLine2",
        )
        content = generator.generate(entry)

        assert "Name=Test\\nApp" in content
        assert "Comment=Line1\\nLine2" in content

    def test_generate_for_container_helper(self) -> None:
        """Test the generate_for_container helper method."""
        generator = DesktopEntryGenerator()
        entry = generator.generate_for_container(
            container_name="my-container",
            exec_command="/usr/bin/app",
            name="My App",
            icon="app-icon",
        )
        assert entry.container_name == "my-container"
        assert entry.name == "My App"
        assert entry.exec_command == "/usr/bin/app"
        assert entry.icon == "app-icon"


class TestDesktopInstaller:
    """Tests for desktop entry installer."""

    def test_get_applications_dir_default(self) -> None:
        """Test default applications directory."""
        with patch.dict(os.environ, {}, clear=True):
            installer = DesktopInstaller()
            apps_dir = installer.get_applications_dir()
            assert apps_dir == Path.home() / ".local" / "share" / "applications"

    def test_get_applications_dir_xdg(self, tmp_path: Path) -> None:
        """Test applications directory respects XDG_DATA_HOME."""
        custom_data = tmp_path / "custom_data"
        custom_data.mkdir()

        with patch.dict(os.environ, {"XDG_DATA_HOME": str(custom_data)}):
            with patch("gaol.desktop.installer.get_data_dir", return_value=tmp_path):
                installer = DesktopInstaller()
                apps_dir = installer.get_applications_dir()
                assert apps_dir == custom_data / "applications"

    def test_get_applications_dir_custom(self) -> None:
        """Test custom applications directory config."""
        config = DesktopConfig(applications_dir="/my/apps")
        installer = DesktopInstaller(config)
        apps_dir = installer.get_applications_dir()
        assert apps_dir == Path("/my/apps")

    def test_get_icons_dir_default(self) -> None:
        """Test default icons directory."""
        with patch.dict(os.environ, {}, clear=True):
            installer = DesktopInstaller()
            icons_dir = installer.get_icons_dir()
            expected = Path.home() / ".local" / "share" / "icons" / "hicolor" / "48x48" / "apps"
            assert icons_dir == expected

    def test_install_creates_file(self, tmp_path: Path) -> None:
        """Test that install creates the .desktop file."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test App",
            container_name="test-container",
            exec_command="/usr/bin/test",
        )

        installed = installer.install(entry)

        assert Path(installed.desktop_file).exists()
        content = Path(installed.desktop_file).read_text()
        assert "[Desktop Entry]" in content
        assert "Name=Test App" in content

    def test_install_sets_executable(self, tmp_path: Path) -> None:
        """Test that installed file is executable."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test",
            exec_command="/bin/test",
        )

        installed = installer.install(entry)

        mode = Path(installed.desktop_file).stat().st_mode
        assert mode & 0o111  # Check executable bits

    def test_install_tracks_entry(self, tmp_path: Path) -> None:
        """Test that install records the entry."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test-container",
            exec_command="/bin/test",
        )

        installer.install(entry)
        entries = installer.list_installed()

        assert len(entries) == 1
        assert entries[0].container_name == "test-container"
        assert entries[0].entry_name == "Test"

    def test_install_existing_raises_error(self, tmp_path: Path) -> None:
        """Test that installing existing entry raises error."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test",
            exec_command="/bin/test",
        )

        installer.install(entry)

        with pytest.raises(DesktopEntryExistsError):
            installer.install(entry)

    def test_install_overwrite_existing(self, tmp_path: Path) -> None:
        """Test that overwrite=True replaces existing entry."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test",
            exec_command="/bin/test",
        )

        installer.install(entry)

        entry2 = DesktopEntry(
            name="Test",
            container_name="test",
            exec_command="/bin/new-test",
        )

        installed = installer.install(entry2, overwrite=True)
        content = Path(installed.desktop_file).read_text()
        assert "new-test" in content

    def test_uninstall_removes_file(self, tmp_path: Path) -> None:
        """Test that uninstall removes the .desktop file."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test-container",
            exec_command="/bin/test",
        )

        installed = installer.install(entry)
        desktop_path = Path(installed.desktop_file)
        assert desktop_path.exists()

        removed = installer.uninstall("test-container", "Test")
        assert removed == 1
        assert not desktop_path.exists()

    def test_uninstall_removes_tracking(self, tmp_path: Path) -> None:
        """Test that uninstall removes entry from tracking."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test-container",
            exec_command="/bin/test",
        )

        installer.install(entry)
        installer.uninstall("test-container", "Test")

        entries = installer.list_installed("test-container")
        assert len(entries) == 0

    def test_uninstall_not_found(self, tmp_path: Path) -> None:
        """Test uninstall raises error when not found."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        with pytest.raises(DesktopEntryNotFoundError):
            installer.uninstall("nonexistent")

    def test_list_installed_filter_by_container(self, tmp_path: Path) -> None:
        """Test listing entries filtered by container."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry1 = DesktopEntry(
            name="App1",
            container_name="container1",
            exec_command="/bin/app1",
        )
        entry2 = DesktopEntry(
            name="App2",
            container_name="container2",
            exec_command="/bin/app2",
        )

        installer.install(entry1)
        installer.install(entry2)

        entries = installer.list_installed("container1")
        assert len(entries) == 1
        assert entries[0].container_name == "container1"

    def test_get_entry_content(self, tmp_path: Path) -> None:
        """Test getting content of installed entry."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        entry = DesktopEntry(
            name="Test",
            container_name="test-container",
            exec_command="/bin/test",
        )

        installer.install(entry)
        content = installer.get_entry_content("test-container")

        assert content is not None
        assert "[Desktop Entry]" in content

    def test_get_entry_content_not_found(self, tmp_path: Path) -> None:
        """Test getting content for nonexistent entry."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)
        installer._store_path = tmp_path / "desktop-entries.yaml"

        content = installer.get_entry_content("nonexistent")
        assert content is None

    def test_filename_generation(self, tmp_path: Path) -> None:
        """Test that filenames are properly sanitized."""
        config = DesktopConfig(applications_dir=str(tmp_path))
        installer = DesktopInstaller(config)

        entry = DesktopEntry(
            name="Test App with Spaces!",
            container_name="my-container",
            exec_command="/bin/test",
        )

        filename = installer._make_filename(entry)
        assert "gaol-" in filename
        assert " " not in filename
        assert "!" not in filename
        assert filename.endswith(".desktop")


class TestCLIDesktop:
    """Tests for desktop CLI commands."""

    def test_desktop_help(self) -> None:
        """Test desktop subcommand help."""
        from gaol.cli import app

        runner = CliRunner()
        result = runner.invoke(app, ["desktop", "--help"])
        assert result.exit_code == 0
        assert "Desktop integration" in result.output

    def test_desktop_create_help(self) -> None:
        """Test desktop create help."""
        from gaol.cli import app

        runner = CliRunner()
        result = runner.invoke(app, ["desktop", "create", "--help"])
        assert result.exit_code == 0
        assert "Create and install" in result.output

    def test_desktop_list_empty(self, tmp_path: Path) -> None:
        """Test listing with no entries."""
        from gaol.cli import app

        runner = CliRunner()

        with patch("gaol.desktop.installer.get_data_dir", return_value=tmp_path):
            result = runner.invoke(app, ["desktop", "list"])

        assert result.exit_code == 0
        assert "No desktop entries" in result.output

    def test_desktop_create_container_not_found(self) -> None:
        """Test create fails for nonexistent container."""
        from gaol.cli import app

        runner = CliRunner()

        with patch("gaol.cli.get_container_store") as mock_store:
            mock_store.return_value.get.return_value = None
            result = runner.invoke(
                app,
                ["desktop", "create", "nonexistent", "/bin/test"],
            )

        assert result.exit_code == 1
        assert "not found" in result.output

    def test_desktop_run_help(self) -> None:
        """Test desktop run help."""
        from gaol.cli import app

        runner = CliRunner()
        result = runner.invoke(app, ["desktop", "run", "--help"])
        assert result.exit_code == 0
        assert "GUI command" in result.output
