from __future__ import annotations

import json
import types
from pathlib import Path

import pytest
from rdflib import Graph

from worai.errors import UsageError
from worai.structured_data import debug, inputs, io, materialization, rendering, validation


def test_inputs_is_url_and_filter() -> None:
    assert inputs.is_url("https://example.com")
    assert not inputs.is_url("local.txt")
    assert inputs.filter_urls(["https://a", "https://b"], "a$", None) == ["https://a"]
    with pytest.raises(UsageError, match="No URLs matched"):
        inputs.filter_urls(["https://a"], "z$", None)


def test_urls_from_sitemap_and_resolve_input_urls(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    class _Col:
        def dropna(self):
            return self

        def astype(self, _kind):
            return self

        def tolist(self):
            return ["https://example.com/a", ""]

    class _Df:
        empty = False
        columns = ["loc"]

        def __getitem__(self, _key):
            return _Col()

    fake_adv = types.SimpleNamespace(sitemap_to_df=lambda _s: _Df())
    monkeypatch.setitem(__import__("sys").modules, "advertools", fake_adv)
    urls = inputs.urls_from_sitemap("https://example.com/sitemap.xml")
    assert urls == ["https://example.com/a"]

    local = tmp_path / "sitemap.xml"
    local.write_text("<xml/>", encoding="utf-8")
    monkeypatch.setattr(inputs, "urls_from_sitemap", lambda _v: ["https://example.com/p"])
    assert inputs.resolve_input_urls(str(local)) == ["https://example.com/p"]

    monkeypatch.setattr(inputs, "urls_from_sitemap", lambda _v: [])
    with pytest.raises(UsageError, match="No URLs found"):
        inputs.resolve_input_urls(str(local))

    monkeypatch.setattr(inputs, "urls_from_sitemap", lambda _v: (_ for _ in ()).throw(RuntimeError("boom")))
    assert inputs.resolve_input_urls("https://example.com/page") == ["https://example.com/page"]


def test_io_helpers(tmp_path: Path) -> None:
    out = tmp_path / "x" / "out.txt"
    io.write_output(out, "abc")
    assert out.read_text() == "abc"
    jsonld, yarrml = io.default_output_paths(tmp_path, "base")
    assert jsonld.name == "base.jsonld"
    assert yarrml.name == "base.yarrml"
    assert io.normalize_output_format("ttl") == ("turtle", "ttl")
    with pytest.raises(UsageError, match="Unsupported format"):
        io.normalize_output_format("bad")


def test_serialize_graph_and_debug(tmp_path: Path) -> None:
    graph = Graph()
    graph.parse(data='{"@context":"https://schema.org","@id":"https://e.com/#x","@type":"Thing"}', format="json-ld")
    text = io.serialize_graph(graph, "jsonld")
    assert text

    logs: list[str] = []
    missing = tmp_path / "missing.json"
    debug.echo_debug(missing, logs.append)
    broken = tmp_path / "broken.json"
    broken.write_text("{bad", encoding="utf-8")
    debug.echo_debug(broken, logs.append)
    assert any("Debug output written" in line for line in logs)

    payload = tmp_path / "ok.json"
    payload.write_text(json.dumps({"prompt": "p", "response": {"a": 1}}), encoding="utf-8")
    logs.clear()
    debug.echo_debug(payload, logs.append)
    assert logs[0] == "--- Agent prompt ---"


def test_materialization_pipeline() -> None:
    seen: list[str] = []

    class _Pipe:
        def normalize_mappings(self, *_args, **_kwargs):
            seen.append("normalize")
            return "norm", [{"m": 1}]

        def materialize_jsonld(self, *_args, **_kwargs):
            seen.append("materialize")
            return {"@id": "x"}

        def postprocess_jsonld(self, *_args, **_kwargs):
            seen.append("postprocess")
            return {"@id": "y"}

    pipe = materialization.MaterializationPipeline(_Pipe())
    jsonld, mappings = pipe.run("y", "u", "<xhtml/>", "d", Path("x.xhtml"), Path("."), "Thing")
    assert jsonld["@id"] == "y"
    assert mappings == [{"m": 1}]
    assert seen == ["normalize", "materialize", "postprocess"]


def test_render_pipeline(monkeypatch: pytest.MonkeyPatch) -> None:
    class _Rendered:
        xhtml = "<html/>"

    monkeypatch.setattr(rendering, "render_html", lambda _opts: _Rendered())
    monkeypatch.setattr(rendering, "clean_xhtml", lambda _xhtml, _opts: "clean")
    logs: list[str] = []
    rendered, cleaned = rendering.RenderPipeline(
        headed=False,
        timeout_ms=10,
        wait_until="domcontentloaded",
        max_xhtml_chars=100,
        max_text_node_chars=10,
    ).render("https://example.com", logs.append)
    assert rendered.xhtml == "<html/>"
    assert cleaned == "clean"
    assert "Rendering page with Playwright..." in logs[0]


def test_validation_service_writes_report(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    class _Schema:
        @staticmethod
        def shape_specs_for_type(_t: str):
            return ["shape"]

    class _Result:
        conforms = True
        warning_count = 0
        report_text = "ok"

    monkeypatch.setattr(validation, "validate_file", lambda _p, shape_specs=None: _Result())
    service = validation.ValidationService(schema=_Schema())
    jsonld_path = tmp_path / "in.jsonld"
    jsonld_path.write_text("{}", encoding="utf-8")
    report = service.validate(jsonld_path, "Thing", tmp_path)
    assert report == "ok"
    payload = json.loads((tmp_path / "jsonld.validation.json").read_text())
    assert payload["conforms"] is True
