from __future__ import annotations

from pathlib import Path

import pytest
from rdflib import BNode, Graph, Literal, RDF, URIRef
from rdflib.collection import Collection
from typer.testing import CliRunner

from worai.commands import validate as validate_cmd
from worai.errors import UsageError


class _Result:
    def __init__(self, conforms: bool = True, warnings: int = 0) -> None:
        self.conforms = conforms
        self.warning_count = warnings
        self.report_text = "REPORT"
        self.report_graph = Graph()
        self.data_graph = Graph()
        self.shape_source_map = {}


def test_term_and_path_helpers() -> None:
    g = Graph()
    p1 = URIRef("http://schema.org/name")
    p2 = URIRef("http://example.com/ns#value")
    chain = BNode()
    Collection(g, chain, [p1, p2])

    assert validate_cmd._shorten_term(p1) == "name"
    assert validate_cmd._path_to_str(g, p1) == "name"
    assert validate_cmd._path_to_str(g, chain) == "name.value"
    assert validate_cmd._describe_path(g, p2) == "value"

    inv = BNode()
    g.add((inv, validate_cmd._SH.inversePath, p1))
    assert "inverse name" == validate_cmd._describe_path(g, inv)


def test_or_shape_hint_and_pretty_lines() -> None:
    g = Graph()
    result = URIRef("urn:result")
    focus = URIRef("https://example.com/page")
    shape = URIRef("urn:shape/google_required")
    prop = BNode()
    alt = BNode()
    or_list = BNode()
    path = URIRef("http://schema.org/headline")
    g.add((result, validate_cmd._SH.resultSeverity, validate_cmd._SH.Violation))
    g.add((result, validate_cmd._SH.focusNode, focus))
    g.add((result, validate_cmd._SH.resultPath, path))
    g.add((result, validate_cmd._SH.resultMessage, Literal("Less than 1 values")))
    g.add((result, validate_cmd._SH.sourceShape, shape))
    g.add((shape, validate_cmd._SH["or"], or_list))
    Collection(g, or_list, [alt])
    g.add((alt, validate_cmd._SH.property, prop))
    g.add((prop, validate_cmd._SH.path, path))
    data = Graph()
    data.add((focus, RDF.type, URIRef("http://schema.org/Thing")))

    hint = validate_cmd._or_shape_hint(g, shape)
    assert hint is not None and "headline" in hint
    header, lines = validate_cmd._format_pretty_lines(g, data, {shape: "shape.ttl"})
    assert header.startswith("Status: ERROR")
    assert any("Missing required property" in line for _label, line in lines)


def test_emit_validation_result_modes(tmp_path: Path) -> None:
    ok = _Result(conforms=True, warnings=0)
    validate_cmd._emit_validation_result(ok, format="raw", color=False, report_file=str(tmp_path / "raw.txt"))
    assert (tmp_path / "raw.txt").read_text() == "REPORT"

    with pytest.raises(UsageError, match="Invalid format"):
        validate_cmd._emit_validation_result(ok, format="bad", color=False, report_file=None)

    bad = _Result(conforms=False, warnings=0)
    with pytest.raises(UsageError, match="validation failed"):
        validate_cmd._emit_validation_result(bad, format="pretty", color=False, report_file=None)

    warn = _Result(conforms=True, warnings=1)
    with pytest.raises(UsageError, match="warnings found"):
        validate_cmd._emit_validation_result(warn, format="pretty", color=False, report_file=None)


def test_validate_jsonld_input_url_error(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(validate_cmd, "validate_file", lambda *_a, **_k: (_ for _ in ()).throw(RuntimeError("x")))
    with pytest.raises(UsageError, match="structured-data validate page"):
        validate_cmd._validate_jsonld_input("https://example.com/page", None)


def test_main_list_shapes_and_missing_input(monkeypatch: pytest.MonkeyPatch) -> None:
    runner = CliRunner()
    monkeypatch.setattr(validate_cmd, "_list_shapes", lambda: None)
    result = runner.invoke(validate_cmd.app, ["--list-shapes"])
    assert result.exit_code == 0

    result = runner.invoke(validate_cmd.app, [])
    assert result.exit_code == 2
