"""Textual pilot tests for TUI screens.

These tests render the actual TUI screens headlessly and exercise wizard
navigation to catch runtime errors (bad mount calls, missing widgets, etc.)
that unit tests on dataclasses alone cannot detect.
"""

from __future__ import annotations

import os
import stat
import textwrap
from pathlib import Path
from unittest.mock import AsyncMock, patch

import pytest
import yaml

from textual.widgets import OptionList, Button, Input, RadioSet, SelectionList

from tui.app import LinuxAutomationApp
from tui.services.script_runner import ScriptRunner


# ── helpers ───────────────────────────────────────────────────────

# Patch _find_packer_vars globally so the app starts at MainMenuScreen
# instead of SetupWizardScreen in tests that don't need the setup wizard.
_PATCH_CREDS = patch.object(
    ScriptRunner, "_find_packer_vars", return_value=Path("/fake/creds")
)


def _make_app(tmp_config_dir: Path) -> LinuxAutomationApp:
    """Create an app instance with a temporary config directory."""
    return LinuxAutomationApp(
        config_dir=tmp_config_dir,
        user_config_dir="",
    )


# ── Main menu ─────────────────────────────────────────────────────


class TestMainMenu:
    """Test the main menu screen loads and navigates."""

    @pytest.mark.asyncio
    async def test_main_menu_renders(self, tmp_config_dir: Path) -> None:
        """Main menu should render without errors."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                option_list = app.screen.query_one("#main-option-list", OptionList)
                assert option_list is not None

    @pytest.mark.asyncio
    async def test_navigate_to_build(self, tmp_config_dir: Path) -> None:
        """Pressing 'b' should push the TemplateBuildScreen."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("b")
                await pilot.pause()
                from tui.screens.template_build import TemplateBuildScreen
                assert isinstance(app.screen, TemplateBuildScreen)

    @pytest.mark.asyncio
    async def test_navigate_to_deploy(self, tmp_config_dir: Path) -> None:
        """Pressing 'd' should push the VMDeployScreen."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_templates",
            new_callable=AsyncMock,
            return_value=[],
        ):
            async with app.run_test() as pilot:
                await pilot.press("d")
                await pilot.pause()
                from tui.screens.vm_deploy import VMDeployScreen
                assert isinstance(app.screen, VMDeployScreen)

    @pytest.mark.asyncio
    async def test_navigate_to_manage(self, tmp_config_dir: Path) -> None:
        """Pressing 'm' should push the VMManageScreen."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_all_vms",
            new_callable=AsyncMock,
            return_value=[],
        ):
            async with app.run_test() as pilot:
                await pilot.press("m")
                await pilot.pause()
                from tui.screens.vm_manage import VMManageScreen
                assert isinstance(app.screen, VMManageScreen)


# ── Template build wizard ─────────────────────────────────────────


class TestTemplateBuildWizard:
    """Test the template build wizard step-by-step navigation."""

    @pytest.mark.asyncio
    async def test_step1_distribution_renders(self, tmp_config_dir: Path) -> None:
        """Step 1 should show the distribution option list."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("b")
                await pilot.pause()

                option_list = app.screen.query_one("#distro-list", OptionList)
                assert option_list is not None

    @pytest.mark.asyncio
    async def test_full_wizard_walkthrough(self, tmp_config_dir: Path) -> None:
        """Walk through all 7 steps without crashing."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("b")
                await pilot.pause()

                from tui.screens.template_build import TemplateBuildScreen
                screen = app.screen
                assert isinstance(screen, TemplateBuildScreen)

                # Step 1: Select distribution (ubuntu)
                distro_list = screen.query_one("#distro-list", OptionList)
                distro_list.action_select()
                await pilot.pause()

                # Step 2: Select version (first available)
                assert screen.step == 1
                version_list = screen.query_one("#version-list", OptionList)
                version_list.action_select()
                await pilot.pause()

                # Step 3: Package profile — accept default and click Next
                assert screen.step == 2
                next_btn = screen.query_one("#next", Button)
                await pilot.click(next_btn)
                await pilot.pause()

                # Step 4: Network config — accept default and click Next
                assert screen.step == 3
                await pilot.click(next_btn)
                await pilot.pause()

                # Step 5: Template config — accept defaults and click Next
                assert screen.step == 4
                await pilot.click(next_btn)
                await pilot.pause()

                # Step 6: Credentials — fill in and click Next
                assert screen.step == 5
                password_input = screen.query_one("#cloudinit-password", Input)
                confirm_input = screen.query_one("#cloudinit-password-confirm", Input)
                password_input.value = "testpass"
                confirm_input.value = "testpass"
                await pilot.click(next_btn)
                await pilot.pause()

                # Step 7: Confirm — should render summary without crashing
                assert screen.step == 6

    @pytest.mark.asyncio
    async def test_back_button_navigates(self, tmp_config_dir: Path) -> None:
        """Back button should go to previous step."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("b")
                await pilot.pause()

                from tui.screens.template_build import TemplateBuildScreen
                screen = app.screen
                assert isinstance(screen, TemplateBuildScreen)

                # Select distro to advance to step 1
                distro_list = screen.query_one("#distro-list", OptionList)
                distro_list.action_select()
                await pilot.pause()
                assert screen.step == 1

                # Press back
                back_btn = screen.query_one("#back", Button)
                await pilot.click(back_btn)
                await pilot.pause()
                assert screen.step == 0

    @pytest.mark.asyncio
    async def test_escape_returns_to_menu(self, tmp_config_dir: Path) -> None:
        """Escape should return to main menu."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("b")
                await pilot.pause()

                from tui.screens.template_build import TemplateBuildScreen
                assert isinstance(app.screen, TemplateBuildScreen)

                await pilot.press("escape")
                await pilot.pause()

                from tui.screens.main_menu import MainMenuScreen
                assert isinstance(app.screen, MainMenuScreen)

    @pytest.mark.asyncio
    async def test_confirm_step_renders_summary(self, tmp_config_dir: Path) -> None:
        """The confirm step should render all summary fields."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("b")
                await pilot.pause()

                from tui.screens.template_build import TemplateBuildScreen
                screen = app.screen
                assert isinstance(screen, TemplateBuildScreen)

                # Pre-populate state to skip to confirm step
                screen.state.distro = "ubuntu"
                screen.state.version = "24.04"
                screen.state.profile = "base"
                screen.state.network_mode = "dhcp"
                screen.state.template_name = "ubuntu-24.04-base"
                screen.state.vm_cores = 2
                screen.state.vm_memory = 2048
                screen.state.vm_disk_size = "10G"
                screen.state.cloudinit_user = "admin"
                screen.state.cloudinit_password = "secret"

                # Jump directly to confirm step
                screen._show_step(6)
                await pilot.pause()

                assert screen.step == 6
                summary = screen.query_one(".summary-panel")
                assert summary is not None


# ── VM deploy wizard ──────────────────────────────────────────────


class TestVMDeployWizard:
    """Test the VM deployment wizard."""

    @pytest.mark.asyncio
    async def test_deploy_screen_no_templates(self, tmp_config_dir: Path) -> None:
        """Deploy screen should handle no templates gracefully."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_templates",
            new_callable=AsyncMock,
            return_value=[],
        ):
            async with app.run_test() as pilot:
                await pilot.press("d")
                await pilot.pause()
                await pilot.pause()

                from tui.screens.vm_deploy import VMDeployScreen
                assert isinstance(app.screen, VMDeployScreen)

    @pytest.mark.asyncio
    async def test_deploy_wizard_with_templates(self, tmp_config_dir: Path) -> None:
        """Deploy screen with mock templates should render template list."""
        mock_templates = [
            {"vmid": 9200, "name": "ubuntu-24.04-base", "status": "stopped", "template": 1},
            {"vmid": 9201, "name": "rocky-9-base", "status": "stopped", "template": 1},
        ]
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_templates",
            new_callable=AsyncMock,
            return_value=mock_templates,
        ):
            async with app.run_test() as pilot:
                await pilot.press("d")
                await pilot.pause()
                await pilot.pause()

                template_list = app.screen.query_one("#template-list", OptionList)
                assert template_list is not None

    @pytest.mark.asyncio
    async def test_deploy_confirm_renders(self, tmp_config_dir: Path) -> None:
        """Deploy confirm step should render summary without crashing."""
        mock_templates = [
            {"vmid": 9200, "name": "ubuntu-24.04-base", "status": "stopped", "template": 1},
        ]
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_templates",
            new_callable=AsyncMock,
            return_value=mock_templates,
        ):
            async with app.run_test() as pilot:
                await pilot.press("d")
                await pilot.pause()
                await pilot.pause()

                from tui.screens.vm_deploy import VMDeployScreen
                screen = app.screen
                assert isinstance(screen, VMDeployScreen)

                screen.state.template_id = 9200
                screen.state.template_name = "ubuntu-24.04-base"
                screen.state.vm_name = "test-vm"
                screen.state.vm_cores = 2
                screen.state.vm_memory = 2048
                screen.state.vm_disk_size = "20G"
                screen.state.network_mode = "dhcp"
                screen.state.clone_type = "linked"
                screen.state.playbooks = ["base-config"]

                screen._show_step(4)
                await pilot.pause()

                assert screen.step == 4
                summary = screen.query_one(".summary-panel")
                assert summary is not None


# ── VM manage screen ──────────────────────────────────────────────


class TestVMManageScreen:
    """Test the VM management screen."""

    @pytest.mark.asyncio
    async def test_manage_screen_no_vms(self, tmp_config_dir: Path) -> None:
        """Manage screen should handle no VMs gracefully."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_all_vms",
            new_callable=AsyncMock,
            return_value=[],
        ):
            async with app.run_test() as pilot:
                await pilot.press("m")
                await pilot.pause()
                await pilot.pause()

                from tui.screens.vm_manage import VMManageScreen
                assert isinstance(app.screen, VMManageScreen)

    @pytest.mark.asyncio
    async def test_manage_screen_with_vms(self, tmp_config_dir: Path) -> None:
        """Manage screen with mock VMs should render selection list."""
        mock_vms = [
            {"vmid": 100, "name": "test-vm", "status": "running", "template": 0},
            {"vmid": 9200, "name": "ubuntu-template", "status": "stopped", "template": 1},
        ]
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_all_vms",
            new_callable=AsyncMock,
            return_value=mock_vms,
        ):
            async with app.run_test() as pilot:
                await pilot.press("m")
                await pilot.pause()
                await pilot.pause()

                selection_list = app.screen.query_one("#vm-selection", SelectionList)
                assert selection_list is not None

    @pytest.mark.asyncio
    async def test_manage_escape_returns(self, tmp_config_dir: Path) -> None:
        """Escape from manage screen should return to main menu."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS, patch(
            "tui.services.proxmox.ProxmoxService.get_all_vms",
            new_callable=AsyncMock,
            return_value=[],
        ):
            async with app.run_test() as pilot:
                await pilot.press("m")
                await pilot.pause()

                await pilot.press("escape")
                await pilot.pause()

                from tui.screens.main_menu import MainMenuScreen
                assert isinstance(app.screen, MainMenuScreen)


# ── Dialog widgets ────────────────────────────────────────────────


class TestDialogs:
    """Test dialog widget rendering."""

    @pytest.mark.asyncio
    async def test_message_dialog_renders(self, tmp_config_dir: Path) -> None:
        """MessageDialog should render and dismiss on button press."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.press("a")
                await pilot.pause()

                ok_btn = app.screen.query_one("#ok", Button)
                assert ok_btn is not None

                await pilot.click(ok_btn)
                await pilot.pause()

                from tui.screens.main_menu import MainMenuScreen
                assert isinstance(app.screen, MainMenuScreen)


# ── Setup wizard ─────────────────────────────────────────────────


class TestSetupWizard:
    """Test the first-run setup wizard."""

    @pytest.mark.asyncio
    async def test_setup_wizard_shown_when_no_creds(self, tmp_config_dir: Path) -> None:
        """Setup wizard should be pushed when packer vars are missing."""
        app = _make_app(tmp_config_dir)
        with patch.object(ScriptRunner, "_find_packer_vars", return_value=None):
            async with app.run_test() as pilot:
                await pilot.pause()
                from tui.screens.setup_wizard import SetupWizardScreen
                assert isinstance(app.screen, SetupWizardScreen)

    @pytest.mark.asyncio
    async def test_main_menu_shown_when_creds_exist(self, tmp_config_dir: Path) -> None:
        """Main menu should be pushed when packer vars exist."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.pause()
                from tui.screens.main_menu import MainMenuScreen
                assert isinstance(app.screen, MainMenuScreen)

    @pytest.mark.asyncio
    async def test_setup_wizard_renders_welcome(self, tmp_config_dir: Path) -> None:
        """Step 0 (welcome) should render without errors."""
        app = _make_app(tmp_config_dir)
        with patch.object(ScriptRunner, "_find_packer_vars", return_value=None):
            async with app.run_test() as pilot:
                await pilot.pause()
                from tui.screens.setup_wizard import SetupWizardScreen
                screen = app.screen
                assert isinstance(screen, SetupWizardScreen)
                assert screen.step == 0

    @pytest.mark.asyncio
    async def test_setup_wizard_full_walkthrough(self, tmp_config_dir: Path) -> None:
        """Walk through all 5 steps and reach confirm."""
        app = _make_app(tmp_config_dir)
        with patch.object(ScriptRunner, "_find_packer_vars", return_value=None):
            async with app.run_test() as pilot:
                await pilot.pause()

                from tui.screens.setup_wizard import SetupWizardScreen
                screen = app.screen
                assert isinstance(screen, SetupWizardScreen)

                next_btn = screen.query_one("#next", Button)

                # Step 0: Welcome — click Next
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 1

                # Step 1: Connection — accept defaults, click Next
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 2

                # Step 2: Auth — fill in token fields
                screen.query_one("#token-id", Input).value = "test@pve!mytoken"
                screen.query_one("#token-secret", Input).value = "secret-uuid"
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 3

                # Step 3: Storage — accept defaults
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 4

                # Step 4: Confirm — should show summary
                summary = screen.query_one(".summary-panel")
                assert summary is not None

    @pytest.mark.asyncio
    async def test_setup_wizard_back_navigation(self, tmp_config_dir: Path) -> None:
        """Back button should navigate to previous step."""
        app = _make_app(tmp_config_dir)
        with patch.object(ScriptRunner, "_find_packer_vars", return_value=None):
            async with app.run_test() as pilot:
                await pilot.pause()

                screen = app.screen
                next_btn = screen.query_one("#next", Button)
                back_btn = screen.query_one("#back", Button)

                # Advance to step 1
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 1

                # Go back to step 0
                await pilot.click(back_btn)
                await pilot.pause()
                assert screen.step == 0

    @pytest.mark.asyncio
    async def test_setup_wizard_validation_empty_url(self, tmp_config_dir: Path) -> None:
        """Empty URL should fail validation and stay on step 1."""
        app = _make_app(tmp_config_dir)
        with patch.object(ScriptRunner, "_find_packer_vars", return_value=None):
            async with app.run_test() as pilot:
                await pilot.pause()

                screen = app.screen
                next_btn = screen.query_one("#next", Button)

                # Advance to step 1
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 1

                # Clear URL and try to advance
                screen.query_one("#proxmox-url", Input).value = ""
                await pilot.click(next_btn)
                await pilot.pause()
                assert screen.step == 1  # Should stay on step 1

    @pytest.mark.asyncio
    async def test_setup_from_main_menu(self, tmp_config_dir: Path) -> None:
        """Setup from menu should push SetupWizardScreen with from_menu=True."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.pause()
                await pilot.press("s")
                await pilot.pause()

                from tui.screens.setup_wizard import SetupWizardScreen
                screen = app.screen
                assert isinstance(screen, SetupWizardScreen)
                assert screen.from_menu is True

    @pytest.mark.asyncio
    async def test_setup_escape_from_menu_returns(self, tmp_config_dir: Path) -> None:
        """Escape from setup wizard (from menu) should return to menu."""
        app = _make_app(tmp_config_dir)
        with _PATCH_CREDS:
            async with app.run_test() as pilot:
                await pilot.pause()
                await pilot.press("s")
                await pilot.pause()

                await pilot.press("escape")
                await pilot.pause()

                from tui.screens.main_menu import MainMenuScreen
                assert isinstance(app.screen, MainMenuScreen)


    @pytest.mark.asyncio(loop_scope="function")
    async def test_auth_method_switch_no_crash(self) -> None:
        """Switching auth method radio should not crash with DuplicateIds."""
        app = LinuxAutomationApp()
        async with app.run_test(size=(120, 40)) as pilot:
            await pilot.pause()

            # Should start on SetupWizardScreen
            from tui.screens.setup_wizard import SetupWizardScreen
            assert isinstance(app.screen, SetupWizardScreen)

            # Navigate: Welcome → Connection → Auth (step 2)
            await pilot.click("#next")
            await pilot.pause()
            await pilot.click("#next")
            await pilot.pause()

            # We're on auth step with token selected by default.
            # Click the "Username / Password" radio button to switch.
            radio = app.screen.query_one("#auth-method-radio", RadioSet)
            buttons = list(radio.query("RadioButton"))
            # Click the password radio button (second one)
            await pilot.click(f"#{buttons[1].id}")
            await pilot.pause()

            # Should still be on auth step, not crashed
            assert app.screen.step == 2
            # Should now show username/password fields
            app.screen.query_one("#username", Input)
            app.screen.query_one("#password-input", Input)

            # Switch back to token
            await pilot.click(f"#{buttons[0].id}")
            await pilot.pause()
            assert app.screen.step == 2
            app.screen.query_one("#token-id", Input)
            app.screen.query_one("#token-secret", Input)


# ── write_packer_vars unit tests ─────────────────────────────────


class TestWritePackerVars:
    """Test the config file writing logic."""

    def test_writes_token_auth(self, tmp_path: Path) -> None:
        """Written file should contain token auth fields."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState(
            proxmox_url="https://pve:8006/api2/json",
            proxmox_node="pve",
            auth_method="token",
            token_id="admin@pve!mytoken",
            token_secret="uuid-secret",
        )
        output = tmp_path / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)

        content = output.read_text()
        assert 'proxmox_url  = "https://pve:8006/api2/json"' in content
        assert 'proxmox_node = "pve"' in content
        assert 'proxmox_token_id     = "admin@pve!mytoken"' in content
        assert 'proxmox_token_secret = "uuid-secret"' in content
        assert "proxmox_username" not in content
        assert "proxmox_password" not in content

    def test_writes_password_auth(self, tmp_path: Path) -> None:
        """Written file should contain username/password fields."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState(
            auth_method="password",
            username="root@pam",
            password="secret123",
        )
        output = tmp_path / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)

        content = output.read_text()
        assert 'proxmox_username = "root@pam"' in content
        assert 'proxmox_password = "secret123"' in content
        assert "proxmox_token_id" not in content

    def test_file_permissions(self, tmp_path: Path) -> None:
        """Written file should have 0600 permissions."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState()
        output = tmp_path / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)

        mode = os.stat(output).st_mode
        assert mode & 0o777 == 0o600

    def test_creates_parent_directories(self, tmp_path: Path) -> None:
        """Should create parent directories if they don't exist."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState()
        output = tmp_path / "nested" / "dir" / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)
        assert output.exists()

    def test_hcl_escapes_quotes(self, tmp_path: Path) -> None:
        """Double quotes in values should be escaped."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState(proxmox_node='node"with"quotes')
        output = tmp_path / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)

        content = output.read_text()
        assert 'proxmox_node = "node\\"with\\"quotes"' in content

    def test_skip_tls_verify_true(self, tmp_path: Path) -> None:
        """skip_tls_verify=True should write 'true' in HCL."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState(skip_tls_verify=True)
        output = tmp_path / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)

        content = output.read_text()
        assert "proxmox_skip_tls_verify = true" in content

    def test_skip_tls_verify_false(self, tmp_path: Path) -> None:
        """skip_tls_verify=False should write 'false' in HCL."""
        from tui.screens.setup_wizard import SetupState, write_packer_vars

        state = SetupState(skip_tls_verify=False)
        output = tmp_path / "proxmox.auto.pkrvars.hcl"
        write_packer_vars(state, output)

        content = output.read_text()
        assert "proxmox_skip_tls_verify = false" in content
