"""Tests for the OpenClaw adapter."""

import asyncio
import tempfile
from pathlib import Path

import pytest

from operon_guard.adapters.openclaw_adapter import (
    OpenClawAdapter,
    _extract_openclaw_output,
    load_openclaw_skill,
)
from operon_guard.adapters.detect import detect_adapter, detect_and_load
from operon_guard.core.spec import GuardSpec, TestCase
from operon_guard.core.runner import GuardRunner


# ── Fixtures ──

@pytest.fixture
def adapter():
    return OpenClawAdapter()


@pytest.fixture
def skill_dir(tmp_path):
    """Create a minimal OpenClaw skill directory."""
    skill = tmp_path / "test-skill"
    skill.mkdir()

    # SKILL.md
    (skill / "SKILL.md").write_text(
        "---\nname: test-skill\ndescription: A test skill\n---\n\n# Test Skill\n"
    )

    # scripts/
    scripts = skill / "scripts"
    scripts.mkdir()
    (scripts / "main.py").write_text(
        'def run(question: str) -> str:\n'
        '    return f"Answer to: {question}"\n'
    )

    return skill


@pytest.fixture
def instruction_only_skill(tmp_path):
    """Skill with SKILL.md but no scripts."""
    skill = tmp_path / "instructions-only"
    skill.mkdir()
    (skill / "SKILL.md").write_text(
        "---\nname: helper\ndescription: Instruction-only skill\n---\n\n"
        "# Helper\n\nJust follow these instructions.\n"
    )
    return skill


# ── Detection Tests ──

class TestOpenClawDetection:
    def test_detect_skill_directory(self, adapter, skill_dir):
        assert OpenClawAdapter.detect(str(skill_dir))

    def test_detect_non_skill_directory(self, adapter, tmp_path):
        assert not OpenClawAdapter.detect(str(tmp_path))

    def test_detect_openclaw_module(self, adapter):
        """Objects with openclaw in their module path should be detected."""
        class FakeAgent:
            pass
        FakeAgent.__module__ = "openclaw.agents.my_agent"
        assert OpenClawAdapter.detect(FakeAgent())

    def test_detect_clawhub_module(self, adapter):
        class FakeClient:
            pass
        FakeClient.__module__ = "clawhub.client"
        assert OpenClawAdapter.detect(FakeClient())

    def test_detect_run_skill_method(self, adapter):
        class FakeAgent:
            def run_skill(self, x): pass
            def send_message(self, x): pass
        assert OpenClawAdapter.detect(FakeAgent())

    def test_detect_clawhub_pattern(self, adapter):
        class FakeHub:
            def search(self, x): pass
            @property
            def installed_skills(self): return []
        assert OpenClawAdapter.detect(FakeHub())

    def test_no_detect_plain_callable(self, adapter):
        def my_fn(x): return x
        assert not OpenClawAdapter.detect(my_fn)


# ── Wrapping Tests ──

class TestOpenClawWrapping:
    def test_wrap_skill_directory(self, adapter, skill_dir):
        fn = adapter.wrap(skill_dir)
        result = fn("Hello world")
        assert "Answer to: Hello world" in result

    def test_wrap_skill_directory_string(self, adapter, skill_dir):
        fn = adapter.wrap(str(skill_dir))
        result = fn("test input")
        assert "Answer to: test input" in result

    def test_wrap_instruction_only_skill(self, adapter, instruction_only_skill):
        fn = adapter.wrap(instruction_only_skill)
        result = fn("some question")
        assert "Instruction-only skill" in result
        assert "some question" in result

    def test_wrap_run_skill_object(self, adapter):
        class FakeAgent:
            def run_skill(self, inp):
                return f"Skill result: {inp}"
        fn = adapter.wrap(FakeAgent())
        assert "Skill result: hello" in fn("hello")

    def test_wrap_send_message_object(self, adapter):
        class FakeAgent:
            def send_message(self, inp):
                return {"content": f"Reply: {inp}"}
        fn = adapter.wrap(FakeAgent())
        assert "Reply: hi" in fn("hi")

    def test_wrap_callable(self, adapter):
        def my_agent(inp):
            return f"Output: {inp}"
        fn = adapter.wrap(my_agent)
        assert "Output: test" in fn("test")

    def test_wrap_invoke_object(self, adapter):
        class FakeAgent:
            def invoke(self, inp):
                return {"text": f"Invoked: {inp}"}
        fn = adapter.wrap(FakeAgent())
        assert "Invoked: query" in fn("query")


# ── Output Extraction Tests ──

class TestOutputExtraction:
    def test_extract_string(self):
        assert _extract_openclaw_output("hello") == "hello"

    def test_extract_none(self):
        assert _extract_openclaw_output(None) == "No output."

    def test_extract_dict_content(self):
        assert _extract_openclaw_output({"content": "msg"}) == "msg"

    def test_extract_dict_text(self):
        assert _extract_openclaw_output({"text": "msg"}) == "msg"

    def test_extract_dict_output(self):
        assert _extract_openclaw_output({"output": "msg"}) == "msg"

    def test_extract_message_list(self):
        messages = [
            {"role": "user", "content": "hi"},
            {"role": "assistant", "content": "hello there"},
        ]
        assert _extract_openclaw_output(messages) == "hello there"

    def test_extract_empty_list(self):
        assert _extract_openclaw_output([]) == "No output."

    def test_extract_object_with_content(self):
        class Resp:
            content = "response text"
        assert _extract_openclaw_output(Resp()) == "response text"

    def test_extract_object_with_raw(self):
        class Resp:
            raw = "raw output"
        assert _extract_openclaw_output(Resp()) == "raw output"


# ── Integration Tests ──

class TestOpenClawIntegration:
    def test_load_openclaw_skill(self, skill_dir):
        fn, adapter = load_openclaw_skill(skill_dir)
        assert adapter.name == "openclaw"
        result = fn("test question")
        assert "Answer to: test question" in result

    def test_full_guard_run(self, skill_dir):
        """Run a full GuardRunner against an OpenClaw skill."""
        fn, _ = load_openclaw_skill(skill_dir)
        spec = GuardSpec(
            name="test-skill",
            test_cases=[
                TestCase(
                    name="basic",
                    input="What is AI?",
                    expected_contains=["Answer to"],
                ),
            ],
            check_determinism=True,
            determinism_runs=3,
            check_concurrency=True,
            concurrency_workers=2,
            check_safety=True,
            check_latency=True,
            latency_p95_ms=5000,
        )
        runner = GuardRunner(spec)
        report = asyncio.run(runner.run(fn))

        assert report.agent_name == "test-skill"
        assert report.trust_score.overall > 0
        assert report.test_cases_run > 0

    def test_code_review_skill(self):
        """Test the example code-review skill from examples/."""
        example_dir = Path(__file__).parent.parent / "examples" / "openclaw-skill-example"
        if not example_dir.exists():
            pytest.skip("Example skill not found")

        fn, adapter = load_openclaw_skill(example_dir)
        assert adapter.name == "openclaw"

        # test clean code
        result = fn("def add(a, b): return a + b")
        assert "No issues" in result

        # test security detection
        result = fn('api_key = "sk-secret123"')
        assert "SECURITY" in result

        # test question answering
        result = fn("What are best practices for code review?")
        assert "best practice" in result.lower()


# ── Edge Case Tests ──

class TestEdgeCases:
    def test_skill_with_main_py(self, tmp_path):
        """Skill with main.py in root (not scripts/)."""
        skill = tmp_path / "main-skill"
        skill.mkdir()
        (skill / "SKILL.md").write_text("---\nname: main-skill\n---\n")
        (skill / "main.py").write_text(
            'def run(x: str) -> str:\n    return f"Main: {x}"\n'
        )
        adapter = OpenClawAdapter()
        fn = adapter.wrap(skill)
        assert "Main: hello" in fn("hello")

    def test_skill_with_run_py(self, tmp_path):
        """Skill with run.py in root."""
        skill = tmp_path / "run-skill"
        skill.mkdir()
        (skill / "SKILL.md").write_text("---\nname: run-skill\n---\n")
        (skill / "run.py").write_text(
            'def run(x: str) -> str:\n    return f"Run: {x}"\n'
        )
        adapter = OpenClawAdapter()
        fn = adapter.wrap(skill)
        assert "Run: test" in fn("test")

    def test_non_wrappable_raises(self, adapter):
        """Non-wrappable objects should raise TypeError."""
        with pytest.raises(TypeError):
            adapter.wrap(42)
