"""Tests for base chain builder and invoke_chain_safe."""
from __future__ import annotations

from unittest.mock import MagicMock, patch

from pydantic import BaseModel, Field

from claim_validator.constants import Severity


class _MockOutput(BaseModel):
    result: str = Field(default="ok")


class TestBuildChain:
    def test_returns_runnable(self) -> None:
        from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
        from langchain_core.output_parsers import PydanticOutputParser
        from claim_validator.chains.base import build_chain

        prompt = ChatPromptTemplate.from_messages([
            ("system", "Test {format_instructions}"),
            MessagesPlaceholder("history", optional=True),
            ("human", "{test_input}"),
        ]).partial(format_instructions=PydanticOutputParser(pydantic_object=_MockOutput).get_format_instructions())

        with patch("claim_validator.chains.provider_factory.build_chat_model") as mock_build:
            mock_build.return_value = MagicMock()
            chain = build_chain(
                prompt=prompt, output_schema=_MockOutput,
                config={"provider": "groq", "model": "test", "api_key": "test"},
                input_key="test_input",
            )
            assert chain is not None

    def test_with_memory_returns_runnable_with_history(self) -> None:
        from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
        from langchain_core.output_parsers import PydanticOutputParser
        from claim_validator.chains.base import build_chain
        from claim_validator.chains.memory.manager import MemoryManager

        prompt = ChatPromptTemplate.from_messages([
            ("system", "Test {format_instructions}"),
            MessagesPlaceholder("history", optional=True),
            ("human", "{test_input}"),
        ]).partial(format_instructions=PydanticOutputParser(pydantic_object=_MockOutput).get_format_instructions())

        with patch("claim_validator.chains.provider_factory.build_chat_model") as mock_build:
            mock_build.return_value = MagicMock()
            chain = build_chain(
                prompt=prompt, output_schema=_MockOutput,
                config={"provider": "groq", "model": "test", "api_key": "test"},
                memory_manager=MemoryManager(), input_key="test_input",
            )
            assert chain is not None


class TestInvokeChainSafe:
    def test_success_returns_output(self) -> None:
        from claim_validator.chains.base import invoke_chain_safe
        mock_chain = MagicMock()
        mock_chain.invoke.return_value = _MockOutput(result="success")
        result = invoke_chain_safe(mock_chain, {"input": "test"})
        assert result is not None
        assert result.result == "success"

    def test_parse_error_retries_once(self) -> None:
        from langchain_core.exceptions import OutputParserException
        from claim_validator.chains.base import invoke_chain_safe
        mock_chain = MagicMock()
        mock_chain.invoke.side_effect = [OutputParserException("bad json"), _MockOutput(result="retry_ok")]
        result = invoke_chain_safe(mock_chain, {"input": "test"})
        assert result is not None
        assert result.result == "retry_ok"
        assert mock_chain.invoke.call_count == 2

    def test_parse_error_both_fail_returns_fallback(self) -> None:
        from langchain_core.exceptions import OutputParserException
        from claim_validator.chains.base import invoke_chain_safe
        mock_chain = MagicMock()
        mock_chain.invoke.side_effect = OutputParserException("bad json")
        fallback = _MockOutput(result="fallback")
        result = invoke_chain_safe(mock_chain, {"input": "test"}, fallback_output=fallback)
        assert result is not None
        assert result.result == "fallback"

    def test_non_parse_failure_returns_fallback(self) -> None:
        from claim_validator.chains.base import invoke_chain_safe
        mock_chain = MagicMock()
        mock_chain.invoke.side_effect = Exception("LLM error")
        fallback = _MockOutput(result="fallback")
        result = invoke_chain_safe(mock_chain, {"input": "test"}, fallback_output=fallback)
        assert result is not None
        assert result.result == "fallback"

    def test_failure_no_fallback_returns_none(self) -> None:
        from claim_validator.chains.base import invoke_chain_safe
        mock_chain = MagicMock()
        mock_chain.invoke.side_effect = Exception("LLM error")
        result = invoke_chain_safe(mock_chain, {"input": "test"})
        assert result is None
