"""Tests for qremotex.execution.script_executor — ScriptExecutor."""

import base64

import pytest

from qremotex.execution.script_executor import ScriptExecutor


def _encode(script: str) -> str:
    """Base64 encode a script string."""
    return base64.b64encode(script.encode()).decode()


class TestScriptExecutor:

    @pytest.mark.asyncio
    async def test_simple_script(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t1",
            script_b64=_encode("result = 42"),
            timeout_sec=10,
        )
        assert result["success"] is True
        assert result["result"] == 42

    @pytest.mark.asyncio
    async def test_script_with_input_vars(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t2",
            script_b64=_encode("result = x + y"),
            timeout_sec=10,
            input_vars={"x": 10, "y": 20},
        )
        assert result["success"] is True
        assert result["result"] == 30

    @pytest.mark.asyncio
    async def test_script_syntax_error(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t3",
            script_b64=_encode("def bad syntax("),
            timeout_sec=10,
        )
        assert result["success"] is False
        assert "error" in result

    @pytest.mark.asyncio
    async def test_script_runtime_error(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t4",
            script_b64=_encode("result = 1 / 0"),
            timeout_sec=10,
        )
        assert result["success"] is False
        assert "error" in result

    @pytest.mark.asyncio
    async def test_script_timeout(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t5",
            script_b64=_encode("import time; time.sleep(30)"),
            timeout_sec=2,
        )
        assert result["success"] is False
        assert "timed out" in result.get("error", "").lower() or "timeout" in result.get("error", "").lower()

    @pytest.mark.asyncio
    async def test_invalid_base64(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t6",
            script_b64="!!!not-base64!!!",
            timeout_sec=10,
        )
        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_log_callback(self) -> None:
        logs: list[dict] = []

        async def on_log(entry: dict) -> None:
            logs.append(entry)

        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t7",
            script_b64=_encode("print('hello from script')"),
            timeout_sec=10,
            on_log=on_log,
        )
        assert result["success"] is True
        # Should have captured the print as stdout
        assert any("hello from script" in log.get("message", "") for log in logs)

    @pytest.mark.asyncio
    async def test_no_result_variable(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t8",
            script_b64=_encode("x = 99"),  # No 'result' variable
            timeout_sec=10,
        )
        assert result["success"] is True
        assert result["result"] is None  # No result captured

    @pytest.mark.asyncio
    async def test_complex_result(self) -> None:
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t9",
            script_b64=_encode("result = {'key': [1, 2, 3], 'nested': True}"),
            timeout_sec=10,
        )
        assert result["success"] is True
        assert result["result"] == {"key": [1, 2, 3], "nested": True}

    @pytest.mark.asyncio
    async def test_output_variable_found(self) -> None:
        """Scripts that set 'output' should have it captured as the result."""
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t10",
            script_b64=_encode("output = {'parsed': True, 'items': [1, 2]}"),
            timeout_sec=10,
        )
        assert result["success"] is True
        assert result["result"] == {"parsed": True, "items": [1, 2]}

    @pytest.mark.asyncio
    async def test_result_takes_priority_over_output(self) -> None:
        """'result' variable takes priority over 'output' (matching local execution)."""
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t11",
            script_b64=_encode("output = 'nope'; result = 'yes'"),
            timeout_sec=10,
        )
        assert result["success"] is True
        assert result["result"] == "yes"

    @pytest.mark.asyncio
    async def test_output_variable_config_ignored_gracefully(self) -> None:
        """output_variable config (e.g. 'code_op') is the context key, not
        the script variable name.  Scripts using 'output' should still work."""
        executor = ScriptExecutor()
        result = await executor.execute(
            request_id="t12",
            script_b64=_encode("output = {'status': 'ok'}"),
            timeout_sec=10,
            output_variable="code_op",  # context key, not in the script
        )
        assert result["success"] is True
        assert result["result"] == {"status": "ok"}
