from __future__ import annotations

import json
import os
import sys
import types
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
from urllib.parse import unquote

from behave import given, then


@dataclass
class _FakeOpenAiTranscriptionBehavior:
    transcript: Optional[str]
    result_kind: str = "object"
    no_speech_probabilities: Optional[List[float]] = None
    segments_payload: Optional[List[Any]] = None


@dataclass
class _FakeOpenAiChatBehavior:
    response: str
    match_text: Optional[str] = None


@dataclass
class _FakeOpenAiEmbeddingBehavior:
    vector: List[float]


class _ManualToolFunction:
    def __init__(self, name: str, arguments: str) -> None:
        self.name = name
        self.arguments = arguments


class _ManualToolCall:
    def __init__(self, tool_id: str, name: str, arguments: str) -> None:
        self.id = tool_id
        self.type = "function"
        self.function = _ManualToolFunction(name=name, arguments=arguments)


def _ensure_fake_openai_transcription_behaviors(
    context,
) -> Dict[str, _FakeOpenAiTranscriptionBehavior]:
    behaviors = getattr(context, "fake_openai_transcriptions", None)
    if behaviors is None:
        behaviors = {}
        context.fake_openai_transcriptions = behaviors
    return behaviors


def _ensure_fake_openai_chat_behaviors(context) -> List[_FakeOpenAiChatBehavior]:
    behaviors = getattr(context, "fake_openai_chat_behaviors", None)
    if behaviors is None:
        behaviors = []
        context.fake_openai_chat_behaviors = behaviors
    return behaviors


def _ensure_fake_openai_embedding_behaviors(context) -> Dict[str, _FakeOpenAiEmbeddingBehavior]:
    behaviors = getattr(context, "fake_openai_embeddings", None)
    if behaviors is None:
        behaviors = {}
        context.fake_openai_embeddings = behaviors
    return behaviors


def _install_fake_dspy_module(context) -> None:
    already_installed = getattr(context, "_fake_dspy_installed", False)
    if already_installed:
        return

    original_modules: Dict[str, object] = {}
    if "dspy" in sys.modules:
        original_modules["dspy"] = sys.modules["dspy"]

    chat_behaviors = _ensure_fake_openai_chat_behaviors(context)
    embedding_behaviors = _ensure_fake_openai_embedding_behaviors(context)

    class _DspyLM:  # noqa: N801 - external dependency uses PascalCase
        def __init__(self, model: str, **kwargs):  # type: ignore[no-untyped-def]
            dspy_module.last_lm_model = model  # type: ignore[attr-defined]
            dspy_module.last_lm_kwargs = dict(kwargs)  # type: ignore[attr-defined]

        def __call__(self, *, messages, **kwargs):  # type: ignore[no-untyped-def]
            dspy_module.last_chat_messages = list(messages)  # type: ignore[attr-defined]
            dspy_module.last_chat_kwargs = dict(kwargs)  # type: ignore[attr-defined]
            prompt_text = "\n".join(str(message.get("content", "")) for message in messages)
            dspy_module.last_chat_prompt = prompt_text  # type: ignore[attr-defined]
            response_text = ""
            for behavior in chat_behaviors:
                if behavior.match_text is None or behavior.match_text in prompt_text:
                    response_text = behavior.response
                    break

            tool_queue = getattr(dspy_module, "fake_tool_queue", [])
            if tool_queue:
                return [{"text": "", "tool_calls": [tool_queue.pop(0)]}]

            try:
                payload = json.loads(response_text)
            except json.JSONDecodeError:
                return [response_text]

            if not isinstance(payload, dict):
                return [response_text]

            operations = payload.get("operations") or []
            tool_calls: List[Any] = []
            tool_index = getattr(dspy_module, "fake_tool_call_index", 0)
            for operation in operations:
                tool_index += 1
                tool_calls.append(
                    {
                        "id": f"toolu_{tool_index}",
                        "type": "function",
                        "function": {
                            "name": "str_replace",
                            "arguments": json.dumps(
                                {
                                    "old_str": operation.get("old_str", ""),
                                    "new_str": operation.get("new_str", ""),
                                }
                            ),
                        },
                    }
                )
            if payload.get("done"):
                tool_index += 1
                tool_calls.append(
                    {
                        "id": f"toolu_{tool_index}",
                        "type": "function",
                        "function": {"name": "done", "arguments": json.dumps({})},
                    }
                )
            dspy_module.fake_tool_call_index = tool_index
            if payload.get("batch"):
                return [{"text": "", "tool_calls": tool_calls}]
            dspy_module.fake_tool_queue = tool_calls[1:]
            if tool_calls:
                return [{"text": "", "tool_calls": [tool_calls[0]]}]
            return [response_text]

    class _FakeArray:
        def __init__(self, values: Any) -> None:
            self._values = values

        def tolist(self) -> Any:
            return self._values

    class _DspyEmbedder:  # noqa: N801 - external dependency uses PascalCase
        def __init__(self, model: str, batch_size: int = 200, caching: bool = True, **kwargs):  # type: ignore[no-untyped-def]
            dspy_module.last_embedding_model = model  # type: ignore[attr-defined]
            dspy_module.last_embedding_kwargs = dict(kwargs)  # type: ignore[attr-defined]
            dspy_module.last_embedding_batch_size = batch_size  # type: ignore[attr-defined]
            dspy_module.last_embedding_caching = caching  # type: ignore[attr-defined]

        def __call__(self, inputs, batch_size=None, caching=None, **kwargs):  # type: ignore[no-untyped-def]
            inputs_list = inputs if isinstance(inputs, list) else [inputs]
            dspy_module.last_embedding_inputs = list(inputs_list)  # type: ignore[attr-defined]
            if batch_size is not None:
                dspy_module.last_embedding_batch_size = batch_size  # type: ignore[attr-defined]
            if caching is not None:
                dspy_module.last_embedding_caching = caching  # type: ignore[attr-defined]
            if kwargs:
                merged = dict(getattr(dspy_module, "last_embedding_kwargs", {}))
                merged.update(kwargs)
                dspy_module.last_embedding_kwargs = merged  # type: ignore[attr-defined]
            vectors: List[List[float]] = []
            for text in inputs_list:
                key = str(text)
                behavior = embedding_behaviors.get(key)
                if behavior is not None:
                    vectors.append([float(value) for value in behavior.vector])
                else:
                    vectors.append([float(len(key))])
            if isinstance(inputs, list):
                return _FakeArray(vectors)
            return _FakeArray(vectors[0])

    dspy_module = types.ModuleType("dspy")
    dspy_module.LM = _DspyLM
    dspy_module.Embedder = _DspyEmbedder
    dspy_module.last_lm_model = None
    dspy_module.last_lm_kwargs = {}
    dspy_module.last_chat_messages = []
    dspy_module.last_chat_kwargs = {}
    dspy_module.last_chat_prompt = None
    dspy_module.last_embedding_model = None
    dspy_module.last_embedding_inputs = []
    dspy_module.last_embedding_kwargs = {}
    dspy_module.last_embedding_batch_size = None
    dspy_module.last_embedding_caching = None
    dspy_module.fake_tool_queue = []
    dspy_module.fake_tool_call_index = 0

    sys.modules["dspy"] = dspy_module
    context._fake_dspy_installed = True
    context._fake_dspy_original_modules = original_modules


def _install_fake_litellm_module(context) -> None:
    already_installed = getattr(context, "_fake_litellm_installed", False)
    if already_installed:
        return

    original_modules: Dict[str, object] = {}
    if "litellm" in sys.modules:
        original_modules["litellm"] = sys.modules["litellm"]

    embedding_behaviors = _ensure_fake_openai_embedding_behaviors(context)

    def embedding(*, model: str, input: Any, **kwargs) -> Dict[str, Any]:  # type: ignore[no-untyped-def]
        litellm_module.last_embedding_model = model  # type: ignore[attr-defined]
        litellm_module.last_embedding_kwargs = dict(kwargs)  # type: ignore[attr-defined]
        inputs = input if isinstance(input, list) else [input]
        litellm_module.last_embedding_inputs = list(inputs)  # type: ignore[attr-defined]
        data: List[Dict[str, Any]] = []
        for text in inputs:
            key = str(text)
            behavior = embedding_behaviors.get(key)
            if behavior is not None:
                vector = [float(value) for value in behavior.vector]
            else:
                vector = [float(len(key))]
            data.append({"embedding": vector})
        return {"data": data}

    litellm_module = types.ModuleType("litellm")
    litellm_module.embedding = embedding
    litellm_module.last_embedding_model = None
    litellm_module.last_embedding_inputs = []
    litellm_module.last_embedding_kwargs = {}

    sys.modules["litellm"] = litellm_module
    context._fake_litellm_installed = True
    context._fake_litellm_original_modules = original_modules


def _install_fake_openai_module(context) -> None:
    already_installed = getattr(context, "_fake_openai_installed", False)
    if already_installed:
        return

    _install_fake_dspy_module(context)
    _install_fake_litellm_module(context)

    original_modules: Dict[str, object] = {}
    module_names = [
        "openai",
    ]
    for name in module_names:
        if name in sys.modules:
            original_modules[name] = sys.modules[name]

    behaviors = _ensure_fake_openai_transcription_behaviors(context)
    chat_behaviors = _ensure_fake_openai_chat_behaviors(context)
    embedding_behaviors = _ensure_fake_openai_embedding_behaviors(context)

    class _TranscriptionResult:
        def __init__(self, text: str) -> None:
            self.text = text

    class _TranscriptionsApi:
        def create(self, *, file, model: str, **kwargs):  # type: ignore[no-untyped-def]
            openai_module.last_transcription_model = model  # type: ignore[attr-defined]
            openai_module.last_transcription_kwargs = dict(kwargs)  # type: ignore[attr-defined]
            filename = getattr(file, "name", "unknown")
            base_name = filename.rsplit("/", 1)[-1]
            normalized_name = base_name.split("--", 1)[-1] if "--" in base_name else base_name
            openai_module.last_transcription_filename = normalized_name  # type: ignore[attr-defined]
            behavior = behaviors.get(normalized_name)
            if behavior is None:
                decoded_name = unquote(normalized_name)
                decoded_basename = decoded_name.rsplit("/", 1)[-1]
                behavior = behaviors.get(decoded_basename)
                if behavior is None:
                    behavior = behaviors.get(decoded_basename.split("--")[-1])
            transcript = behavior.transcript if behavior is not None else ""
            transcript_text = str(transcript or "")
            if behavior is not None and behavior.result_kind == "dict":
                return {"text": transcript_text}
            if behavior is not None and behavior.result_kind == "verbose_json":
                if behavior.segments_payload is not None:
                    return {"text": transcript_text, "segments": behavior.segments_payload}
                probabilities = behavior.no_speech_probabilities or []
                segments = [{"no_speech_prob": float(value)} for value in probabilities]
                return {"text": transcript_text, "segments": segments}
            return _TranscriptionResult(transcript_text)

    class _AudioApi:
        def __init__(self) -> None:
            self.transcriptions = _TranscriptionsApi()

    class _ChatCompletionMessage:
        def __init__(self, content: str, tool_calls: Optional[List[Any]] = None) -> None:
            self.content = content
            self.tool_calls = tool_calls or []

    class _ChatCompletionChoice:
        def __init__(self, content: str, tool_calls: Optional[List[Any]] = None) -> None:
            self.message = _ChatCompletionMessage(content, tool_calls=tool_calls)

    class _ChatCompletionResult:
        def __init__(self, content: str, tool_calls: Optional[List[Any]] = None) -> None:
            self.choices = [_ChatCompletionChoice(content, tool_calls=tool_calls)]

    class _ToolFunction:
        def __init__(self, name: str, arguments: str) -> None:
            self.name = name
            self.arguments = arguments

    class _ToolCall:
        def __init__(self, tool_id: str, name: str, arguments: str) -> None:
            self.id = tool_id
            self.type = "function"
            self.function = _ToolFunction(name=name, arguments=arguments)

    class _ChatCompletionsApi:
        def create(self, *, model: str, messages: List[Dict[str, Any]], **kwargs):  # type: ignore[no-untyped-def]
            openai_module.last_chat_model = model  # type: ignore[attr-defined]
            openai_module.last_chat_messages = list(messages)  # type: ignore[attr-defined]
            openai_module.last_chat_kwargs = dict(kwargs)  # type: ignore[attr-defined]
            prompt_text = "\n".join(str(message.get("content", "")) for message in messages)
            openai_module.last_chat_prompt = prompt_text  # type: ignore[attr-defined]
            response_text = ""
            for behavior in chat_behaviors:
                if behavior.match_text is None or behavior.match_text in prompt_text:
                    response_text = behavior.response
                    break

            tool_queue = getattr(openai_module, "fake_tool_queue", [])
            if tool_queue:
                return _ChatCompletionResult("", tool_calls=[tool_queue.pop(0)])

            try:
                payload = json.loads(response_text)
            except json.JSONDecodeError:
                return _ChatCompletionResult(response_text)

            if not isinstance(payload, dict):
                return _ChatCompletionResult(response_text)

            operations = payload.get("operations") or []
            tool_calls: List[Any] = []
            tool_index = getattr(openai_module, "fake_tool_call_index", 0)
            for operation in operations:
                tool_index += 1
                tool_calls.append(
                    _ToolCall(
                        tool_id=f"toolu_{tool_index}",
                        name="str_replace",
                        arguments=json.dumps(
                            {
                                "old_str": operation.get("old_str", ""),
                                "new_str": operation.get("new_str", ""),
                            }
                        ),
                    )
                )
            if payload.get("done"):
                tool_index += 1
                tool_calls.append(
                    _ToolCall(
                        tool_id=f"toolu_{tool_index}",
                        name="done",
                        arguments=json.dumps({}),
                    )
                )
            openai_module.fake_tool_call_index = tool_index
            if payload.get("batch"):
                return _ChatCompletionResult("", tool_calls=tool_calls)
            openai_module.fake_tool_queue = tool_calls[1:]
            if tool_calls:
                return _ChatCompletionResult("", tool_calls=[tool_calls[0]])
            return _ChatCompletionResult(response_text)

    class _ChatApi:
        def __init__(self) -> None:
            self.completions = _ChatCompletionsApi()

    class _EmbeddingRow:
        def __init__(self, embedding: List[float]) -> None:
            self.embedding = embedding

    class _EmbeddingsResult:
        def __init__(self, vectors: List[List[float]]) -> None:
            self.data = [_EmbeddingRow(vector) for vector in vectors]

    class _EmbeddingsApi:
        def create(self, *, model: str, input: Any, **kwargs):  # type: ignore[no-untyped-def]
            openai_module.last_embedding_model = model  # type: ignore[attr-defined]
            openai_module.last_embedding_kwargs = dict(kwargs)  # type: ignore[attr-defined]
            inputs = input if isinstance(input, list) else [input]
            openai_module.last_embedding_inputs = list(inputs)  # type: ignore[attr-defined]
            vectors: List[List[float]] = []
            for text in inputs:
                key = str(text)
                behavior = embedding_behaviors.get(key)
                if behavior is not None:
                    vectors.append([float(v) for v in behavior.vector])
                else:
                    vectors.append([float(len(key))])
            return _EmbeddingsResult(vectors)

    class OpenAI:  # noqa: N801 - external dependency uses PascalCase
        def __init__(self, **kwargs):  # type: ignore[no-untyped-def]
            openai_module.last_api_key = kwargs.get("api_key")  # type: ignore[attr-defined]
            self.audio = _AudioApi()
            self.chat = _ChatApi()
            self.embeddings = _EmbeddingsApi()

    openai_module = types.ModuleType("openai")
    openai_module.OpenAI = OpenAI
    openai_module.last_api_key = None
    openai_module.last_transcription_filename = None
    openai_module.last_transcription_model = None
    openai_module.last_transcription_kwargs = {}
    openai_module.last_chat_model = None
    openai_module.last_chat_messages = []
    openai_module.last_chat_kwargs = {}
    openai_module.last_chat_prompt = None
    openai_module.fake_tool_queue = []
    openai_module.fake_tool_call_index = 0
    openai_module.last_embedding_model = None
    openai_module.last_embedding_inputs = []
    openai_module.last_embedding_kwargs = {}

    sys.modules["openai"] = openai_module

    context._fake_openai_installed = True
    context._fake_openai_original_modules = original_modules


def _install_openai_unavailable_module(context) -> None:
    already_installed = getattr(context, "_fake_openai_unavailable_installed", False)
    if already_installed:
        return

    original_modules: Dict[str, object] = {}
    module_names = [
        "openai",
    ]
    for name in module_names:
        if name in sys.modules:
            original_modules[name] = sys.modules[name]

    openai_module = types.ModuleType("openai")
    sys.modules["openai"] = openai_module

    context._fake_openai_unavailable_installed = True
    context._fake_openai_unavailable_original_modules = original_modules


def _install_dspy_unavailable_module(context) -> None:
    already_installed = getattr(context, "_fake_dspy_unavailable_installed", False)
    if already_installed:
        return

    original_modules: Dict[str, object] = {}
    for name in ["dspy", "litellm"]:
        if name in sys.modules:
            original_modules[name] = sys.modules[name]

    dspy_module = types.ModuleType("dspy")
    sys.modules["dspy"] = dspy_module

    litellm_module = types.ModuleType("litellm")
    sys.modules["litellm"] = litellm_module

    context._fake_dspy_unavailable_installed = True
    context._fake_dspy_unavailable_original_modules = original_modules


def _install_dspy_missing_module(context) -> None:
    already_installed = getattr(context, "_fake_dspy_missing_installed", False)
    if already_installed:
        return

    original_modules: Dict[str, object] = {}
    if "dspy" in sys.modules:
        original_modules["dspy"] = sys.modules["dspy"]

    sys.modules["dspy"] = None

    context._fake_dspy_missing_installed = True
    context._fake_dspy_missing_original_modules = original_modules


@given("a fake OpenAI library is available")
def step_fake_openai_available(context) -> None:
    _install_fake_openai_module(context)


@given(
    'a fake OpenAI library is available that returns chat completion "{response}" for any prompt'
)
def step_fake_openai_returns_chat_completion(context, response: str) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_chat_behaviors(context)
    behaviors.append(_FakeOpenAiChatBehavior(response=response))


@given('a fake OpenAI library is available that returns chat completion "" for any prompt')
def step_fake_openai_returns_empty_chat_completion(context) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_chat_behaviors(context)
    behaviors.append(_FakeOpenAiChatBehavior(response=""))


@given(
    'a fake OpenAI library is available that returns chat completion "{response}" '
    'for prompt containing "{match_text}"'
)
def step_fake_openai_returns_chat_completion_for_prompt(
    context, response: str, match_text: str
) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_chat_behaviors(context)
    behaviors.append(_FakeOpenAiChatBehavior(response=response, match_text=match_text))


@given(
    'a fake OpenAI library is available that returns chat completion for prompt containing "{match_text}":'
)
def step_fake_openai_returns_chat_completion_for_prompt_block(context, match_text: str) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_chat_behaviors(context)
    response_text = str(getattr(context, "text", "") or "")
    behaviors.append(_FakeOpenAiChatBehavior(response=response_text, match_text=match_text))


@given(
    'a fake OpenAI library is available that returns embedding vector "{vector}" for input text "{text}"'
)
def step_fake_openai_returns_embedding_for_text(context, vector: str, text: str) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_embedding_behaviors(context)
    parsed: List[float] = []
    for token in vector.split(","):
        token = token.strip()
        if not token:
            continue
        parsed.append(float(token))
    behaviors[text] = _FakeOpenAiEmbeddingBehavior(vector=parsed)


@given(
    'a fake OpenAI library is available that returns transcript "{transcript}" for filename "{filename}"'
)
def step_fake_openai_returns_transcript(context, transcript: str, filename: str) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_transcription_behaviors(context)
    behaviors[filename] = _FakeOpenAiTranscriptionBehavior(transcript=transcript)


@given(
    'a fake OpenAI library is available that returns a dict transcript "{transcript}" for filename "{filename}"'
)
def step_fake_openai_returns_dict_transcript(context, transcript: str, filename: str) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_transcription_behaviors(context)
    behaviors[filename] = _FakeOpenAiTranscriptionBehavior(
        transcript=transcript, result_kind="dict"
    )


@given(
    'a fake OpenAI library is available that returns verbose transcript "{transcript}" '
    'with no speech probabilities "{no_speech_probabilities}" for filename "{filename}"'
)
def step_fake_openai_returns_verbose_json_transcript(
    context,
    transcript: str,
    no_speech_probabilities: str,
    filename: str,
) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_transcription_behaviors(context)
    parsed: list[float] = []
    for token in (no_speech_probabilities or "").split(","):
        token = token.strip()
        if not token:
            continue
        parsed.append(float(token))
    behaviors[filename] = _FakeOpenAiTranscriptionBehavior(
        transcript=transcript,
        result_kind="verbose_json",
        no_speech_probabilities=parsed,
    )


@given(
    'a fake OpenAI library is available that returns verbose transcript "{transcript}" '
    'with segments "{segments_json}" for filename "{filename}"'
)
def step_fake_openai_returns_verbose_json_with_segments(
    context,
    transcript: str,
    segments_json: str,
    filename: str,
) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_transcription_behaviors(context)
    parsed = json.loads(segments_json)
    if not isinstance(parsed, list):
        raise AssertionError("segments_json must decode to a list")
    behaviors[filename] = _FakeOpenAiTranscriptionBehavior(
        transcript=transcript,
        result_kind="verbose_json",
        segments_payload=parsed,
    )


@given(
    'a fake OpenAI library is available that returns verbose transcript "{transcript}" '
    'for filename "{filename}" with segments:'
)
def step_fake_openai_returns_verbose_json_with_segments_block(
    context,
    transcript: str,
    filename: str,
) -> None:
    _install_fake_openai_module(context)
    behaviors = _ensure_fake_openai_transcription_behaviors(context)
    raw = (context.text or "").strip()
    parsed = json.loads(raw)
    if not isinstance(parsed, list):
        raise AssertionError("segments JSON must decode to a list")
    behaviors[filename] = _FakeOpenAiTranscriptionBehavior(
        transcript=transcript,
        result_kind="verbose_json",
        segments_payload=parsed,
    )


@then('the OpenAI transcription request used response format "{response_format}"')
def step_openai_transcription_used_response_format(context, response_format: str) -> None:
    _ = context
    openai_module = sys.modules.get("openai")
    assert openai_module is not None
    kwargs: Dict[str, Any] = getattr(openai_module, "last_transcription_kwargs", {})
    assert kwargs.get("response_format") == response_format


@then('the DSPy chat request used response format "{response_format}"')
def step_dspy_chat_used_response_format(context, response_format: str) -> None:
    _ = context
    dspy_module = sys.modules.get("dspy")
    assert dspy_module is not None
    kwargs: Dict[str, Any] = getattr(dspy_module, "last_chat_kwargs", {})
    assert kwargs.get("response_format") == {"type": response_format}


@given("an OpenAI API key is configured for this scenario")
def step_openai_api_key_configured(context) -> None:
    api_key = os.environ.get("OPENAI_API_KEY")
    if not api_key:
        scenario = getattr(context, "scenario", None)
        tags = set(getattr(scenario, "tags", [])) if scenario else set()
        feature = getattr(context, "feature", None)
        if feature is not None:
            tags.update(getattr(feature, "tags", []))
        if "openai" in tags or "integration" in tags:
            if scenario is not None:
                scenario.skip("OPENAI_API_KEY is required for OpenAI integration scenarios.")
            return
        api_key = "test-openai-key"
    extra_env = getattr(context, "extra_env", None)
    if extra_env is None:
        extra_env = {}
        context.extra_env = extra_env
    extra_env["OPENAI_API_KEY"] = api_key


@given("the OpenAI dependency is unavailable")
def step_openai_dependency_unavailable(context) -> None:
    _install_openai_unavailable_module(context)


@given("the DSPy dependency is unavailable")
def step_dspy_dependency_unavailable(context) -> None:
    _install_dspy_unavailable_module(context)


@given("the DSPy dependency is missing")
def step_dspy_dependency_missing(context) -> None:
    _install_dspy_missing_module(context)


@given('a fake OpenAI tool call is queued for "{tool_name}"')
def step_queue_openai_tool_call(context, tool_name: str) -> None:
    _install_fake_openai_module(context)
    dspy_module = sys.modules.get("dspy")
    assert dspy_module is not None
    tool_index = getattr(dspy_module, "fake_tool_call_index", 0) + 1
    tool_call = _ManualToolCall(
        tool_id=f"toolu_{tool_index}",
        name=tool_name,
        arguments=json.dumps({}),
    )
    dspy_module.fake_tool_call_index = tool_index
    dspy_module.fake_tool_queue.append(tool_call)


@given('a fake OpenAI tool call is queued for "{tool_name}" with arguments:')
def step_queue_openai_tool_call_with_args(context, tool_name: str) -> None:
    _install_fake_openai_module(context)
    dspy_module = sys.modules.get("dspy")
    assert dspy_module is not None
    raw_args = str(getattr(context, "text", "") or "")
    parsed_args = json.loads(raw_args)
    tool_index = getattr(dspy_module, "fake_tool_call_index", 0) + 1
    tool_call = _ManualToolCall(
        tool_id=f"toolu_{tool_index}",
        name=tool_name,
        arguments=json.dumps(parsed_args),
    )
    dspy_module.fake_tool_call_index = tool_index
    dspy_module.fake_tool_queue.append(tool_call)


@given('the OpenAI client was configured with API key "{expected_api_key}"')
@then('the OpenAI client was configured with API key "{expected_api_key}"')
def step_openai_client_configured_with_api_key(context, expected_api_key: str) -> None:
    _ = context
    openai_module = sys.modules.get("openai")
    assert openai_module is not None
    configured = getattr(openai_module, "last_api_key", None)
    assert configured == expected_api_key


@given("no OpenAI API key is configured")
def step_no_openai_api_key_configured(context) -> None:
    """Ensure no OpenAI API key is available from any source."""
    # Remove from environment
    if "OPENAI_API_KEY" in os.environ:
        if not hasattr(context, "_saved_openai_key"):
            context._saved_openai_key = os.environ["OPENAI_API_KEY"]
        del os.environ["OPENAI_API_KEY"]

    # Mock the config loader to return None for API key
    import biblicus.user_config as config_module
    original_load = getattr(config_module, "_original_load_user_config", None)
    if original_load is None:
        config_module._original_load_user_config = config_module.load_user_config

    def mock_load_user_config():
        from biblicus.user_config import BiblicusUserConfig
        return BiblicusUserConfig(openai=None, aldea=None)

    config_module.load_user_config = mock_load_user_config
