"""Tests for the persistent REPL tool."""

from __future__ import annotations

import time

from openjarvis.core.registry import ToolRegistry
from openjarvis.tools.repl import ReplTool


class TestReplSpec:
    def test_spec_name(self):
        tool = ReplTool()
        assert tool.spec.name == "repl"

    def test_spec_category(self):
        tool = ReplTool()
        assert tool.spec.category == "code"

    def test_spec_parameters(self):
        tool = ReplTool()
        params = tool.spec.parameters
        assert params["type"] == "object"
        assert "code" in params["properties"]
        assert "session_id" in params["properties"]
        assert "reset" in params["properties"]
        assert params["required"] == ["code"]

    def test_tool_id(self):
        tool = ReplTool()
        assert tool.tool_id == "repl"

    def test_to_openai_function(self):
        tool = ReplTool()
        fn = tool.to_openai_function()
        assert fn["type"] == "function"
        assert fn["function"]["name"] == "repl"


class TestReplBasicExecution:
    def test_expression(self):
        tool = ReplTool()
        result = tool.execute(code="2 + 2")
        assert result.success
        assert "4" in result.content

    def test_print(self):
        tool = ReplTool()
        result = tool.execute(code="print('hello')")
        assert result.success
        assert "hello" in result.content

    def test_multiline(self):
        tool = ReplTool()
        result = tool.execute(code="x = 5\nprint(x * 2)")
        assert result.success
        assert "10" in result.content

    def test_no_output(self):
        tool = ReplTool()
        result = tool.execute(code="x = 42")
        assert result.success
        assert result.content == "(no output)"


class TestReplStatePersistence:
    def test_variable_persists(self):
        tool = ReplTool()
        r1 = tool.execute(code="x = 42")
        sid = r1.metadata["session_id"]
        r2 = tool.execute(code="print(x)", session_id=sid)
        assert r2.success
        assert "42" in r2.content

    def test_function_persists(self):
        tool = ReplTool()
        r1 = tool.execute(code="def square(n): return n * n")
        sid = r1.metadata["session_id"]
        r2 = tool.execute(code="square(7)", session_id=sid)
        assert r2.success
        assert "49" in r2.content

    def test_import_persists(self):
        tool = ReplTool()
        r1 = tool.execute(code="import math")
        sid = r1.metadata["session_id"]
        r2 = tool.execute(code="math.sqrt(16)", session_id=sid)
        assert r2.success
        assert "4.0" in r2.content

    def test_class_persists(self):
        tool = ReplTool()
        r1 = tool.execute(code="class Foo:\n    val = 99")
        sid = r1.metadata["session_id"]
        r2 = tool.execute(code="Foo.val", session_id=sid)
        assert r2.success
        assert "99" in r2.content

    def test_mutable_state_across_calls(self):
        tool = ReplTool()
        r1 = tool.execute(code="data = []")
        sid = r1.metadata["session_id"]
        tool.execute(code="data.append(1)", session_id=sid)
        tool.execute(code="data.append(2)", session_id=sid)
        r4 = tool.execute(code="print(data)", session_id=sid)
        assert "[1, 2]" in r4.content


class TestReplSessionManagement:
    def test_auto_create_session(self):
        tool = ReplTool()
        result = tool.execute(code="x = 1")
        assert "session_id" in result.metadata
        assert result.metadata["session_id"]

    def test_explicit_session_id(self):
        tool = ReplTool()
        result = tool.execute(code="x = 1", session_id="my-session")
        assert result.metadata["session_id"] == "my-session"

    def test_session_isolation(self):
        tool = ReplTool()
        tool.execute(code="x = 'session_a'", session_id="a")
        tool.execute(code="x = 'session_b'", session_id="b")
        r3 = tool.execute(code="print(x)", session_id="a")
        assert "session_a" in r3.content

    def test_session_reset(self):
        tool = ReplTool()
        tool.execute(code="x = 42", session_id="s1")
        tool.execute(code="print('reset')", session_id="s1", reset=True)
        result = tool.execute(code="print(x)", session_id="s1")
        assert not result.success
        assert "NameError" in result.content

    def test_execution_count(self):
        tool = ReplTool()
        r1 = tool.execute(code="x = 1", session_id="cnt")
        assert r1.metadata["execution_count"] == 1
        r2 = tool.execute(code="x += 1", session_id="cnt")
        assert r2.metadata["execution_count"] == 2

    def test_lru_eviction(self):
        tool = ReplTool(max_sessions=2)
        tool.execute(code="x = 'first'", session_id="s1")
        time.sleep(0.01)
        tool.execute(code="x = 'second'", session_id="s2")
        time.sleep(0.01)
        # s1 is oldest; creating s3 should evict s1
        tool.execute(code="x = 'third'", session_id="s3")
        # s1 should be gone — new session with no x
        result = tool.execute(code="print(x)", session_id="s1")
        assert "NameError" in result.content


class TestReplErrorHandling:
    def test_syntax_error(self):
        tool = ReplTool()
        result = tool.execute(code="def foo(")
        assert not result.success
        assert "SyntaxError" in result.content

    def test_runtime_error(self):
        tool = ReplTool()
        result = tool.execute(code="1 / 0")
        assert not result.success
        assert "ZeroDivisionError" in result.content

    def test_name_error(self):
        tool = ReplTool()
        result = tool.execute(code="print(undefined)")
        assert not result.success
        assert "NameError" in result.content

    def test_error_doesnt_corrupt_session(self):
        tool = ReplTool()
        tool.execute(code="x = 10", session_id="err")
        tool.execute(code="1 / 0", session_id="err")  # Error
        r3 = tool.execute(code="print(x)", session_id="err")
        assert r3.success
        assert "10" in r3.content

    def test_no_code(self):
        tool = ReplTool()
        result = tool.execute(code="")
        assert not result.success
        assert "No code" in result.content


class TestReplSecurity:
    def test_blocked_os_system(self):
        tool = ReplTool()
        result = tool.execute(code="os.system('ls')")
        assert not result.success
        assert "Blocked" in result.content

    def test_blocked_subprocess(self):
        tool = ReplTool()
        result = tool.execute(code="import subprocess")
        assert not result.success
        assert "Blocked" in result.content

    def test_blocked_open(self):
        tool = ReplTool()
        result = tool.execute(code="f = open('file.txt')")
        assert not result.success
        assert "Blocked" in result.content

    def test_safe_imports_allowed(self):
        tool = ReplTool()
        result = tool.execute(code="import math\nprint(math.pi)")
        assert result.success
        assert "3.14" in result.content

    def test_safe_json_import(self):
        tool = ReplTool()
        result = tool.execute(code="import json\nprint(json.dumps({'a': 1}))")
        assert result.success
        assert '"a"' in result.content

    def test_unsafe_import_blocked(self):
        tool = ReplTool()
        result = tool.execute(code="import os")
        assert not result.success
        assert "not allowed" in result.content


class TestReplTimeout:
    def test_sleep_timeout(self):
        tool = ReplTool(timeout=1)
        result = tool.execute(code="import time\ntime.sleep(10)")
        assert not result.success
        assert "timed out" in result.content

    def test_infinite_loop_timeout(self):
        tool = ReplTool(timeout=1)
        result = tool.execute(code="while True: pass")
        assert not result.success
        assert "timed out" in result.content


class TestReplOutput:
    def test_truncation(self):
        tool = ReplTool(max_output=50)
        result = tool.execute(code="print('x' * 200)")
        assert "truncated" in result.content

    def test_expression_display(self):
        """Expressions should show their repr (REPL-like behavior)."""
        tool = ReplTool()
        result = tool.execute(code="2 + 2")
        assert "4" in result.content

    def test_string_expression_display(self):
        tool = ReplTool()
        result = tool.execute(code="'hello'")
        assert "hello" in result.content


class TestReplRegistration:
    def test_registered(self):
        # Re-register after conftest clears all registries
        ToolRegistry.register_value("repl", ReplTool)
        assert ToolRegistry.contains("repl")
