from __future__ import annotations

from types import SimpleNamespace

import click
import pytest
from rdflib import BNode, Graph, Literal, URIRef
from rdflib.collection import Collection

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


class _R:
    def __init__(self, conforms: bool = True, warning_count: int = 0, header_graph: Graph | None = None):
        self.conforms = conforms
        self.warning_count = warning_count
        self.report_text = "RAW"
        self.report_graph = header_graph or Graph()
        self.data_graph = Graph()
        self.shape_source_map = {}


def test_validate_helpers_additional_branches(capsys: pytest.CaptureFixture[str]) -> None:
    b = BNode()
    g = Graph()
    assert mod._path_to_str(g, None) == ""
    assert mod._describe_path(g, None) == ""
    assert mod._path_to_str(g, b) == str(b)
    assert mod._shorten_term(URIRef("urn:no_delimiter")) == "urn:no_delimiter"

    shape = URIRef("urn:shape")
    alt1 = BNode()
    alt2 = BNode()
    prop1 = BNode()
    prop2 = BNode()
    or_list = BNode()
    g.add((shape, mod._SH["or"], or_list))
    Collection(g, or_list, [alt1, alt2])
    g.add((alt1, mod._SH.property, prop1))
    g.add((prop1, mod._SH.path, URIRef("http://schema.org/name")))
    g.add((alt2, mod._SH.property, prop2))
    g.add((prop2, mod._SH.path, URIRef("http://schema.org/headline")))
    hint = mod._or_shape_hint(g, shape)
    assert hint and "Provide one of" in hint

    r = _R()
    mod._emit_validation_result(r, format="raw", color=False, report_file=None)
    assert "RAW" in capsys.readouterr().out


def test_format_pretty_lines_value_shape_paths() -> None:
    rg = Graph()
    focus = URIRef("urn:focus")

    r1 = URIRef("urn:r1")
    rg.add((r1, mod._SH.resultSeverity, mod._SH.Warning))
    rg.add((r1, mod._SH.focusNode, focus))
    rg.add((r1, mod._SH.resultMessage, Literal("Value does not conform to Shape")))
    rg.add((r1, mod._SH.value, Literal("v")))

    r2 = URIRef("urn:r2")
    rg.add((r2, mod._SH.resultSeverity, mod._SH.Info))
    rg.add((r2, mod._SH.focusNode, focus))
    rg.add((r2, mod._SH.resultMessage, Literal("x must conform to one or more shapes")))

    r3 = URIRef("urn:r3")
    rg.add((r3, mod._SH.resultSeverity, URIRef("urn:unknown")))
    rg.add((r3, mod._SH.focusNode, focus))

    header, lines = mod._format_pretty_lines(rg, None, None)
    assert header.startswith("Status: WARNING") or header.startswith("Status: INFO")
    joined = "\n".join(line for _label, line in lines)
    assert "Value does not satisfy the required shape" in joined
    assert "Failed to satisfy one of the alternative constraints" in joined


def test_validate_group_resolve_command_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
    group = mod._ValidateGroup(name="validate")
    calls: list[list[str]] = []

    def _fake_super(self, _ctx, args):
        calls.append(list(args))
        if args and args[0] != "legacy":
            raise click.UsageError("bad")
        return ("legacy", None, list(args))

    monkeypatch.setattr(mod.TyperGroup, "resolve_command", _fake_super)
    out = group.resolve_command(None, ["file.jsonld"])
    assert out[0] == "legacy"
    assert calls[1][0] == "legacy"

    def _always_error(self, _ctx, _args):
        raise click.UsageError("x")

    monkeypatch.setattr(mod.TyperGroup, "resolve_command", _always_error)
    with pytest.raises(click.UsageError):
        group.resolve_command(None, [])


def test_validate_jsonld_input_reraises_non_url(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(mod, "validate_file", lambda *_a, **_k: (_ for _ in ()).throw(RuntimeError("boom")))
    with pytest.raises(RuntimeError):
        mod._validate_jsonld_input("local.jsonld", None)

    with pytest.raises(UsageError):
        mod.main(SimpleNamespace(invoked_subcommand=None), list_shapes=False)


def test_validate_format_branches_and_list_shapes(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
    rg = Graph()
    shape_anon = BNode()
    prop = BNode()
    rg.add((shape_anon, mod._SH.property, prop))
    rg.add((prop, mod._SH.path, URIRef("http://schema.org/name")))

    r1 = URIRef("urn:r1")
    rg.add((r1, mod._SH.resultSeverity, mod._SH.Warning))
    rg.add((r1, mod._SH.resultPath, URIRef("http://schema.org/name")))
    rg.add((r1, mod._SH.resultMessage, Literal("Less than 1 values")))
    rg.add((r1, mod._SH.sourceShape, shape_anon))

    shape_uri = URIRef("urn:/google/shape")
    rg.add((shape_uri, mod._SH.message, Literal("custom shape msg")))
    r2 = URIRef("urn:r2")
    rg.add((r2, mod._SH.resultSeverity, mod._SH.Violation))
    rg.add((r2, mod._SH.resultMessage, Literal("Value does not conform to Shape")))
    rg.add((r2, mod._SH.sourceShape, shape_uri))
    rg.add((r2, mod._SH.value, Literal("v")))

    r3 = URIRef("urn:r3")
    rg.add((r3, mod._SH.resultSeverity, mod._SH.Info))
    rg.add((r3, mod._SH.resultMessage, Literal("must conform to one or more shapes")))
    rg.add((r3, mod._SH.sourceShape, shape_uri))

    class _Result:
        conforms = True
        warning_count = 0
        report_text = "RAW"
        report_graph = rg
        data_graph = None
        shape_source_map = {}

    mod._emit_validation_result(_Result(), format="pretty", color=True, report_file=None)
    out = capsys.readouterr().out
    assert "custom shape msg" in out
    assert "[shape: (anonymous)]" in out

    monkeypatch.setitem(__import__("sys").modules, "wordlift_sdk.validation", SimpleNamespace(list_shape_names=lambda: ["s1", "s2"]))
    mod._list_shapes()
    out = capsys.readouterr().out
    assert "s1" in out and "s2" in out
