# retoor <retoor@molodetz.nl>

import types
import devplacepy.seo as seo
import devplacepy.attachments as attach


def _seo_request():
    return types.SimpleNamespace(
        base_url="https://x.test/",
        url=types.SimpleNamespace(path="/posts/abc"),
        query_params={},
    )


def test_truncate_no_overflow():
    assert len(seo.truncate("x" * 200)) <= 160
    assert seo.truncate("short text") == "short text"
    assert seo.truncate("") == ""


def test_software_application_url_is_per_project():
    schema = seo.software_application_schema(
        {"uid": "p1", "slug": "p1-foo", "title": "P"}, "https://x.test"
    )
    assert schema["url"] == "https://x.test/projects/p1-foo"


def test_schema_types():
    assert seo.organization_schema("https://x.test")["@type"] == "Organization"
    assert (
        seo.news_article_schema(
            {"uid": "n1", "title": "T", "synced_at": "2026-01-01"}, "https://x.test"
        )["@type"]
        == "NewsArticle"
    )
    assert (
        seo.software_source_code_schema(
            {"uid": "g1", "title": "G", "language": "python"}, "https://x.test"
        )["@type"]
        == "SoftwareSourceCode"
    )


def test_news_article_publisher_is_organization():
    schema = seo.news_article_schema(
        {"uid": "n1", "title": "T", "synced_at": "2026-01-01"}, "https://x.test"
    )
    assert schema["publisher"]["@type"] == "Organization"


def test_site_url_precedence(monkeypatch):
    import devplacepy.database as database

    monkeypatch.setattr(seo, "SITE_URL", "https://constant.test")

    monkeypatch.setattr(
        database, "get_setting", lambda *a, **k: "https://setting.test"
    )
    assert seo.site_url(None) == "https://setting.test"

    monkeypatch.setattr(database, "get_setting", lambda *a, **k: "")
    assert seo.site_url(None) == "https://constant.test"

    monkeypatch.setattr(seo, "SITE_URL", "")
    request = types.SimpleNamespace(base_url="https://req.test/")
    assert seo.site_url(request) == "https://req.test"


def test_site_url_falls_back_to_request(monkeypatch):
    monkeypatch.setattr(seo, "SITE_URL", "")
    request = types.SimpleNamespace(base_url="http://fallback.test/")
    assert seo.site_url(request) == "http://fallback.test"


def test_upload_allowlist_excludes_dangerous_types():
    assert ".svg" not in attach.ALLOWED_UPLOAD_TYPES
    assert ".html" not in attach.ALLOWED_UPLOAD_TYPES
    assert ".png" in attach.ALLOWED_UPLOAD_TYPES
    assert ".svg" not in attach.POST_IMAGE_EXTENSIONS


def test_detect_mime_neutralizes_dangerous_types():
    assert attach._detect_mime(b"", "x.svg") == "application/octet-stream"
    assert attach._detect_mime(b"", "x.html") == "application/octet-stream"
    assert attach._detect_mime(b"", "x.png") == "image/png"


def test_base_seo_context_emits_keywords_key():
    ctx = seo.base_seo_context(
        _seo_request(),
        title="A Post",
        description="Body text",
        keywords="python, sqlite",
    )
    assert "meta_keywords" in ctx
    assert ctx["meta_keywords"] == "python, sqlite"


def test_base_seo_context_default_keywords_empty_without_target():
    ctx = seo.base_seo_context(
        _seo_request(), title="A Post", description="Body text"
    )
    assert ctx["meta_keywords"] == ""


def test_base_seo_context_description_strips_markdown(monkeypatch):
    monkeypatch.setattr(seo, "_ready_seo_metadata", lambda target: None)
    ctx = seo.base_seo_context(
        _seo_request(),
        title="A Post",
        description="## Heading\n\nThis is **bold** body with `code`.",
    )
    assert "##" not in ctx["meta_description"]
    assert "**" not in ctx["meta_description"]
    assert "`" not in ctx["meta_description"]
    assert "bold" in ctx["meta_description"]


def test_base_seo_context_plain_default_fills_fields_for_target(monkeypatch):
    monkeypatch.setattr(seo, "_ready_seo_metadata", lambda target: None)
    ctx = seo.base_seo_context(
        _seo_request(),
        title="Async Python Database Tooling",
        description="A **guide** to async python database tooling with sqlite.",
        seo_target=("post", "abc"),
    )
    assert ctx["meta_keywords"]
    assert "python" in ctx["meta_keywords"]
    assert ctx["meta_description"]
    assert "**" not in ctx["meta_description"]


def test_base_seo_context_consumes_ready_metadata(monkeypatch):
    monkeypatch.setattr(
        seo,
        "_ready_seo_metadata",
        lambda target: {
            "seo_title": "Generated Title",
            "seo_description": "Generated clean description",
            "seo_keywords": "generated, metadata",
        },
    )
    ctx = seo.base_seo_context(
        _seo_request(),
        title="Raw **markdown** title",
        description="raw markdown body",
        seo_target=("post", "abc"),
    )
    assert "Generated Title" in ctx["page_title"]
    assert ctx["meta_description"] == "Generated clean description"
    assert ctx["meta_keywords"] == "generated, metadata"


def _comment_item(uid, content, children=None, author="alice"):
    return {
        "comment": {"uid": uid, "content": content, "created_at": "2026-01-01T00:00:00+00:00"},
        "author": {"username": author},
        "children": children or [],
    }


def test_comment_schema_shapes_a_comment_type():
    item = _comment_item("c1", "Nice post, thanks!")
    schema = seo.comment_schema(item, "https://x.test")
    assert schema["@type"] == "Comment"
    assert schema["text"] == "Nice post, thanks!"
    assert schema["author"] == {
        "@type": "Person",
        "name": "alice",
        "url": "https://x.test/profile/alice",
    }
    assert schema["datePublished"] == "2026-01-01T00:00:00+00:00"


def test_comment_schema_handles_a_missing_author():
    item = _comment_item("c1", "text", author=None)
    item["author"] = None
    schema = seo.comment_schema(item, "https://x.test")
    assert schema["author"] == {"@type": "Person", "name": "Unknown", "url": ""}


def test_comment_schema_list_flattens_replies_depth_first():
    reply = _comment_item("c2", "a reply")
    top = _comment_item("c1", "a top-level comment", children=[reply])
    flat = seo.comment_schema_list([top], "https://x.test")
    assert [c["text"] for c in flat] == ["a top-level comment", "a reply"]


def test_comment_schema_list_respects_the_limit():
    tree = [_comment_item(f"c{i}", f"comment {i}") for i in range(30)]
    flat = seo.comment_schema_list(tree, "https://x.test", limit=5)
    assert len(flat) == 5


def test_discussion_forum_posting_omits_comment_key_without_comments():
    schema = seo.discussion_forum_posting(
        {"uid": "p1", "title": "T", "content": "body", "created_at": "2026-01-01"},
        {"username": "alice"},
        0,
        0,
        "https://x.test",
    )
    assert "comment" not in schema


def test_discussion_forum_posting_embeds_nested_comments():
    top = _comment_item("c1", "a top-level comment")
    schema = seo.discussion_forum_posting(
        {"uid": "p1", "title": "T", "content": "body", "created_at": "2026-01-01"},
        {"username": "alice"},
        1,
        0,
        "https://x.test",
        comments=seo.comment_schema_list([top], "https://x.test"),
    )
    assert schema["comment"][0]["@type"] == "Comment"
    assert schema["comment"][0]["text"] == "a top-level comment"
