# retoor <retoor@molodetz.nl>

import httpx
from devplacepy.services import news as news_mod
from devplacepy.services import base as base_mod
from devplacepy.services.news import (
    NewsService,
    _extract_grade,
    _get_ai_key,
    _get_article_images,
)
from devplacepy.database import get_table
from devplacepy.utils import generate_uid
from tests.conftest import run_async
API_URL = "http://news.test/api"
AI_URL = "http://ai.test/v1/chat"
LINK_HIGH = "http://news.test/high"
LINK_LOW = "http://news.test/low"
class FakeResp_news_service:
    def __init__(self, json_data=None, text="", status=200):
        self._json = json_data
        self.text = text
        self.status_code = status

    def raise_for_status(self):
        if self.status_code >= 400:
            raise httpx.HTTPError(f"status {self.status_code}")

    def json(self):
        return self._json
class FakeClient_news_service:
    def __init__(self, articles):
        self.articles = articles

    async def __aenter__(self):
        return self

    async def __aexit__(self, *exc):
        return False

    async def get(self, url, timeout=None):
        if url == API_URL:
            return FakeResp_news_service(json_data={"articles": self.articles})
        return FakeResp_news_service(text='<img src="http://img.test/a.png">')

    async def post(self, url, json=None, headers=None, timeout=None):
        prompt = json["messages"][0]["content"]
        if "FailArticle" in prompt:
            return FakeResp_news_service(status=500, text="err")
        if "EmptyArticle" in prompt:
            return FakeResp_news_service(json_data={"choices": [{"message": {"content": ""}}]})
        if "BadArticle" in prompt:
            return FakeResp_news_service(
                json_data={"choices": [{"message": {"content": "no number"}}]}
            )
        grade = "9" if "HighArticle" in prompt else "3"
        return FakeResp_news_service(json_data={"choices": [{"message": {"content": grade}}]})
class FailingApiClient(FakeClient_news_service):
    async def get(self, url, timeout=None):
        if url == API_URL:
            raise httpx.HTTPError("api down")
        return FakeResp_news_service(text="")
def _settings_stub(threshold="7"):
    def fake_get_setting(key, default=None):
        return {
            "news_api_url": API_URL,
            "news_ai_url": AI_URL,
            "news_ai_model": "test-model",
            "news_grade_threshold": threshold,
            "news_ai_key": "",
        }.get(key, default)

    return fake_get_setting


def test_extract_grade_parsing():
    assert _extract_grade("8") == 8
    assert _extract_grade("Grade: 9") == 9
    assert _extract_grade("Score is 7/10") == 7
    assert _extract_grade("0") is None
    assert _extract_grade("11") is None
    assert _extract_grade("not a number") is None


def test_get_ai_key_env_precedence(monkeypatch):
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub())
    monkeypatch.setenv("NEWS_AI_KEY", "primary")
    assert _get_ai_key() == "primary"


def test_get_ai_key_setting_fallback(monkeypatch):
    monkeypatch.delenv("NEWS_AI_KEY", raising=False)
    monkeypatch.setattr(
        news_mod,
        "get_setting",
        lambda key, default=None: "from-setting" if key == "news_ai_key" else default,
    )
    assert _get_ai_key() == "from-setting"


def test_get_ai_key_internal_gateway_fallback(local_db, monkeypatch):
    from devplacepy.database import internal_gateway_key

    monkeypatch.delenv("NEWS_AI_KEY", raising=False)
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub())
    assert _get_ai_key() == internal_gateway_key()


def test_grade_article_empty_content_returns_none(local_db, monkeypatch):
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub())
    grade = run_async(
        NewsService()._grade_article(
            {"title": "EmptyArticle", "description": "d", "content": "c"},
            AI_URL,
            "m",
            FakeClient_news_service([]),
        )
    )
    assert grade is None


def test_grade_article_unparseable_returns_none(local_db, monkeypatch):
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub())
    grade = run_async(
        NewsService()._grade_article(
            {"title": "BadArticle", "description": "d", "content": "c"},
            AI_URL,
            "m",
            FakeClient_news_service([]),
        )
    )
    assert grade is None


def test_run_once_handles_api_failure(local_db, monkeypatch):
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub())
    monkeypatch.setattr(base_mod, "get_setting", _settings_stub())
    monkeypatch.setattr(
        news_mod.httpx, "AsyncClient", lambda *a, **k: FailingApiClient([])
    )
    run_async(NewsService().run_once())


def test_run_once_updates_existing_news_row(local_db, monkeypatch):
    external_id = f"news-{generate_uid()}"
    existing_uid = generate_uid()
    get_table("news").insert(
        {
            "deleted_at": None,
            "deleted_by": None,
            "uid": existing_uid,
            "external_id": external_id,
            "slug": "",
            "title": "Old Title",
            "status": "draft",
            "grade": 0,
            "synced_at": "2020-01-01",
        }
    )
    articles = [
        {
            "guid": external_id,
            "title": "HighArticle",
            "description": "d",
            "content": "c",
            "link": "",
            "feed_name": "Feed",
            "author": "A",
            "published": "2026-01-01",
        }
    ]
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub(threshold="7"))
    monkeypatch.setattr(base_mod, "get_setting", _settings_stub(threshold="7"))
    monkeypatch.setattr(
        news_mod.httpx, "AsyncClient", lambda *a, **k: FakeClient_news_service(articles)
    )

    run_async(NewsService().run_once())

    row = get_table("news").find_one(uid=existing_uid)
    assert row["title"] == "HighArticle"
    assert row["status"] == "published"
    assert get_table("news").count(external_id=external_id) == 1


def test_get_article_images_filters_and_dedupes():
    html = (
        '<img src="http://img.test/a.png">'
        "<img src='http://img.test/a.png'>"
        '<img src="http://img.test/b.svg">'
        '<img src="/relative.png">'
    )
    client = FakeClient_news_service([])

    async def fake_get(url, timeout=None):
        return FakeResp_news_service(text=html)

    client.get = fake_get
    images = run_async(_get_article_images("http://news.test/page", client))
    assert [img["url"] for img in images] == ["http://img.test/a.png"]


def test_run_once_publishes_grades_and_is_idempotent(local_db, monkeypatch):
    g_high, g_low, g_fail = (f"news-{generate_uid()}" for _ in range(3))
    articles = [
        {
            "guid": g_high,
            "title": "HighArticle",
            "description": "d",
            "content": "c",
            "link": LINK_HIGH,
            "feed_name": "Feed",
            "author": "A",
            "published": "2026-01-01",
        },
        {
            "guid": g_low,
            "title": "LowArticle",
            "description": "d",
            "content": "c",
            "link": LINK_LOW,
            "feed_name": "Feed",
            "author": "A",
            "published": "2026-01-01",
        },
        {
            "guid": g_fail,
            "title": "FailArticle",
            "description": "d",
            "content": "c",
            "link": "",
            "feed_name": "Feed",
            "author": "A",
            "published": "2026-01-01",
        },
        {
            "guid": "",
            "title": "NoGuid",
            "description": "d",
            "content": "c",
            "link": "",
            "feed_name": "Feed",
            "author": "A",
            "published": "2026-01-01",
        },
    ]
    monkeypatch.setattr(news_mod, "get_setting", _settings_stub(threshold="7"))
    monkeypatch.setattr(base_mod, "get_setting", _settings_stub(threshold="7"))
    monkeypatch.setattr(
        news_mod.httpx, "AsyncClient", lambda *a, **k: FakeClient_news_service(articles)
    )

    run_async(NewsService().run_once())

    news = get_table("news")
    sync = get_table("news_sync")
    assert news.find_one(external_id=g_high)["status"] == "published"
    assert news.find_one(external_id=g_high)["grade"] == 9
    assert news.find_one(external_id=g_low)["status"] == "draft"
    assert news.find_one(external_id=g_low)["grade"] == 3
    fail_row = news.find_one(external_id=g_fail)
    assert fail_row["status"] == "draft"
    assert fail_row["grade"] == 0
    assert sync.find_one(external_id=g_fail)["status"] == "grading_failed"
    assert sync.find_one(external_id=g_high)["status"] == "graded"

    run_async(NewsService().run_once())
    assert news.count(external_id=g_high) == 1
    assert news.count(external_id=g_fail) == 1
