from __future__ import annotations

import tempfile
import unittest
from pathlib import Path
from unittest.mock import Mock, patch

from src import agent_core
from src.prompt_manager import _get_or_create_langfuse_prompt_text, _resolve_prompt_bundle
from src.prompts import DEFAULT_PROMPTS


class AgentCoreTests(unittest.TestCase):
    def _tool_name(self, tool_obj) -> str:
        return str(getattr(tool_obj, "name", "") or getattr(tool_obj, "__name__", ""))

    def test_with_mode_overrides_existing_mode(self) -> None:
        updated = agent_core._with_mode("mode=overview limit=5", "evaluation", force_compare=True)
        self.assertIn("mode=evaluation", updated)
        self.assertIn("compare_models=true", updated)

    def test_agent_subagents_are_specialized(self) -> None:
        names = [item["name"] for item in agent_core._agent_subagents(dict(DEFAULT_PROMPTS))]
        self.assertIn("lens-overview-analyst", names)
        self.assertIn("lens-model-comparison-analyst", names)
        self.assertIn("lens-langfuse-controller", names)
        self.assertGreaterEqual(len(names), 5)

    def test_resolve_prompt_bundle_returns_defaults_when_sync_disabled(self) -> None:
        with patch.dict("os.environ", {"AGENT_LANGFUSE_PROMPT_SYNC": "false"}, clear=False):
            bundle = _resolve_prompt_bundle()
        self.assertEqual(bundle["orchestrator_system"], DEFAULT_PROMPTS["orchestrator_system"])

    def test_get_or_create_langfuse_prompt_text_creates_when_missing(self) -> None:
        fake_client = Mock()
        fake_client.get_prompt.side_effect = RuntimeError("404 prompt not found")
        fake_created = Mock()
        fake_created.prompt = "created-prompt"
        fake_client.create_prompt.return_value = fake_created

        resolved = _get_or_create_langfuse_prompt_text(fake_client, "x.prompt", "default-prompt")
        self.assertEqual(resolved, "created-prompt")
        fake_client.create_prompt.assert_called_once()

    def test_run_returns_error_when_deep_agent_unavailable(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            artifacts = Path(tmp)
            with patch.object(agent_core, "ARTIFACTS", artifacts), patch.object(
                agent_core, "_get_deep_agent", return_value=(None, "missing")
            ):
                result = agent_core.run("mode=overview")

        self.assertIn("Deep Agent initialization failed", result)
        self.assertIn("fallback pipeline has been intentionally disabled", result)

    def test_run_uses_deep_agent_output_when_available(self) -> None:
        dummy_agent = Mock()
        dummy_agent.invoke.return_value = {"messages": [{"role": "assistant", "content": "deep-output"}]}

        with tempfile.TemporaryDirectory() as tmp:
            artifacts = Path(tmp)
            with patch.object(agent_core, "ARTIFACTS", artifacts), patch.object(
                agent_core, "_get_deep_agent", return_value=(dummy_agent, None)
            ):
                result = agent_core.run("mode=overview")

        self.assertIn("deep-output", result)

    def test_run_returns_error_when_deep_agent_execution_fails(self) -> None:
        dummy_agent = Mock()
        dummy_agent.invoke.side_effect = RuntimeError("boom")
        with tempfile.TemporaryDirectory() as tmp:
            artifacts = Path(tmp)
            with patch.object(agent_core, "ARTIFACTS", artifacts), patch.object(
                agent_core, "_get_deep_agent", return_value=(dummy_agent, None)
            ):
                result = agent_core.run("mode=overview")
        self.assertIn("Deep Agent execution failed", result)

    def test_extract_agent_output_supports_block_content(self) -> None:
        result = {"messages": [{"role": "assistant", "content": [{"type": "text", "text": "hello"}]}]}
        self.assertEqual(agent_core._extract_agent_output(result), "hello")

    def test_run_lens_langfuse_control_returns_error_markdown_on_failure(self) -> None:
        with patch("src.agent_core.run_langfuse_control_message", side_effect=RuntimeError("boom")):
            output = agent_core.run_lens_langfuse_control('{\"operation\":\"status\"}')
        self.assertIn("ok: false", output)

    def test_build_agent_tools_readonly_excludes_mutation_tools(self) -> None:
        with patch.dict(
            "os.environ",
            {
                "AGENT_LANGFUSE_TOOL_PROFILE": "readonly",
                "AGENT_LANGFUSE_CONTROL_ALLOW_MUTATION": "true",
                "AGENT_LANGFUSE_TOOLS_ENABLE_MUTATION": "false",
                "AGENT_LANGFUSE_OPENAPI_DYNAMIC_TOOLS": "false",
            },
            clear=False,
        ):
            tools, summary = agent_core._build_agent_tools()
        names = [self._tool_name(item) for item in tools]
        self.assertEqual(summary["profile"], "readonly")
        self.assertFalse(summary["include_mutation_tools"])
        self.assertNotIn("langfuse_prompt_upsert", names)
        self.assertNotIn("langfuse_openapi_execute", names)

    def test_build_agent_tools_admin_includes_mutation_tools(self) -> None:
        with patch.dict(
            "os.environ",
            {
                "AGENT_LANGFUSE_TOOL_PROFILE": "admin",
                "AGENT_LANGFUSE_CONTROL_ALLOW_MUTATION": "true",
                "AGENT_LANGFUSE_TOOLS_ENABLE_MUTATION": "true",
                "AGENT_LANGFUSE_TOOLS_ENABLE_OPENAPI_EXECUTE": "true",
                "AGENT_LANGFUSE_OPENAPI_DYNAMIC_TOOLS": "false",
            },
            clear=False,
        ):
            tools, summary = agent_core._build_agent_tools()
        names = [self._tool_name(item) for item in tools]
        self.assertEqual(summary["profile"], "admin")
        self.assertTrue(summary["include_mutation_tools"])
        self.assertIn("langfuse_prompt_upsert", names)
        self.assertIn("langfuse_openapi_execute", names)

    def test_get_agent_tool_runtime_summary_contains_tools(self) -> None:
        with patch.dict("os.environ", {"AGENT_LANGFUSE_OPENAPI_DYNAMIC_TOOLS": "false"}, clear=False):
            summary = agent_core.get_agent_tool_runtime_summary()
        self.assertIn("tool_count", summary)
        self.assertIsInstance(summary.get("tools"), list)

    def test_get_agent_tool_presets_returns_catalog(self) -> None:
        with patch.dict("os.environ", {"AGENT_LANGFUSE_TOOL_PRESET": "readonly-safe"}, clear=False):
            presets = agent_core.get_agent_tool_presets()
        self.assertIn("presets", presets)
        self.assertEqual(presets["selected_preset"], "readonly-safe")

    def test_run_handles_local_agent_command_without_deep_agent(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            artifacts = Path(tmp)
            with patch.object(agent_core, "ARTIFACTS", artifacts), patch.object(
                agent_core, "_get_deep_agent", return_value=(None, "should-not-be-used")
            ):
                output = agent_core.run("/agent tools")
        self.assertIn("# Final Output", output)
        self.assertIn("Agent Runtime Tools", output)

    def test_get_agent_state_runtime_summary_reports_ready(self) -> None:
        fake_checkpointer = object()
        with patch.object(agent_core, "_get_checkpointer", return_value=(fake_checkpointer, None)):
            summary = agent_core.get_agent_state_runtime_summary()
        self.assertIn("ready", summary)
        self.assertTrue(summary["ready"])

    def test_run_passes_thread_id_in_invoke_config(self) -> None:
        dummy_agent = Mock()
        dummy_agent.invoke.return_value = {"messages": [{"role": "assistant", "content": "ok"}]}

        with tempfile.TemporaryDirectory() as tmp:
            artifacts = Path(tmp)
            with patch.object(agent_core, "ARTIFACTS", artifacts), patch.object(
                agent_core, "_get_deep_agent", return_value=(dummy_agent, None)
            ):
                _ = agent_core.run("mode=overview", thread_id="th_123", user_id="u_1")
        args, kwargs = dummy_agent.invoke.call_args
        self.assertIn("config", kwargs)
        self.assertEqual(kwargs["config"]["configurable"]["thread_id"], "th_123")
        self.assertEqual(kwargs["config"]["configurable"]["user_id"], "u_1")

    def test_list_agent_state_checkpoints_raises_without_thread_id(self) -> None:
        with self.assertRaises(ValueError):
            agent_core.list_agent_state_checkpoints("", limit=10)


if __name__ == "__main__":
    unittest.main()
