import pytest
from unittest.mock import MagicMock, patch

from oiiai import (
    FetchBase,
    FetchZhipu,
    FetchOpenRouter,
    FetchModelScope,
    FetchSiliconFlow,
    FetchIFlow,
)


class DummyResponse:
    def __init__(self, json_data, status_code: int = 200, text: str = ""):
        self._json_data = json_data
        self.status_code = status_code
        self.text = text

    def json(self):
        return self._json_data

    def raise_for_status(self):
        if self.status_code >= 400:
            raise Exception(f"HTTP {self.status_code}")


@pytest.fixture(autouse=True)
def reset_session():
    """Reset the shared session before each test to ensure isolation."""
    FetchBase.close_session()
    yield
    FetchBase.close_session()


def test_fetch_base_is_abstract():
    with pytest.raises(TypeError):
        FetchBase()  # type: ignore[abstract]


def test_fetch_openrouter_fetch_models(monkeypatch):
    captured = {}

    def fake_http_get(self, url, headers=None, timeout=None):
        captured["url"] = url
        captured["headers"] = headers or {}
        captured["timeout"] = timeout
        return DummyResponse({"data": [{"id": "model-1"}, {"id": "model-2"}, {"name": "no-id"}]})

    monkeypatch.setattr(FetchBase, "_http_get", fake_http_get)

    fetcher = FetchOpenRouter(api_key="test-key")
    models = fetcher.fetch_models()

    assert models == ["model-1", "model-2"]
    assert captured["headers"].get("Authorization") == "Bearer test-key"


def test_fetch_modelscope_fetch_models_with_list(monkeypatch):
    def fake_http_get(self, url, headers=None, timeout=None):
        return DummyResponse(["a", "b"])

    monkeypatch.setattr(FetchBase, "_http_get", fake_http_get)

    fetcher = FetchModelScope()
    models = fetcher.fetch_models()

    assert models == ["a", "b"]


def test_fetch_siliconflow_fetch_models(monkeypatch):
    def fake_getenv(key, default=None):
        if key == "SILICONFLOW_API_KEY":
            return "dummy-key"
        return default

    def fake_http_get(self, url, headers=None, timeout=None):
        return DummyResponse({"data": [{"id": "m1"}, {"id": "m2"}]})

    monkeypatch.setattr("oiiai.fetchModelList.siliconflow.os.getenv", fake_getenv)
    monkeypatch.setattr(FetchBase, "_http_get", fake_http_get)

    fetcher = FetchSiliconFlow()
    models = fetcher.fetch_models()

    assert models == ["m1", "m2"]


def test_fetch_iflow_fetch_models(monkeypatch):
    def fake_http_post(self, url, json=None, headers=None, timeout=None):
        data = {
            "data": {
                "chat": [
                    {"modelName": "qwen-plus"},
                    {"showName": "deepseek"},
                    {"id": "other"},
                ]
            }
        }
        return DummyResponse(data)

    monkeypatch.setattr(FetchBase, "_http_post", fake_http_post)

    fetcher = FetchIFlow()
    models = fetcher.fetch_models()

    # Expect flattened list of model names as strings
    assert models == ["qwen-plus", "deepseek", "other"]


def test_fetch_zhipu_extract_model_names():
    html = """
    <h2 id="通用模型"></h2>
    <table>
      <tbody>
        <tr><td>glm-4</td></tr>
        <tr><td>glm-4-flash</td></tr>
      </tbody>
    </table>
    """

    fetcher = FetchZhipu()
    result, strategy_used = fetcher._extract_model_names(html)

    assert "通用模型" in result
    assert result["通用模型"] == ["glm-4", "glm-4-flash"]
    assert strategy_used == "section_id"


def test_fetch_zhipu_fallback_when_all_strategies_fail(monkeypatch):
    """Test that FetchZhipu returns fallback list when all strategies fail."""
    fallback_models = ["glm-4", "glm-4-flash", "glm-4-plus"]
    
    def fake_http_get(self, url, headers=None, timeout=None):
        # Return HTML that won't match any parsing strategy
        return DummyResponse({}, text="<html><body>No models here</body></html>")
    
    monkeypatch.setattr(FetchBase, "_http_get", fake_http_get)
    
    fetcher = FetchZhipu(fallback_models=fallback_models)
    models = fetcher.fetch_models()
    
    assert models == fallback_models


def test_fetch_zhipu_empty_list_when_no_fallback_and_all_strategies_fail(monkeypatch):
    """Test that FetchZhipu returns empty list when no fallback and all strategies fail."""
    def fake_http_get(self, url, headers=None, timeout=None):
        # Return HTML that won't match any parsing strategy
        return DummyResponse({}, text="<html><body>No models here</body></html>")
    
    monkeypatch.setattr(FetchBase, "_http_get", fake_http_get)
    
    fetcher = FetchZhipu()  # No fallback configured
    models = fetcher.fetch_models()
    
    assert models == []


def test_fetch_zhipu_fallback_on_http_error(monkeypatch):
    """Test that FetchZhipu returns fallback list on HTTP errors."""
    import requests
    
    fallback_models = ["glm-4", "glm-4-flash"]
    
    def fake_http_get(self, url, headers=None, timeout=None):
        raise requests.exceptions.ConnectionError("Connection failed")
    
    monkeypatch.setattr(FetchBase, "_http_get", fake_http_get)
    
    fetcher = FetchZhipu(fallback_models=fallback_models)
    models = fetcher.fetch_models()
    
    assert models == fallback_models
