from __future__ import annotations

import threading

from evals import run_eval
from evals.patchers import openai_compat_patcher as openai_mod
from evals.patchers.base import PatchResult
from evals.patchers.openai_compat_patcher import (
    OpenAICompatPatcher,
    _base_url_from_chat_url,
    _build_diff_from_changes,
)


def test_openai_force_patch_attempt_builds_fallback_diff(tmp_path, monkeypatch) -> None:
    module_path = "evals.patchers.openai_compat_patcher._propose_patch_via_diff"
    monkeypatch.setattr(
        module_path,
        lambda **kwargs: PatchResult(diff=None, meta={"error": "no_diff_found"}),
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_edits",
        lambda **kwargs: PatchResult(diff=None, meta={"error": "no_json_payload"}),
    )

    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")
    (tmp_path / "test_buggy.py").write_text("def test_f():\n    assert True\n", encoding="utf-8")

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="diff",
        force_patch_attempt=True,
    )
    prompt = (
        "Available files (use exact relative paths):\n"
        "test_buggy.py\n"
        "buggy.py\n\n"
        "Pytest output (stdout):\nfailed\n"
    )
    result = patcher.propose_patch(prompt=prompt, context_dir=tmp_path, cwd=tmp_path)

    assert result.diff is not None
    assert "diff --git a/buggy.py b/buggy.py" in result.diff
    assert "llmdebug-force-attempt" in result.diff
    assert result.meta is not None
    assert result.meta["error"] == "no_json_payload"
    assert result.meta.get("fallback_from") == "diff"
    assert result.meta["forced_patch_attempt"] is True
    assert result.meta["forced_patch_target"] == "buggy.py"

    applied, _ = run_eval.apply_diff(tmp_path, result.diff)
    assert applied


def test_openai_force_patch_attempt_disabled_preserves_none(tmp_path, monkeypatch) -> None:
    module_path = "evals.patchers.openai_compat_patcher._propose_patch_via_diff"
    monkeypatch.setattr(
        module_path,
        lambda **kwargs: PatchResult(diff=None, meta={"error": "no_diff_found"}),
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_edits",
        lambda **kwargs: PatchResult(diff=None, meta={"error": "no_json_payload"}),
    )

    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="diff",
        force_patch_attempt=False,
    )
    prompt = (
        "Available files (use exact relative paths):\nbuggy.py\n\nPytest output (stdout):\nfailed\n"
    )
    result = patcher.propose_patch(prompt=prompt, context_dir=tmp_path, cwd=tmp_path)

    assert result.diff is None
    assert result.meta is not None
    assert result.meta["error"] == "no_json_payload"
    assert result.meta.get("fallback_from") == "diff"
    assert result.meta["response_mode"] == "diff"
    assert result.meta["api_compat_seed_requested"] is None


def test_build_diff_from_changes_is_patch_apply_compatible(tmp_path) -> None:
    target = tmp_path / "buggy.py"
    before = "def f():\n\n    return 1\n"
    after = "def f():\n\n    return 2\n"
    target.write_text(before, encoding="utf-8")

    diff = _build_diff_from_changes([("buggy.py", before, after)])
    assert "diff --git a/buggy.py b/buggy.py" in diff

    applied, message = run_eval.apply_diff(tmp_path, diff)
    assert applied, message
    assert target.read_text(encoding="utf-8") == after


def test_base_url_from_chat_url_supports_non_v1_paths() -> None:
    assert _base_url_from_chat_url("https://api.z.ai/api/coding/paas/v4/chat/completions") == (
        "https://api.z.ai/api/coding/paas/v4"
    )
    assert _base_url_from_chat_url("http://127.0.0.1:1234/v1/chat/completions") == (
        "http://127.0.0.1:1234/v1"
    )


def test_openai_patcher_prefers_z_api_env_for_missing_key(tmp_path, monkeypatch) -> None:
    captured: dict[str, str] = {}

    def _fake_diff(**kwargs) -> PatchResult:
        captured["api_key"] = str(kwargs.get("api_key") or "")
        return PatchResult(diff=None, meta={"error": "no_diff_found"})

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_diff",
        _fake_diff,
    )
    monkeypatch.setenv("Z_API", "z-key")
    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    monkeypatch.delenv("LLMDEBUG_EVAL_OPENAI_API_KEY", raising=False)

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="diff",
        force_patch_attempt=False,
        api_key=None,
    )
    _ = patcher.propose_patch(
        prompt="Pytest output (stdout):\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )
    assert captured["api_key"] == "z-key"


def test_openai_edits_fallback_aggregates_call_and_token_usage(tmp_path, monkeypatch) -> None:
    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {"choices": [{"message": {"content": "not json"}}]},
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 5, "completion_tokens": 2, "total_tokens": 7},
            },
        )

    def _fake_diff(**kwargs) -> PatchResult:
        del kwargs
        return PatchResult(
            diff=(
                "diff --git a/buggy.py b/buggy.py\n"
                "--- a/buggy.py\n"
                "+++ b/buggy.py\n"
                "@@ -1 +1 @@\n"
                "-old\n"
                "+new\n"
            ),
            meta={
                "llm_call_count": 2,
                "usage": {"prompt_tokens": 11, "completion_tokens": 4, "total_tokens": 15},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_diff",
        _fake_diff,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output (stdout):\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert int(result.meta["llm_call_count"]) == 5
    usage = result.meta.get("usage")
    assert isinstance(usage, dict)
    assert int(usage.get("prompt_tokens") or 0) == 26
    assert int(usage.get("completion_tokens") or 0) == 10
    assert int(usage.get("total_tokens") or 0) == 36


def test_request_context_retry_aggregates_usage_when_response_format_is_rejected(
    tmp_path, monkeypatch
) -> None:
    calls = {"count": 0}

    def _fake_chat_completion(**kwargs):
        del kwargs
        calls["count"] += 1
        if calls["count"] == 1:
            return (
                None,
                {
                    "error": "http_error",
                    "body": "response_format not supported",
                    "llm_call_count": 1,
                    "usage": {"prompt_tokens": 3, "completion_tokens": 0, "total_tokens": 3},
                },
            )
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"need_more_context":false,"requests":[],'
                                + '"sufficiency_rationale":"ok"}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 2, "completion_tokens": 1, "total_tokens": 3},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
    )
    result = patcher.request_context(prompt="x", context_dir=tmp_path, cwd=tmp_path)
    assert result is not None
    assert result.meta is not None
    assert int(result.meta["llm_call_count"]) == 2
    usage = result.meta.get("usage")
    assert isinstance(usage, dict)
    assert int(usage.get("prompt_tokens") or 0) == 5
    assert int(usage.get("completion_tokens") or 0) == 1
    assert int(usage.get("total_tokens") or 0) == 6


def test_openai_diff_retry_does_not_prefill_assistant(tmp_path, monkeypatch) -> None:
    captured_messages: list[list[dict[str, str]]] = []

    def _fake_chat_completion(**kwargs):
        messages = kwargs.get("messages")
        if isinstance(messages, list):
            captured_messages.append(messages)
        return (
            None,
            {
                "error": "http_error",
                "body": "upstream transient",
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 1, "completion_tokens": 0, "total_tokens": 1},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="diff",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is None
    assert len(captured_messages) == 2
    assert all(
        all(isinstance(message, dict) and message.get("role") != "assistant" for message in call)
        for call in captured_messages
    )
    second_system = captured_messages[1][0]
    assert "Start your response with 'diff --git a/'." in str(second_system.get("content"))


def test_openai_diff_no_diff_found_falls_back_to_edits(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_diff(**kwargs) -> PatchResult:
        del kwargs
        return PatchResult(
            diff=None,
            meta={
                "error": "no_diff_found",
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 3, "completion_tokens": 1, "total_tokens": 4},
            },
        )

    def _fake_edits(**kwargs) -> PatchResult:
        assert kwargs.get("allow_diff_fallback") is False
        return PatchResult(
            diff=(
                "diff --git a/buggy.py b/buggy.py\n"
                "--- a/buggy.py\n"
                "+++ b/buggy.py\n"
                "@@ -1,2 +1,2 @@\n"
                " def f():\n"
                "-    return 1\n"
                "+    return 2\n"
            ),
            meta={
                "structured_output_mode": "json_schema",
                "llm_call_count": 2,
                "usage": {"prompt_tokens": 5, "completion_tokens": 2, "total_tokens": 7},
            },
        )

    monkeypatch.setattr("evals.patchers.openai_compat_patcher._propose_patch_via_diff", _fake_diff)
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_edits", _fake_edits
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="diff",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("fallback_from") == "diff"
    assert result.meta.get("fallback_reason") == "no_diff_found"
    fallback_meta = result.meta.get("fallback_meta")
    assert isinstance(fallback_meta, dict)
    assert fallback_meta.get("error") == "no_diff_found"
    assert int(result.meta.get("llm_call_count") or 0) == 3
    usage = result.meta.get("usage")
    assert isinstance(usage, dict)
    assert int(usage.get("prompt_tokens") or 0) == 8
    assert int(usage.get("completion_tokens") or 0) == 3
    assert int(usage.get("total_tokens") or 0) == 11


def test_openai_edits_fallback_sequence_json_schema_json_object_plain(
    tmp_path, monkeypatch
) -> None:
    call_extras: list[object] = []
    calls = {"count": 0}

    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        calls["count"] += 1
        call_extras.append(kwargs.get("extra_payload"))
        if calls["count"] == 1:
            return (
                None,
                {
                    "error": "http_error",
                    "body": "json_schema is not supported",
                    "llm_call_count": 1,
                    "usage": {"prompt_tokens": 2, "completion_tokens": 0, "total_tokens": 2},
                },
            )
        if calls["count"] == 2:
            return (
                None,
                {
                    "error": "http_error",
                    "body": "response_format not supported",
                    "llm_call_count": 1,
                    "usage": {"prompt_tokens": 2, "completion_tokens": 0, "total_tokens": 2},
                },
            )
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"return 1",'
                                '"replace":"return 2","count":1}]}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 3, "completion_tokens": 4, "total_tokens": 7},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert len(call_extras) == 3
    assert isinstance(call_extras[0], dict)
    assert call_extras[0]["response_format"]["type"] == "json_schema"
    assert isinstance(call_extras[1], dict)
    assert call_extras[1]["response_format"]["type"] == "json_object"
    assert call_extras[2] is None
    assert result.meta is not None
    assert result.meta.get("structured_output_mode") == "plain"
    assert int(result.meta.get("llm_call_count") or 0) == 3
    attempts = result.meta.get("structured_output_attempts")
    assert isinstance(attempts, list)
    assert [entry.get("mode") for entry in attempts] == ["json_schema", "json_object", "plain"]

    applied, message = run_eval.apply_diff(tmp_path, result.diff)
    assert applied, message
    assert (tmp_path / "buggy.py").read_text(encoding="utf-8") == "def f():\n    return 2\n"


def test_openai_edits_records_reasoning_content_metadata(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"return 1",'
                                '"replace":"return 2","count":1}]}'
                            ),
                            "reasoning_content": "step by step reasoning",
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("reasoning_content_present") is True
    assert int(result.meta.get("reasoning_content_chars") or 0) > 0


def test_openai_edits_content_parts_ignore_reasoning_chunks(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": [
                                {"type": "reasoning", "text": "internal thoughts"},
                                {
                                    "type": "output_text",
                                    "text": (
                                        '{"edits":[{"path":"buggy.py","find":"return 1",'
                                        '"replace":"return 2","count":1}]}'
                                    ),
                                },
                            ],
                            "reasoning_content": "internal thoughts",
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    applied, message = run_eval.apply_diff(tmp_path, result.diff)
    assert applied, message
    assert (tmp_path / "buggy.py").read_text(encoding="utf-8") == "def f():\n    return 2\n"


def test_openai_edits_parse_failure_advances_to_next_stage(tmp_path, monkeypatch) -> None:
    call_extras: list[object] = []
    calls = {"count": 0}
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        calls["count"] += 1
        call_extras.append(kwargs.get("extra_payload"))
        if calls["count"] == 1:
            return (
                {
                    "choices": [
                        {
                            "message": {
                                "content": '{"edits": [ {"path":"buggy.py","find":"return 1"',
                            }
                        }
                    ]
                },
                {
                    "llm_call_count": 1,
                    "usage": {"prompt_tokens": 2, "completion_tokens": 2, "total_tokens": 4},
                },
            )
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"return 1",'
                                '"replace":"return 2","count":1}]}'
                            ),
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 3, "completion_tokens": 3, "total_tokens": 6},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert len(call_extras) >= 2
    assert isinstance(call_extras[0], dict)
    assert call_extras[0]["response_format"]["type"] == "json_schema"
    assert isinstance(call_extras[1], dict)
    assert call_extras[1]["response_format"]["type"] == "json_object"
    assert result.meta is not None
    assert result.meta.get("structured_output_mode") == "json_object"
    attempts = result.meta.get("structured_output_attempts")
    assert isinstance(attempts, list)
    assert attempts[0].get("parse_error") == "no_json_payload"


def test_openai_edits_accepts_single_edit_root_shape(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"path":"buggy.py","find":"return 1","replace":"return 2","count":1}'
                            ),
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 2, "completion_tokens": 2, "total_tokens": 4},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("edits_payload_shape") == "single_edit_root"
    applied, message = run_eval.apply_diff(tmp_path, result.diff)
    assert applied, message
    assert (tmp_path / "buggy.py").read_text(encoding="utf-8") == "def f():\n    return 2\n"


def test_openai_edits_find_recovery_normalized_whitespace(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_bytes(b"def f():\r\n    value = 1   \r\n    return value\r\n")

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"def f():\\n    value = 1\\n'
                                '    return value\\n","replace":"def f():\\n    value = 2\\n'
                                '    return value\\n","count":1}]}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 3, "completion_tokens": 3, "total_tokens": 6},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("find_recovery_applied") is True
    assert result.meta.get("find_recovery_mode") in {"normalized_newlines", "normalized_whitespace"}
    applied, message = run_eval.apply_diff(tmp_path, result.diff)
    assert applied, message
    assert "value = 2" in (tmp_path / "buggy.py").read_text(encoding="utf-8")


def test_openai_edits_find_recovery_fuzzy_block(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text(
        "def f(res, n):\n    return res // (n * (n - 1))\n",
        encoding="utf-8",
    )

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"return res // n * (n - 1)",'
                                '"replace":"return res // (n * (n + 1))","count":1}]}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 2, "completion_tokens": 2, "total_tokens": 4},
            },
        )

    def _unexpected_diff(**kwargs) -> PatchResult:
        del kwargs
        raise AssertionError("diff fallback should not be used when fuzzy recovery succeeds")

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_diff",
        _unexpected_diff,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("find_recovery_applied") is True
    assert result.meta.get("find_recovery_mode") == "fuzzy_block"
    applied, message = run_eval.apply_diff(tmp_path, result.diff)
    assert applied, message
    assert "return res // (n * (n + 1))" in (tmp_path / "buggy.py").read_text(encoding="utf-8")


def test_openai_edits_fuzzy_recovery_skips_replace_all_count(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text(
        "def f(res, n):\n    return res // (n * (n - 1))\n",
        encoding="utf-8",
    )

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"return res // n * (n - 1)",'
                                '"replace":"return res // (n * (n + 1))","count":0}]}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 2, "completion_tokens": 2, "total_tokens": 4},
            },
        )

    def _fake_diff(**kwargs) -> PatchResult:
        del kwargs
        return PatchResult(
            diff=(
                "diff --git a/buggy.py b/buggy.py\n"
                "--- a/buggy.py\n"
                "+++ b/buggy.py\n"
                "@@ -1,2 +1,2 @@\n"
                " def f(res, n):\n"
                "-    return res // (n * (n - 1))\n"
                "+    return res // (n * (n + 1))\n"
            ),
            meta={
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_diff",
        _fake_diff,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("fallback_reason") == "find_not_found"
    applied, message = run_eval.apply_diff(tmp_path, result.diff)
    assert applied, message
    assert "return res // (n * (n + 1))" in (tmp_path / "buggy.py").read_text(encoding="utf-8")


def test_openai_edits_find_not_found_falls_back_to_diff(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"does_not_exist",'
                                '"replace":"return 2","count":1}]}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 2, "completion_tokens": 2, "total_tokens": 4},
            },
        )

    def _fake_diff(**kwargs) -> PatchResult:
        del kwargs
        return PatchResult(
            diff=(
                "diff --git a/buggy.py b/buggy.py\n"
                "--- a/buggy.py\n"
                "+++ b/buggy.py\n"
                "@@ -1,2 +1,2 @@\n"
                " def f():\n"
                "-    return 1\n"
                "+    return 2\n"
            ),
            meta={
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_diff",
        _fake_diff,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("fallback_reason") == "find_not_found"


def test_openai_edits_no_effective_changes_falls_back_to_diff(tmp_path, monkeypatch) -> None:
    (tmp_path / "buggy.py").write_text("def f():\n    return 1\n", encoding="utf-8")

    def _fake_chat_completion(**kwargs):
        del kwargs
        return (
            {
                "choices": [
                    {
                        "message": {
                            "content": (
                                '{"edits":[{"path":"buggy.py","find":"return 1",'
                                '"replace":"return 1","count":1}]}'
                            )
                        }
                    }
                ]
            },
            {
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 2, "completion_tokens": 2, "total_tokens": 4},
            },
        )

    def _fake_diff(**kwargs) -> PatchResult:
        del kwargs
        return PatchResult(
            diff=(
                "diff --git a/buggy.py b/buggy.py\n"
                "--- a/buggy.py\n"
                "+++ b/buggy.py\n"
                "@@ -1,2 +1,2 @@\n"
                " def f():\n"
                "-    return 1\n"
                "+    return 2\n"
            ),
            meta={
                "llm_call_count": 1,
                "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
            },
        )

    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._chat_completion",
        _fake_chat_completion,
    )
    monkeypatch.setattr(
        "evals.patchers.openai_compat_patcher._propose_patch_via_diff",
        _fake_diff,
    )

    patcher = OpenAICompatPatcher(
        base_url="http://127.0.0.1:1234",
        model="dummy",
        response_mode="edits",
        force_patch_attempt=False,
    )
    result = patcher.propose_patch(
        prompt="Pytest output:\nfailed\n", context_dir=tmp_path, cwd=tmp_path
    )

    assert result.diff is not None
    assert result.meta is not None
    assert result.meta.get("fallback_reason") == "no_effective_changes"


def test_chat_completion_reuses_thread_local_http_connection(monkeypatch) -> None:
    class _FakeResponse:
        def __init__(self, status: int, body: str, *, will_close: bool = False) -> None:
            self.status = status
            self.reason = "OK"
            self.will_close = will_close
            self._body = body.encode("utf-8")

        def read(self) -> bytes:
            return self._body

    created: list[_FakeHTTPConnection] = []

    class _FakeHTTPConnection:
        def __init__(self, host: str, port: int, timeout: float | None = None) -> None:
            del host, port, timeout
            self.request_count = 0
            created.append(self)

        def request(self, method: str, target: str, body: bytes, headers: dict[str, str]) -> None:
            del method, target, body, headers
            self.request_count += 1

        def getresponse(self) -> _FakeResponse:
            return _FakeResponse(
                200,
                '{"choices":[{"message":{"content":"{\\"edits\\":[]}"}}],'
                '"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}',
            )

        def close(self) -> None:
            return None

    monkeypatch.setattr(openai_mod, "_THREAD_HTTP_CLIENTS", threading.local())
    monkeypatch.setattr(openai_mod.http.client, "HTTPConnection", _FakeHTTPConnection)

    for _ in range(2):
        raw, meta = openai_mod._chat_completion(
            url="http://127.0.0.1:1234/v1/chat/completions",
            api_key="local",
            model="dummy",
            messages=[{"role": "user", "content": "hello"}],
            temperature=0.0,
            max_tokens=64,
            timeout_sec=30.0,
            seed=None,
        )
        assert raw is not None
        assert meta.get("error") is None

    assert len(created) == 1
    assert created[0].request_count == 2


def test_chat_completion_reconnects_after_broken_pipe(monkeypatch) -> None:
    class _FakeResponse:
        def __init__(self, status: int, body: str) -> None:
            self.status = status
            self.reason = "OK"
            self.will_close = False
            self._body = body.encode("utf-8")

        def read(self) -> bytes:
            return self._body

    state = {"request_calls": 0, "instances": 0}

    class _FlakyHTTPConnection:
        def __init__(self, host: str, port: int, timeout: float | None = None) -> None:
            del host, port, timeout
            state["instances"] += 1

        def request(self, method: str, target: str, body: bytes, headers: dict[str, str]) -> None:
            del method, target, body, headers
            state["request_calls"] += 1
            if state["request_calls"] == 1:
                raise BrokenPipeError("simulated broken pipe")

        def getresponse(self) -> _FakeResponse:
            return _FakeResponse(
                200,
                '{"choices":[{"message":{"content":"{\\"ok\\":true}"}}],'
                '"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}',
            )

        def close(self) -> None:
            return None

    monkeypatch.setattr(openai_mod, "_THREAD_HTTP_CLIENTS", threading.local())
    monkeypatch.setattr(openai_mod.http.client, "HTTPConnection", _FlakyHTTPConnection)

    raw, meta = openai_mod._chat_completion(
        url="http://127.0.0.1:1234/v1/chat/completions",
        api_key="local",
        model="dummy",
        messages=[{"role": "user", "content": "hello"}],
        temperature=0.0,
        max_tokens=64,
        timeout_sec=30.0,
        seed=None,
    )

    assert raw is not None
    assert meta.get("error") is None
    assert state["instances"] == 2


def test_chat_completion_uses_urllib_transport_when_proxy_enabled(monkeypatch) -> None:
    class _URLResponse:
        def __init__(self) -> None:
            self.status = 200

        def read(self) -> bytes:
            return (
                b'{"choices":[{"message":{"content":"{\\"ok\\":true}"}}],'
                b'"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}'
            )

        def __enter__(self) -> _URLResponse:
            return self

        def __exit__(self, exc_type, exc, tb) -> bool:
            del exc_type, exc, tb
            return False

    def _fake_urlopen(req, timeout):
        del req, timeout
        return _URLResponse()

    def _unexpected_client(*, url: str):
        raise AssertionError(
            f"persistent HTTP client should not be used when proxy is enabled: {url}"
        )

    monkeypatch.setattr(openai_mod, "_should_use_proxy_transport", lambda url: True)
    monkeypatch.setattr(openai_mod, "_get_thread_http_client", _unexpected_client)
    monkeypatch.setattr(openai_mod.urllib.request, "urlopen", _fake_urlopen)

    raw, meta = openai_mod._chat_completion(
        url="http://127.0.0.1:1234/v1/chat/completions",
        api_key="local",
        model="dummy",
        messages=[{"role": "user", "content": "hello"}],
        temperature=0.0,
        max_tokens=64,
        timeout_sec=30.0,
        seed=None,
    )

    assert raw is not None
    assert meta.get("error") is None
