"""Tests for the web_navigate skill."""

import os
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from fliiq.runtime.package_data import bundled_skills_dir
from fliiq.runtime.skills.base import SkillBase

SKILLS_DIR = str(bundled_skills_dir())


def _load_skill() -> SkillBase:
    return SkillBase(os.path.join(SKILLS_DIR, "web_navigate"))


def test_web_navigate_schema():
    skill = _load_skill()
    schema = skill.schema()
    assert schema["name"] == "web_navigate"
    assert "task" in schema["parameters"]["properties"]
    assert "url" in schema["parameters"]["properties"]
    assert "browser_visible" in schema["parameters"]["properties"]
    assert "max_steps" in schema["parameters"]["properties"]
    assert schema["parameters"]["required"] == ["task"]


async def test_web_navigate_missing_browser_use():
    """Raises ValueError with install instructions when browser-use not installed."""
    from fliiq.data.skills.core.web_navigate.main import handler

    with patch(
        "fliiq.data.skills.core.web_navigate.main.handler",
        side_effect=handler,
    ):
        # Make the import inside handler() fail
        with patch.dict(
            "sys.modules",
            {"fliiq.runtime.browser.engine": None, "fliiq.runtime.browser": None},
        ):
            with pytest.raises((ValueError, ImportError)):
                await handler({"task": "test"})


async def test_web_navigate_forwards_params():
    """Handler forwards task, url, browser_visible, max_steps to run_browser_task."""
    skill = _load_skill()
    expected_result = {
        "success": True,
        "result": "Found phone number: 555-1234",
        "screenshots": [],
        "urls_visited": ["https://example.com"],
        "steps_taken": 3,
        "error": None,
    }

    with patch("fliiq.runtime.browser.engine.run_browser_task", new_callable=AsyncMock) as mock_run:
        mock_run.return_value = expected_result
        result = await skill.execute({
            "task": "find the phone number",
            "url": "https://example.com",
            "browser_visible": True,
            "max_steps": 10,
        })

    mock_run.assert_called_once_with(
        task="find the phone number",
        url="https://example.com",
        browser_visible=True,
        max_steps=10,
    )
    assert result == expected_result


async def test_web_navigate_default_params():
    """Handler uses defaults for optional params."""
    skill = _load_skill()
    expected_result = {
        "success": True,
        "result": "done",
        "screenshots": [],
        "urls_visited": [],
        "steps_taken": 1,
        "error": None,
    }

    with patch("fliiq.runtime.browser.engine.run_browser_task", new_callable=AsyncMock) as mock_run:
        mock_run.return_value = expected_result
        await skill.execute({"task": "test task"})

    mock_run.assert_called_once_with(
        task="test task",
        url=None,
        browser_visible=False,
        max_steps=50,
    )


async def test_web_navigate_result_shape():
    """Verify the result dict has all expected keys."""
    expected_keys = {"success", "result", "screenshots", "urls_visited", "steps_taken", "error"}

    with patch("fliiq.runtime.browser.engine.run_browser_task", new_callable=AsyncMock) as mock_run:
        mock_run.return_value = {
            "success": True,
            "result": "test",
            "screenshots": ["/tmp/step_0.png"],
            "urls_visited": ["https://example.com"],
            "steps_taken": 5,
            "error": None,
        }
        skill = _load_skill()
        result = await skill.execute({"task": "test"})

    assert set(result.keys()) == expected_keys


# --- engine.py unit tests (mocked browser-use) ---


def _mock_browser_use():
    """Create a mock browser_use module and inject it into sys.modules."""
    import sys

    mock_bu = MagicMock()
    mock_bu.BrowserConfig = MagicMock
    # Ensure Browser() returns something with async close
    mock_browser_instance = MagicMock()
    mock_browser_instance.close = AsyncMock()
    mock_bu.Browser.return_value = mock_browser_instance
    # Ensure Controller() works
    mock_bu.Controller.return_value = MagicMock()

    sys.modules["browser_use"] = mock_bu
    return mock_bu, mock_browser_instance


async def test_run_browser_task_success():
    """run_browser_task creates agent, runs it, returns structured result."""
    import importlib
    import sys

    mock_bu, mock_browser = _mock_browser_use()

    mock_history = MagicMock()
    mock_history.is_done.return_value = True
    mock_history.final_result.return_value = "Appointment booked"
    mock_history.screenshot_paths.return_value = []
    mock_history.urls.return_value = ["https://example.com"]
    mock_history.number_of_steps.return_value = 8

    mock_agent = MagicMock()
    mock_agent.run = AsyncMock(return_value=mock_history)
    mock_bu.Agent.return_value = mock_agent

    # Reload engine so it picks up the mocked browser_use
    import fliiq.runtime.browser.engine as engine_mod

    importlib.reload(engine_mod)

    with patch.object(engine_mod, "_create_browser_llm"), patch.object(engine_mod, "_build_controller"):
        result = await engine_mod.run_browser_task(
            task="book an oil change",
            url="https://example.com",
            browser_visible=False,
            max_steps=20,
        )

    assert result["success"] is True
    assert result["result"] == "Appointment booked"
    assert result["urls_visited"] == ["https://example.com"]
    assert result["steps_taken"] == 8
    assert result["error"] is None


async def test_run_browser_task_failure():
    """run_browser_task returns error dict on exception."""
    import importlib

    mock_bu, mock_browser = _mock_browser_use()
    mock_bu.Agent.side_effect = RuntimeError("Chromium not found")

    import fliiq.runtime.browser.engine as engine_mod

    importlib.reload(engine_mod)

    with patch.object(engine_mod, "_create_browser_llm"), patch.object(engine_mod, "_build_controller"):
        result = await engine_mod.run_browser_task(task="test")

    assert result["success"] is False
    assert "Chromium not found" in result["error"]
    assert result["steps_taken"] == 0


async def test_run_browser_task_browser_always_closed():
    """Browser is closed even when agent.run() raises."""
    import importlib

    mock_bu, mock_browser = _mock_browser_use()

    mock_agent = MagicMock()
    mock_agent.run = AsyncMock(side_effect=Exception("timeout"))
    mock_bu.Agent.return_value = mock_agent
    mock_bu.Agent.side_effect = None

    import fliiq.runtime.browser.engine as engine_mod

    importlib.reload(engine_mod)

    with patch.object(engine_mod, "_create_browser_llm"), patch.object(engine_mod, "_build_controller"):
        result = await engine_mod.run_browser_task(task="test")

    assert result["success"] is False
    assert "timeout" in result["error"]
    mock_browser.close.assert_awaited()
