from __future__ import annotations

import asyncio
import time
from collections import Counter

import pytest
from textual.app import App, ComposeResult
from textual.containers import ScrollableContainer
from textual.widgets import Button, Checkbox, Input, Select, Static

from pve_vm_setup.app import PveVmSetupApp
from pve_vm_setup.models.workflow import WorkflowState
from pve_vm_setup.screens.login import LoginView
from pve_vm_setup.screens.wizard import NO_DISK_SELECTED, AutoStartConfirmModal, WizardView
from pve_vm_setup.services.fake import FakeProxmoxService
from pve_vm_setup.settings import AppSettings


class LoginHarnessApp(App[None]):
    def compose(self) -> ComposeResult:
        yield LoginView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            FakeProxmoxService(),
        )


async def wait_for_wizard_ready(
    pilot,
    app: App[None],
    *,
    attempts: int = 12,
    delay: float = 0.1,
) -> None:
    for _ in range(attempts):
        await pilot.pause(delay)
        if (
            app.query_one("#general-vmid", Input).value == "123"
            and app.query_one("#general-node", Select).value == "fake-node-01"
            and app.query_one("#os-storage", Select).value == "cephfs"
        ):
            return
    raise AssertionError("Timed out waiting for wizard reference data to load.")


@pytest.mark.asyncio
async def test_login_view_authenticates_with_pilot() -> None:
    app = LoginHarnessApp()

    async with app.run_test() as pilot:
        await pilot.pause()
        assert str(app.query_one("#title", Static).renderable) == "Proxmox Login"
        assert app.focused is app.query_one("#username", Input)

        app.query_one("#username", Input).value = "junior"
        app.query_one("#password", Input).value = "secret"
        app.query_one("#connect", Button).press()
        await pilot.pause()

        assert "Authenticated as junior@pam." == str(app.query_one("#status", Static).renderable)


@pytest.mark.asyncio
async def test_main_app_mounts_wizard_only_after_login() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        await pilot.pause()
        assert app.query(LoginView)
        assert not app.query(WizardView)

        login = app.query_one(LoginView)
        login.post_message(LoginView.Authenticated("junior@pam", "pam"))
        await pilot.pause()
        await pilot.pause()
        await wait_for_wizard_ready(pilot, app)

        assert not app.query(LoginView)
        assert app.query(WizardView)
        assert app.focused is app.query_one("#general-name", Input)


@pytest.mark.asyncio
async def test_wizard_activation_focuses_first_editable_field() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        assert app.focused is app.query_one("#general-name", Input)


@pytest.mark.asyncio
async def test_wizard_initial_activation_does_not_duplicate_live_reference_loads() -> None:
    class CountingService(FakeProxmoxService):
        def __init__(self) -> None:
            super().__init__()
            self.calls: list[str] = []

        def load_nodes(self):
            self.calls.append("load_nodes")
            return super().load_nodes()

        def load_pools(self):
            self.calls.append("load_pools")
            return super().load_pools()

        def load_existing_tags(self):
            self.calls.append("load_existing_tags")
            return super().load_existing_tags()

        def load_next_vmid(self):
            self.calls.append("load_next_vmid")
            return super().load_next_vmid()

        def load_storages(self, node: str):
            self.calls.append(f"load_storages:{node}")
            return super().load_storages(node)

        def load_bridges(self, node: str):
            self.calls.append(f"load_bridges:{node}")
            return super().load_bridges(node)

        def load_isos(self, node: str, storage: str):
            self.calls.append(f"load_isos:{node}:{storage}")
            return super().load_isos(node, storage)

    service = CountingService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        for _ in range(6):
            await pilot.pause()

        assert Counter(service.calls) == Counter(
            [
                "load_nodes",
                "load_pools",
                "load_existing_tags",
                "load_next_vmid",
                "load_storages:fake-node-01",
                "load_bridges:fake-node-01",
                "load_isos:fake-node-01:cephfs",
            ]
        )


@pytest.mark.asyncio
async def test_wizard_initial_activation_loads_reference_data_concurrently() -> None:
    class SlowService(FakeProxmoxService):
        delay = 0.15

        def load_nodes(self):
            time.sleep(self.delay)
            return super().load_nodes()

        def load_pools(self):
            time.sleep(self.delay)
            return super().load_pools()

        def load_existing_tags(self):
            time.sleep(self.delay)
            return super().load_existing_tags()

        def load_next_vmid(self):
            time.sleep(self.delay)
            return super().load_next_vmid()

        def load_storages(self, node: str):
            time.sleep(self.delay)
            return super().load_storages(node)

        def load_bridges(self, node: str):
            time.sleep(self.delay)
            return super().load_bridges(node)

        def load_isos(self, node: str, storage: str):
            time.sleep(self.delay)
            return super().load_isos(node, storage)

    service = SlowService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)

        started_at = time.perf_counter()
        wizard.activate()

        await wait_for_wizard_ready(pilot, app)

        elapsed = time.perf_counter() - started_at

        assert elapsed < 1.0


@pytest.mark.asyncio
async def test_wizard_uses_scrollable_sections_with_border_titles() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        general_section = app.query_one("#general-section", ScrollableContainer)
        os_section = app.query_one("#os-section", ScrollableContainer)

        assert str(general_section.border_title).strip() == "General"
        assert str(os_section.border_title).strip() == "Operating System"


@pytest.mark.asyncio
async def test_wizard_hides_os_fields_based_on_media_choice() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        assert app.query_one("#os-storage", Select).display is True
        assert app.query_one("#os-iso", Select).display is True
        assert app.query_one("#os-physical-drive", Input).display is False

        app.query_one("#os-media-choice", Select).value = "physical"
        await pilot.pause()

        assert app.query_one("#os-storage", Select).display is False
        assert app.query_one("#os-iso", Select).display is False
        assert app.query_one("#os-physical-drive", Input).display is True

        app.query_one("#os-media-choice", Select).value = "none"
        await pilot.pause()

        assert app.query_one("#os-storage", Select).display is False
        assert app.query_one("#os-iso", Select).display is False
        assert app.query_one("#os-physical-drive", Input).display is False


@pytest.mark.asyncio
async def test_wizard_hides_dependent_system_memory_and_network_fields() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        assert app.query_one("#system-efi-storage", Select).display is True
        assert app.query_one("#system-pre-enroll", Checkbox).display is True

        app.query_one("#system-add-efi", Checkbox).value = False
        await pilot.pause()

        assert app.query_one("#system-efi-storage", Select).display is False
        assert app.query_one("#system-pre-enroll", Checkbox).display is False

        app.query_one("#system-tpm", Checkbox).value = True
        await pilot.pause()

        assert app.query_one("#system-efi-storage", Select).display is True
        assert app.query_one("#system-pre-enroll", Checkbox).display is False

        assert app.query_one("#memory-min-size", Input).display is True
        assert app.query_one("#memory-ksm", Checkbox).display is True

        app.query_one("#memory-ballooning", Checkbox).value = False
        await pilot.pause()

        assert app.query_one("#memory-min-size", Input).display is False
        assert app.query_one("#memory-ksm", Checkbox).display is False

        assert app.query_one("#network-bridge", Select).display is True
        assert app.query_one("#network-rate", Input).display is True

        app.query_one("#network-none", Checkbox).value = True
        await pilot.pause()

        assert app.query_one("#network-bridge", Select).display is False
        assert app.query_one("#network-rate", Input).display is False


@pytest.mark.asyncio
async def test_wizard_tag_rows_keep_input_and_button_visible() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        assert app.query_one("#general-tag-input", Input).display is True
        assert app.query_one("#general-tag-add", Button).display is True
        assert app.query_one("#general-tag-existing", Select).display is True
        assert app.query_one("#general-tag-use", Button).display is True


@pytest.mark.asyncio
async def test_wizard_add_tag_button_updates_current_tags() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        app.query_one("#general-tag-input", Input).value = "alpha"
        app.query_one("#general-tag-add", Button).press()
        await pilot.pause()

        assert wizard._workflow.config.general.tags == ["alpha"]
        current_tags = app.query_one("#general-tag-current", Select)
        assert current_tags.display is True


@pytest.mark.asyncio
async def test_wizard_hiding_select_collapses_open_overlay() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        storage = app.query_one("#os-storage", Select)
        storage.expanded = True
        await pilot.pause()
        assert storage.expanded is True

        app.query_one("#os-media-choice", Select).value = "physical"
        await pilot.pause()

        assert storage.display is False
        assert storage.expanded is False


@pytest.mark.asyncio
async def test_disk_toolbar_buttons_render_left_of_disk_selector() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        wizard._workflow.current_step_index = 3
        wizard._show_step()
        await pilot.pause()

        add_button = app.query_one("#disks-add", Button)
        remove_button = app.query_one("#disks-remove", Button)
        selector = app.query_one("#disks-select", Select)

        assert add_button.region.x < remove_button.region.x
        assert selector.region.x == add_button.region.x
        assert selector.region.width > remove_button.region.width * 3


@pytest.mark.asyncio
async def test_disk_selector_switches_between_configured_disks_without_blank_option() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        wizard._workflow.current_step_index = 3
        wizard._show_step()
        await pilot.pause()

        app.query_one("#disks-add", Button).press()
        await pilot.pause()

        selector = app.query_one("#disks-select", Select)
        option_values = [value for _, value in selector._options]

        assert NO_DISK_SELECTED not in option_values
        assert selector.disabled is False
        assert selector.value == "1"

        await asyncio.wait_for(pilot.click("#disks-select"), timeout=2)
        await pilot.pause()
        assert selector.expanded is True

        await asyncio.wait_for(pilot.press("up"), timeout=2)
        await pilot.pause()
        await asyncio.wait_for(pilot.press("enter"), timeout=2)
        await pilot.pause()

        assert selector.expanded is False
        assert selector.value == "0"
        assert wizard._selected_disk_index == 0
        assert app.focused is selector


@pytest.mark.asyncio
async def test_confirm_step_replaces_create_with_exit_after_success() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        wizard._workflow.current_step_index = 7
        wizard._workflow.submission.phase = "success"
        wizard._workflow.submission.message = "VM 123 created."
        wizard._show_step()
        await pilot.pause()

        create_button = app.query_one("#wizard-create", Button)
        assert str(create_button.label) == "Exit"
        assert app.focused is create_button

        exited: list[bool] = []
        app.exit = lambda *args, **kwargs: exited.append(True)  # type: ignore[method-assign]
        create_button.press()
        await pilot.pause()

        assert exited == [True]


@pytest.mark.asyncio
async def test_confirm_step_asks_whether_to_start_vm_before_submitting() -> None:
    service = FakeProxmoxService()
    app = PveVmSetupApp(
        AppSettings.from_env({}, load_dotenv_file=False),
        service=service,
    )

    async with app.run_test() as pilot:
        app.query_one(LoginView).remove()
        wizard = WizardView(
            AppSettings.from_env({}, load_dotenv_file=False),
            WorkflowState(),
            service,
        )
        await app.query_one("#app-body").mount(wizard)
        wizard.activate()
        await wait_for_wizard_ready(pilot, app)

        app.query_one("#general-name", Input).value = "demo"
        wizard._workflow.current_step_index = 7
        wizard._show_step()
        await pilot.pause()

        app.query_one("#wizard-create", Button).press()
        await pilot.pause()

        assert isinstance(app.screen_stack[-1], AutoStartConfirmModal)
        assert service.created_vms == []

        app.query_one("#auto-start-no", Button).press()

        for _ in range(20):
            await pilot.pause(0.05)
            if service.created_vms:
                break

        assert len(service.created_vms) == 1
        assert service.start_after_create_requests == [False]
