"""Auxiliary Responses normalization must not certify a partial checkpoint."""

from types import SimpleNamespace

import pytest

from agent.auxiliary_client import _CodexCompletionsAdapter
from agent.context_compressor import ContextCompressor


def _message(text, *, phase: str | None = "final_answer", status="completed"):
    return SimpleNamespace(
        type="message", role="assistant", phase=phase, status=status,
        content=[SimpleNamespace(type="output_text", text=text)],
    )


def _adapter_response(final, *, streamed, base_url=""):
    class FakeStream:
        def __iter__(self):
            for item in final.output or []:
                yield SimpleNamespace(type="response.output_item.done", item=item)
            if getattr(final, "output_text", None):
                yield SimpleNamespace(type="response.output_text.delta", delta=final.output_text)
            yield SimpleNamespace(type=f"response.{final.status}", response=final)

        def close(self):
            pass

    class FakeResponses:
        def create(self, **kwargs):
            assert kwargs["stream"] is True
            return FakeStream() if streamed else final

    return _CodexCompletionsAdapter(
        SimpleNamespace(base_url=base_url, responses=FakeResponses()), "aux-model",
    ).create(messages=[{"role": "user", "content": "Summarize the task."}])


def test_compressor_rejects_actual_adapter_partial_summary(monkeypatch):
    partial = SimpleNamespace(
        status="incomplete", output=[_message("PARTIAL_CHECKPOINT")],
        incomplete_details={"reason": "max_output_tokens"}, error=None,
        usage=SimpleNamespace(input_tokens=11, output_tokens=3, total_tokens=14),
    )
    calls = []

    def fake_call_llm(**kwargs):
        calls.append(kwargs.get("model"))
        return _adapter_response(partial, streamed=True)

    monkeypatch.setattr("agent.context_compressor.call_llm", fake_call_llm)
    monkeypatch.setattr("agent.context_compressor.get_model_context_length", lambda *a, **k: 100000)
    compressor = ContextCompressor(
        model="main-model", quiet_mode=True, tail_mode="legacy", protect_first_n=2, protect_last_n=2,
        abort_on_summary_failure=False,
    )
    messages = [
        {"role": "user" if i % 2 == 0 else "assistant", "content": f"task {i} " + "x" * 50}
        for i in range(12)
    ]
    original = [dict(message) for message in messages]
    result = compressor.compress(messages, current_tokens=999999, force=True)

    assert calls == [None]
    assert result == original and messages == original
    assert compressor._previous_summary is None
    assert compressor._last_summary_truncated_failure is True
    assert compressor._last_compress_aborted is True


_TOOL_CALL = SimpleNamespace(
    type="function_call", status="completed", call_id="call_1", name="inspect", arguments='{"path":"x"}',
)
_LEAK_LIKE_ANSWER = 'Summary of work.\nRunning the tests next.\n{"cmd": "pytest -q"}'
_PARTIAL_TOOL_CALL = SimpleNamespace(
    type="function_call", status="in_progress", call_id="call_2", name="inspect", arguments='{"pa',
)
_CODEX_REASONING_ONLY = SimpleNamespace(
    type="reasoning", id="rs_test", encrypted_content=None, summary=[SimpleNamespace(text="still thinking")],
)


@pytest.mark.parametrize("streamed", [False, True], ids=["response-object", "sse"])
@pytest.mark.parametrize(
    "base_url, status, output, reason, expected_content, expected_finish",
    [
        pytest.param("", "completed", [_message("FINAL")], None, "FINAL", "stop", id="final-answer"),
        pytest.param("", "completed", [_message("COMMENTARY", phase="commentary")], None, None, "length", id="commentary-only"),
        pytest.param("", "completed", [_TOOL_CALL], None, None, "tool_calls", id="tool-call"),
        pytest.param("", "completed", [{"type": "message", "role": "assistant", "status": "completed",
                                        "content": [{"type": "output_text", "text": "DICT"}]}], None, "DICT", "stop",
                     id="dict-items"),
        pytest.param("", "completed", [_message(_LEAK_LIKE_ANSWER, phase=None)], None, _LEAK_LIKE_ANSWER, "stop",
                     id="tool-call-like-answer"),
        pytest.param("", "completed", [_message(_LEAK_LIKE_ANSWER, phase=None), _PARTIAL_TOOL_CALL], None,
                     _LEAK_LIKE_ANSWER, "length", id="tool-call-like-partial-item"),
        # A str ``output`` is delivered only via output_text (empty output list), as streamed Codex answers can be.
        pytest.param("", "completed", _LEAK_LIKE_ANSWER, None, _LEAK_LIKE_ANSWER, "stop", id="tool-call-like-output-text"),
        # (items, text): output_text beside a commentary item is narration, never the rescued answer.
        pytest.param("", "completed", ([_message("", phase="commentary")], _LEAK_LIKE_ANSWER), None, None, "length",
                     id="tool-call-like-output-text-beside-commentary"),
        pytest.param("", "incomplete", [_message("PARTIAL")], "max_output_tokens", "PARTIAL", "length", id="token-cap"),
        pytest.param("", "incomplete", [_TOOL_CALL], "max_output_tokens", None, "tool_calls", id="token-cap-after-tool-call"),
        # Compressor only rejects "length": a content-filtered partial must not be committed as a summary.
        pytest.param("", "incomplete", [_message("CF")], "content_filter", "CF", "length", id="content-filter"),
        # Commentary + unphased text is incomplete whether or not the unphased text looks like a tool call.
        pytest.param("", "completed", [_message("NOTE", phase="commentary"), _message(_LEAK_LIKE_ANSWER, phase=None)],
                     None, _LEAK_LIKE_ANSWER, "length", id="tool-call-like-beside-commentary"),
        # Route-sensitive normalization: the issuer comes from the request's route classification.
        pytest.param("https://chatgpt.com/backend-api/codex", "completed", [_CODEX_REASONING_ONLY], None, None, "length",
                     id="codex-reasoning-only"),
    ],
)
def test_actual_adapter_preserves_completion_contract(
    streamed, base_url, status, output, reason, expected_content, expected_finish,
):
    items, output_text = output if isinstance(output, tuple) else ([], output) if isinstance(output, str) else (output, "")
    final = SimpleNamespace(
        status=status, output=items, output_text=output_text,
        incomplete_details={"reason": reason} if reason else None, error=None,
        usage={"input_tokens": 11, "output_tokens": 3, "total_tokens": 14},
    )

    choice = _adapter_response(final, streamed=streamed, base_url=base_url).choices[0]

    assert (choice.message.content, choice.finish_reason) == (expected_content, expected_finish)
    if expected_finish == "tool_calls":
        call, = choice.message.tool_calls
        assert (call.id, call.function.name, call.function.arguments) == ("call_1", "inspect", '{"path":"x"}')
