"""
全面测试问题1修复：understand_image 参数别名、ALL_SCHEMAS 注册、函数体回退

覆盖：
  Fix A: ToolParameter 别名 + 类型推导 (main_agent.py)
  Fix B: ALL_SCHEMAS 包含多模态工具 (tool_schemas.py)
  Fix C: 函数体内 **kwargs 参数名回退 (multimodal_tools.py)
"""
import sys
from pathlib import Path
from unittest.mock import AsyncMock, patch, MagicMock

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent))

from aacode.utils.tool_schemas import get_all_schemas
from aacode.utils.tool_registry import get_global_registry, ToolSchema, ToolParameter
from aacode.utils.tool_adapter import to_openai_tools, to_anthropic_tools


# ============================================================
#  Fix B 测试：ALL_SCHEMAS 包含多模态工具
# ============================================================

class TestMultimodalInAllSchemas:
    """验证多模态工具已在 ALL_SCHEMAS 中"""

    def test_all_four_multimodal_tools_in_schemas(self):
        schemas = get_all_schemas()
        expected = [
            "understand_image",
            "understand_video",
            "understand_ui_design",
            "analyze_image_consistency",
        ]
        for name in expected:
            assert name in schemas, f"{name} should be in ALL_SCHEMAS"

    def test_understand_image_schema_structure(self):
        schema = get_all_schemas()["understand_image"]
        assert schema.name == "understand_image"
        param_names = {p.name for p in schema.parameters}
        assert param_names == {"image_path", "prompt"}
        required = {p.name for p in schema.parameters if p.required}
        assert required == {"image_path"}

    def test_understand_video_schema_structure(self):
        schema = get_all_schemas()["understand_video"]
        param_names = {p.name for p in schema.parameters}
        assert param_names == {"video_path", "prompt"}
        required = {p.name for p in schema.parameters if p.required}
        assert required == {"video_path"}

    def test_understand_ui_design_schema_structure(self):
        schema = get_all_schemas()["understand_ui_design"]
        param_names = {p.name for p in schema.parameters}
        assert param_names == {"design_path", "prompt", "generate_code"}
        required = {p.name for p in schema.parameters if p.required}
        assert required == {"design_path"}

    def test_analyze_image_consistency_schema_structure(self):
        schema = get_all_schemas()["analyze_image_consistency"]
        param_names = {p.name for p in schema.parameters}
        assert param_names == {"image_paths", "prompt"}
        required = {p.name for p in schema.parameters if p.required}
        assert required == {"image_paths"}


# ============================================================
#  Fix A 测试：参数别名 + 类型推导
# ============================================================

class TestParameterAliases:
    """验证参数别名解析"""

    def test_image_path_aliases(self):
        schema = get_all_schemas()["understand_image"]
        param = next(p for p in schema.parameters if p.name == "image_path")
        assert "image" in param.aliases, "image_path should accept 'image' alias"
        assert "path" in param.aliases
        assert "file" in param.aliases

    def test_video_path_aliases(self):
        schema = get_all_schemas()["understand_video"]
        param = next(p for p in schema.parameters if p.name == "video_path")
        assert "video" in param.aliases
        assert "path" in param.aliases
        assert "file" in param.aliases

    def test_design_path_aliases(self):
        schema = get_all_schemas()["understand_ui_design"]
        param = next(p for p in schema.parameters if p.name == "design_path")
        assert "design" in param.aliases
        assert "path" in param.aliases
        assert "image" in param.aliases
        assert "file" in param.aliases

    def test_image_paths_aliases(self):
        schema = get_all_schemas()["analyze_image_consistency"]
        param = next(p for p in schema.parameters if p.name == "image_paths")
        assert "images" in param.aliases
        assert "paths" in param.aliases
        assert "files" in param.aliases
        assert "image_path" in param.aliases

    def test_prompt_aliases(self):
        for tool_name in ["understand_image", "understand_video",
                          "understand_ui_design", "analyze_image_consistency"]:
            schema = get_all_schemas()[tool_name]
            param = next(p for p in schema.parameters if p.name == "prompt")
            assert "question" in param.aliases, f"{tool_name}: prompt should have 'question' alias"
            assert "query" in param.aliases, f"{tool_name}: prompt should have 'query' alias"

    def test_generate_code_aliases_and_type(self):
        schema = get_all_schemas()["understand_ui_design"]
        param = next(p for p in schema.parameters if p.name == "generate_code")
        assert "code" in param.aliases
        assert "gen_code" in param.aliases
        assert param.type == bool, f"generate_code should be bool, got {param.type}"


# ============================================================
#  validate_call + normalize_params 端到端测试
# ============================================================

class TestValidateAndNormalize:
    """验证别名在 validate_call → normalize_params 全流程中正确工作"""

    def setup_method(self):
        self.registry = get_global_registry()
        for name, schema in get_all_schemas().items():
            if name.startswith(("understand", "analyze")):
                if name not in self.registry.schemas:
                    self.registry.register(lambda: None, schema)

    # ---- understand_image ----

    def test_validate_understand_image_with_image_path(self):
        result = self.registry.validate_call(
            "understand_image", {"image_path": "test.png"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_validate_understand_image_with_image_alias(self):
        result = self.registry.validate_call(
            "understand_image", {"image": "test.png"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_validate_understand_image_with_path_alias(self):
        result = self.registry.validate_call(
            "understand_image", {"path": "test.png"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_validate_understand_image_missing_required(self):
        result = self.registry.validate_call(
            "understand_image", {"prompt": "hello"})
        assert not result.valid

    def test_normalize_image_alias_to_image_path(self):
        schema = self.registry.get_schema("understand_image")
        result = schema.normalize_params({"image": "test.png", "question": "desc"})
        assert result == {"image_path": "test.png", "prompt": "desc"}

    def test_normalize_mixed_aliases_and_standard(self):
        schema = self.registry.get_schema("understand_image")
        result = schema.normalize_params({"image": "a.png", "prompt": "b"})
        assert result == {"image_path": "a.png", "prompt": "b"}

    # ---- understand_video ----

    def test_validate_understand_video_with_video_alias(self):
        result = self.registry.validate_call(
            "understand_video", {"video": "v.mp4"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_normalize_video_alias(self):
        schema = self.registry.get_schema("understand_video")
        result = schema.normalize_params({"video": "v.mp4", "query": "Q"})
        assert result == {"video_path": "v.mp4", "prompt": "Q"}

    # ---- understand_ui_design ----

    def test_validate_understand_ui_design_with_design_alias(self):
        result = self.registry.validate_call(
            "understand_ui_design", {"design": "mockup.png"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_normalize_ui_design_full_aliases(self):
        schema = self.registry.get_schema("understand_ui_design")
        result = schema.normalize_params({
            "design": "d.png", "question": "Q", "code": True
        })
        assert result == {
            "design_path": "d.png", "prompt": "Q", "generate_code": True
        }

    def test_normalize_ui_design_gen_code_alias(self):
        schema = self.registry.get_schema("understand_ui_design")
        result = schema.normalize_params({
            "design": "d.png", "gen_code": False
        })
        assert result == {"design_path": "d.png", "generate_code": False}

    # ---- analyze_image_consistency ----

    def test_validate_analyze_consistency_with_images_alias(self):
        result = self.registry.validate_call(
            "analyze_image_consistency", {"images": "a.jpg,b.jpg"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_validate_analyze_consistency_with_image_path_alias(self):
        result = self.registry.validate_call(
            "analyze_image_consistency", {"image_path": "a.jpg,b.jpg"})
        assert result.valid, f"Expected valid, got: {result.error_message}"

    def test_normalize_consistency_aliases(self):
        schema = self.registry.get_schema("analyze_image_consistency")
        result = schema.normalize_params({
            "images": "a.jpg,b.jpg", "query": "same person?"
        })
        assert result == {"image_paths": "a.jpg,b.jpg", "prompt": "same person?"}

    # ---- generate_code 类型验证 ----

    def test_generate_code_accepts_bool(self):
        param = ToolParameter(
            name="generate_code", type=bool, required=False, description="",
            aliases=["code", "gen_code"])
        valid, err = param.validate(True)
        assert valid, f"bool should be valid: {err}"
        valid, err = param.validate(False)
        assert valid, f"bool should be valid: {err}"

    def test_generate_code_rejects_string(self):
        param = ToolParameter(
            name="generate_code", type=bool, required=False, description="",
            aliases=["code", "gen_code"])
        valid, err = param.validate("true")
        assert not valid, "string 'true' should be rejected for bool type"


# ============================================================
#  to_openai_tools / to_anthropic_tools 集成测试
# ============================================================

class TestToolAdapterWithMultimodal:
    """验证多模态工具能正确转为 OpenAI/Anthropic 格式"""

    def test_openai_format_contains_multimodal_tools(self):
        schemas = get_all_schemas()
        multimodal_schemas = {
            k: v for k, v in schemas.items()
            if k.startswith(("understand", "analyze"))
        }
        tools = to_openai_tools(multimodal_schemas)
        assert len(tools) == 4
        names = [t["function"]["name"] for t in tools]
        assert "understand_image" in names
        assert "understand_video" in names
        assert "understand_ui_design" in names
        assert "analyze_image_consistency" in names

    def test_openai_format_image_right_params(self):
        schemas = get_all_schemas()
        tools = to_openai_tools({"understand_image": schemas["understand_image"]})
        func = tools[0]["function"]
        props = func["parameters"]["properties"]
        assert "image_path" in props
        assert props["image_path"]["type"] == "string"
        assert "image_path" in func["parameters"]["required"]

    def test_openai_format_ui_design_bool_type(self):
        schemas = get_all_schemas()
        tools = to_openai_tools({"understand_ui_design": schemas["understand_ui_design"]})
        func = tools[0]["function"]
        props = func["parameters"]["properties"]
        assert props["generate_code"]["type"] == "boolean"

    def test_anthropic_format_contains_multimodal_tools(self):
        schemas = get_all_schemas()
        multimodal_schemas = {
            k: v for k, v in schemas.items()
            if k.startswith(("understand", "analyze"))
        }
        tools = to_anthropic_tools(multimodal_schemas)
        assert len(tools) == 4
        names = [t["name"] for t in tools]
        assert "understand_image" in names

    def test_anthropic_format_input_schema(self):
        schemas = get_all_schemas()
        tools = to_anthropic_tools({"understand_image": schemas["understand_image"]})
        tool = tools[0]
        assert tool["input_schema"]["type"] == "object"
        assert "image_path" in tool["input_schema"]["properties"]
        assert "image_path" in tool["input_schema"]["required"]


# ============================================================
#  Fix C 测试：函数体内 **kwargs 参数名回退
# ============================================================

class TestFunctionBodyFallback:
    """验证多模态工具函数体内的 kwargs 回退逻辑"""

    @staticmethod
    def _create_mock_tools():
        from aacode.tools.multimodal_tools import MultimodalTools

        tools = MultimodalTools(project_path=Path("/tmp"))
        tools._validate_file = MagicMock(return_value={"valid": True})
        tools._encode_image = MagicMock(return_value="fake_base64")
        tools._call_vision_api = AsyncMock(return_value="Image analysis result")
        tools._get_model_config = MagicMock(return_value={
            "provider": "openai",
            "name": "gpt-4o",
            "api_key": "test",
            "video": True,
        })
        return tools

    @pytest.mark.asyncio
    async def test_understand_image_fallback_to_image(self):
        tools = self._create_mock_tools()
        tools._validate_file = MagicMock(return_value={"valid": True})

        result = await tools.understand_image(
            image_path="", image="/tmp/test.png",
            prompt="describe this"
        )
        assert result.get("success"), f"Should succeed, got: {result}"
        assert "Image analysis result" in result.get("description", "")

    @pytest.mark.asyncio
    async def test_understand_image_fallback_to_path(self):
        tools = self._create_mock_tools()
        result = await tools.understand_image(
            image_path="", path="/tmp/test.png"
        )
        assert result.get("success")

    @pytest.mark.asyncio
    async def test_understand_video_fallback_to_video(self):
        import tempfile
        tools = self._create_mock_tools()
        with tempfile.NamedTemporaryFile(suffix=".mp4", delete=True) as f:
            f.write(b"fake video content")
            f.flush()
            result = await tools.understand_video(
                video_path="", video=f.name
            )
        assert result.get("success")

    @pytest.mark.asyncio
    async def test_understand_ui_design_fallback_to_design(self):
        tools = self._create_mock_tools()
        result = await tools.understand_ui_design(
            design_path="", design="/tmp/mockup.png"
        )
        assert result.get("success")

    @pytest.mark.asyncio
    async def test_understand_ui_design_fallback_code_param(self):
        tools = self._create_mock_tools()
        result = await tools.understand_ui_design(
            design_path="/tmp/d.png", generate_code=True, code=False
        )
        assert result.get("success")

    @pytest.mark.asyncio
    async def test_analyze_image_consistency_fallback_to_images(self):
        tools = self._create_mock_tools()
        result = await tools.analyze_image_consistency(
            image_paths="", images="/tmp/a.jpg,/tmp/b.jpg"
        )
        assert result.get("success")

    @pytest.mark.asyncio
    async def test_analyze_image_consistency_fallback_to_image_path(self):
        tools = self._create_mock_tools()
        result = await tools.analyze_image_consistency(
            image_paths="", image_path="/tmp/a.jpg,/tmp/b.jpg"
        )
        assert result.get("success")


# ============================================================
#  集成：validate + normalize + execute 完整管线
# ============================================================

class TestFullPipelineIntegration:
    """模拟 react_loop 中的完整执行流程：validate → normalize → execute"""

    def setup_method(self):
        self.registry = get_global_registry()
        for name, schema in get_all_schemas().items():
            if name.startswith(("understand", "analyze")):
                if name not in self.registry.schemas:
                    self.registry.register(lambda: None, schema)

    def test_pipeline_understand_image_with_image_alias(self):
        """模型传入 {"image": "..."} 时的完整链路"""
        action = "understand_image"
        action_input = {"image": "screenshot.png", "question": "What is this?"}

        # Step 1: validate
        result = self.registry.validate_call(action, action_input)
        assert result.valid, f"Validate failed: {result.error_message}"

        # Step 2: normalize
        schema = self.registry.get_schema(action)
        normalized = schema.normalize_params(action_input)
        assert normalized == {
            "image_path": "screenshot.png",
            "prompt": "What is this?",
        }

    def test_pipeline_understand_video_with_video_alias(self):
        action = "understand_video"
        action_input = {"video": "demo.mp4", "query": "summary"}

        result = self.registry.validate_call(action, action_input)
        assert result.valid

        schema = self.registry.get_schema(action)
        normalized = schema.normalize_params(action_input)
        assert normalized == {
            "video_path": "demo.mp4",
            "prompt": "summary",
        }

    def test_pipeline_analyze_consistency_with_images_alias(self):
        action = "analyze_image_consistency"
        action_input = {"images": "a.jpg,b.jpg", "query": "same?"}

        result = self.registry.validate_call(action, action_input)
        assert result.valid

        schema = self.registry.get_schema(action)
        normalized = schema.normalize_params(action_input)
        assert normalized == {
            "image_paths": "a.jpg,b.jpg",
            "prompt": "same?",
        }

    def test_pipeline_analyze_consistency_with_image_path_singular(self):
        """image_paths 接受单数别名 image_path"""
        action = "analyze_image_consistency"
        action_input = {"image_path": "a.jpg,b.jpg"}

        result = self.registry.validate_call(action, action_input)
        assert result.valid

        schema = self.registry.get_schema(action)
        normalized = schema.normalize_params(action_input)
        assert normalized == {"image_paths": "a.jpg,b.jpg"}

    def test_pipeline_ui_design_full_aliases(self):
        action = "understand_ui_design"
        action_input = {"design": "page.png", "question": "analyze", "code": False}

        result = self.registry.validate_call(action, action_input)
        assert result.valid

        schema = self.registry.get_schema(action)
        normalized = schema.normalize_params(action_input)
        assert normalized == {
            "design_path": "page.png",
            "prompt": "analyze",
            "generate_code": False,
        }

    def test_pipeline_rejects_unknown_alias(self):
        """不存在的参数名+没注册别名 → 应该被拒绝（image_path 是 required）"""
        action = "understand_image"
        action_input = {"img": "test.png"}  # 'img' 不是注册的别名

        result = self.registry.validate_call(action, action_input)
        assert not result.valid, "Unknown alias should be rejected"
