"""Tests for speq core functionality."""

import pytest
from speq.core import _body_is_stub, _clean_code, _spec_hash, _build_prompt


# ---------------------------------------------------------------------------
# _body_is_stub tests
# ---------------------------------------------------------------------------

class TestBodyIsStub:

    def test_ellipsis_body(self):
        def f():
            """docs"""
            ...
        assert _body_is_stub(f) is True

    def test_pass_body(self):
        def f():
            """docs"""
            pass
        assert _body_is_stub(f) is True

    def test_real_body(self):
        def f():
            """docs"""
            return 42
        assert _body_is_stub(f) is False

    def test_no_docstring_ellipsis(self):
        def f():
            ...
        assert _body_is_stub(f) is True

    def test_multiline_docstring_ellipsis(self):
        def f():
            """
            This is a long docstring
            that spans multiple lines.
            """
            ...
        assert _body_is_stub(f) is True

    def test_multiline_signature_ellipsis(self):
        def f(a: int, b: int,
              c: int) -> int:
            """docs"""
            ...
        assert _body_is_stub(f) is True

    def test_body_with_assignment(self):
        def f():
            """docs"""
            x = 1
            return x
        assert _body_is_stub(f) is False


# ---------------------------------------------------------------------------
# _clean_code tests
# ---------------------------------------------------------------------------

class TestCleanCode:

    def test_strips_python_fences(self):
        code = "```python\ndef f():\n    return 1\n```"
        assert _clean_code(code) == "def f():\n    return 1"

    def test_strips_plain_fences(self):
        code = "```\ndef f():\n    return 1\n```"
        assert _clean_code(code) == "def f():\n    return 1"

    def test_no_fences(self):
        code = "def f():\n    return 1"
        assert _clean_code(code) == "def f():\n    return 1"

    def test_strips_whitespace(self):
        code = "  \n def f():\n    return 1 \n  "
        assert _clean_code(code) == "def f():\n    return 1"


# ---------------------------------------------------------------------------
# _spec_hash tests
# ---------------------------------------------------------------------------

class TestSpecHash:

    def test_same_input_same_hash(self):
        h1 = _spec_hash("f", "(x: int)", "double x")
        h2 = _spec_hash("f", "(x: int)", "double x")
        assert h1 == h2

    def test_different_docstring_different_hash(self):
        h1 = _spec_hash("f", "(x: int)", "double x")
        h2 = _spec_hash("f", "(x: int)", "triple x")
        assert h1 != h2

    def test_different_name_different_hash(self):
        h1 = _spec_hash("f", "(x: int)", "double x")
        h2 = _spec_hash("g", "(x: int)", "double x")
        assert h1 != h2

    def test_hash_length(self):
        h = _spec_hash("f", "(x: int)", "docs")
        assert len(h) == 16


# ---------------------------------------------------------------------------
# _build_prompt tests
# ---------------------------------------------------------------------------

class TestBuildPrompt:

    def test_includes_function_name(self):
        prompt = _build_prompt("factorial", "(n: int) -> int", 
                               "Return factorial of n", {})
        assert "factorial" in prompt

    def test_includes_docstring(self):
        prompt = _build_prompt("f", "(x: int)", "Double the input", {})
        assert "Double the input" in prompt

    def test_includes_type_hints(self):
        hints = {"n": "int", "return": "int"}
        prompt = _build_prompt("f", "(n: int) -> int", "docs", hints)
        assert "n: int" in prompt


# ---------------------------------------------------------------------------
# Integration test — @ai decorator (requires ANTHROPIC_API_KEY)
# ---------------------------------------------------------------------------

class TestAIDecorator:
    """
    These tests actually call the LLM. Skip if no API key is set.
    Run with: pytest tests/ -m integration
    """

    @pytest.fixture(autouse=True)
    def check_api_key(self):
        import os
        if not os.environ.get("ANTHROPIC_API_KEY"):
            pytest.skip("ANTHROPIC_API_KEY not set")

    @pytest.mark.integration
    def test_simple_function(self):
        from speq import ai

        @ai
        def double(x: int) -> int:
            """Return x multiplied by 2."""
            ...

        assert double(5) == 10
        assert double(0) == 0
        assert double(-3) == -6

    @pytest.mark.integration
    def test_string_function(self):
        from speq import ai

        @ai
        def reverse_string(s: str) -> str:
            """Reverse the input string. Example: 'hello' -> 'olleh'."""
            ...

        assert reverse_string("hello") == "olleh"
        assert reverse_string("") == ""
        assert reverse_string("a") == "a"

    @pytest.mark.integration
    def test_caching(self):
        """Second call should use cache, not call LLM again."""
        from speq import ai
        from speq.core import _memory_cache

        @ai
        def add_one(x: int) -> int:
            """Add 1 to x."""
            ...

        result1 = add_one(5)
        cache_size_after_first = len(_memory_cache)

        result2 = add_one(10)
        cache_size_after_second = len(_memory_cache)

        assert result1 == 6
        assert result2 == 11
        assert cache_size_after_first == cache_size_after_second
