import importlib
import sys
from types import ModuleType, SimpleNamespace

import pytest


def _load_gemini_module(monkeypatch, request):
    fake_pm = SimpleNamespace(
        is_installed=lambda name: True,
        install=lambda name: None,
    )

    class FakeGenerateContentConfig:
        def __init__(self, **kwargs):
            self.kwargs = kwargs

    class FakeHttpOptions:
        def __init__(self, **kwargs):
            self.kwargs = kwargs

    fake_types = SimpleNamespace(
        GenerateContentConfig=FakeGenerateContentConfig,
        HttpOptions=FakeHttpOptions,
    )
    fake_genai = SimpleNamespace(Client=lambda **kwargs: SimpleNamespace(kwargs=kwargs))
    fake_google_module = ModuleType("google")
    fake_google_module.genai = fake_genai
    fake_api_exceptions = SimpleNamespace(
        InternalServerError=type("InternalServerError", (Exception,), {}),
        ServiceUnavailable=type("ServiceUnavailable", (Exception,), {}),
        ResourceExhausted=type("ResourceExhausted", (Exception,), {}),
        GatewayTimeout=type("GatewayTimeout", (Exception,), {}),
        BadGateway=type("BadGateway", (Exception,), {}),
        DeadlineExceeded=type("DeadlineExceeded", (Exception,), {}),
        Aborted=type("Aborted", (Exception,), {}),
        Unknown=type("Unknown", (Exception,), {}),
    )
    fake_google_api_core = ModuleType("google.api_core")
    fake_google_api_core.exceptions = fake_api_exceptions

    monkeypatch.setitem(sys.modules, "pipmaster", fake_pm)
    monkeypatch.setitem(sys.modules, "google", fake_google_module)
    monkeypatch.setitem(sys.modules, "google.genai", SimpleNamespace(types=fake_types))
    monkeypatch.setitem(sys.modules, "google.api_core", fake_google_api_core)
    monkeypatch.setitem(sys.modules, "google.api_core.exceptions", fake_api_exceptions)

    # Force a fresh import of lightrag.llm.gemini against the fakes above,
    # and restore the original module (or absence) on teardown — otherwise
    # subsequent tests (e.g. tests/llm/test_asymmetric_embedding.py) inherit
    # this stubbed `genai.types` namespace and break with AttributeError on
    # types.EmbedContentConfig. Note: clearing sys.modules alone is not
    # enough — Python also caches the submodule as an attribute on the parent
    # package, and `from lightrag.llm import gemini` resolves via that
    # attribute. Both pointers must be cleared.
    parent = sys.modules.get("lightrag.llm")
    original_gemini = sys.modules.get("lightrag.llm.gemini")
    original_parent_attr = getattr(parent, "gemini", None) if parent else None
    sys.modules.pop("lightrag.llm.gemini", None)
    if parent is not None and hasattr(parent, "gemini"):
        delattr(parent, "gemini")

    def _restore_gemini():
        if original_gemini is not None:
            sys.modules["lightrag.llm.gemini"] = original_gemini
        else:
            sys.modules.pop("lightrag.llm.gemini", None)
        if parent is not None:
            if original_parent_attr is not None:
                parent.gemini = original_parent_attr
            elif hasattr(parent, "gemini"):
                delattr(parent, "gemini")

    request.addfinalizer(_restore_gemini)

    return importlib.import_module("lightrag.llm.gemini")


def _make_fake_gemini_response(regular_text="", thought_text=""):
    parts = []
    if thought_text:
        parts.append(SimpleNamespace(text=thought_text, thought=True))
    if regular_text:
        parts.append(SimpleNamespace(text=regular_text, thought=False))

    return SimpleNamespace(
        candidates=[
            SimpleNamespace(content=SimpleNamespace(parts=parts)),
        ],
        usage_metadata=SimpleNamespace(
            prompt_token_count=1,
            candidates_token_count=2,
            total_token_count=3,
        ),
    )


@pytest.mark.offline
def test_gemini_maps_schema_response_format_to_response_json_schema(
    monkeypatch, request
):
    gemini_module = _load_gemini_module(monkeypatch, request)

    schema = {
        "type": "object",
        "properties": {"answer": {"type": "string"}},
        "required": ["answer"],
    }
    config = gemini_module._build_generation_config(
        base_config=None,
        system_prompt=None,
        response_format=schema,
    )

    assert config.kwargs["response_mime_type"] == "application/json"
    assert config.kwargs["response_json_schema"] == schema
    assert "response_schema" not in config.kwargs


@pytest.mark.offline
def test_gemini_unwraps_openai_json_schema_wrapper(monkeypatch, request):
    gemini_module = _load_gemini_module(monkeypatch, request)

    schema = {
        "type": "object",
        "properties": {"answer": {"type": "string"}},
        "required": ["answer"],
    }
    response_format = {
        "type": "json_schema",
        "json_schema": {
            "name": "answer_payload",
            "schema": schema,
        },
    }

    config = gemini_module._build_generation_config(
        base_config=None,
        system_prompt=None,
        response_format=response_format,
    )

    assert config.kwargs["response_mime_type"] == "application/json"
    assert config.kwargs["response_json_schema"] == schema


@pytest.mark.offline
def test_gemini_rejects_typed_response_format(monkeypatch, request):
    gemini_module = _load_gemini_module(monkeypatch, request)

    class FakeSchemaModel:
        pass

    with pytest.raises(TypeError, match="typed/Pydantic"):
        gemini_module._validate_gemini_response_format(FakeSchemaModel)


@pytest.mark.offline
def test_gemini_default_service_root_is_not_treated_as_custom_base_url(
    monkeypatch, request
):
    gemini_module = _load_gemini_module(monkeypatch, request)
    gemini_module._get_gemini_client.cache_clear()
    monkeypatch.delenv("GOOGLE_GENAI_USE_VERTEXAI", raising=False)

    client = gemini_module._get_gemini_client(
        "test-key",
        "https://generativelanguage.googleapis.com",
        1234,
    )

    assert client.kwargs["api_key"] == "test-key"
    assert "http_options" in client.kwargs
    assert client.kwargs["http_options"].kwargs == {"timeout": 1234}


@pytest.mark.offline
def test_gemini_custom_base_url_is_preserved(monkeypatch, request):
    gemini_module = _load_gemini_module(monkeypatch, request)
    gemini_module._get_gemini_client.cache_clear()
    monkeypatch.delenv("GOOGLE_GENAI_USE_VERTEXAI", raising=False)

    client = gemini_module._get_gemini_client(
        "test-key",
        "https://proxy.example.com",
        1234,
    )

    assert client.kwargs["http_options"].kwargs == {
        "base_url": "https://proxy.example.com",
        "timeout": 1234,
    }


@pytest.mark.offline
@pytest.mark.asyncio
async def test_gemini_streaming_structured_output_disables_cot(monkeypatch, request):
    gemini_module = _load_gemini_module(monkeypatch, request)

    fake_stream_response = _make_fake_gemini_response(
        regular_text='{"answer":"ok"}',
        thought_text="this should not be included",
    )

    async def _single_chunk_stream(response):
        yield response

    async def _fake_generate_content_stream(**kwargs):
        return _single_chunk_stream(fake_stream_response)

    fake_client = SimpleNamespace(
        aio=SimpleNamespace(
            models=SimpleNamespace(
                generate_content_stream=_fake_generate_content_stream
            )
        )
    )

    monkeypatch.setattr(gemini_module, "_get_gemini_client", lambda *args: fake_client)

    stream = await gemini_module.gemini_complete_if_cache(
        model="gemini-model",
        prompt="hello",
        stream=True,
        enable_cot=True,
        response_format={"type": "json_object"},
        api_key="test-key",
    )
    chunks = []
    async for chunk in stream:
        chunks.append(chunk)

    assert "".join(chunks) == '{"answer":"ok"}'
