"""Tests for worai.core.entity_matrix and the entity-matrix CLI command."""

from __future__ import annotations

import textwrap

import pytest
from typer.testing import CliRunner

from worai.cli import app
from worai.core.entity_matrix import EntityMatrixOptions, build_entity_matrix

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_TURTLE_SIMPLE = textwrap.dedent("""\
    @prefix schema: <http://schema.org/> .

    <https://example.com/article>
        a schema:Article ;
        schema:url "https://example.com/article" ;
        schema:about <https://example.com/entity/topic> .

    <https://example.com/article/faq>
        a schema:FAQPage .

    <https://example.com/article/faq/q1>
        a schema:Question .

    <https://example.com/entity/topic>
        a schema:Thing .
""")

_TURTLE_BLOG = textwrap.dedent("""\
    @prefix schema: <http://schema.org/> .

    <https://example.com/blog/post-1>
        a schema:Article ;
        schema:url "https://example.com/blog/post-1" .

    <https://example.com/blog/post-2>
        a schema:Article ;
        schema:url "https://example.com/blog/post-2" .

    <https://example.com/blog/post-3>
        a schema:Article, schema:BlogPosting ;
        schema:url "https://example.com/blog/post-3" .
""")

# Two distinct signature groups under the same parent (/mixed/*), both with ≥2
# URLs → the SDK emits two separate "parent/*" rows; the CLI must merge them.
_TURTLE_MIXED = textwrap.dedent("""\
    @prefix schema: <http://schema.org/> .

    <https://example.com/mixed/a1>
        a schema:Article ;
        schema:url "https://example.com/mixed/a1" .

    <https://example.com/mixed/a2>
        a schema:Article ;
        schema:url "https://example.com/mixed/a2" .

    <https://example.com/mixed/b1>
        a schema:BlogPosting ;
        schema:url "https://example.com/mixed/b1" .

    <https://example.com/mixed/b2>
        a schema:BlogPosting ;
        schema:url "https://example.com/mixed/b2" .
""")


def _write_ttl(tmp_path, content: str, name: str = "graph.ttl"):
    path = tmp_path / name
    path.write_text(content, encoding="utf-8")
    return str(path)


# ---------------------------------------------------------------------------
# Unit: subgraph coverage (tested via build_entity_matrix)
# ---------------------------------------------------------------------------


def test_subgraph_includes_iri_prefix_children(tmp_path):
    # FAQPage and Question are IRI-prefix children of the article → included
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path))
    article_row = next(r for r in rows if r["url"] == "https://example.com/article")
    assert article_row.get("FAQPage", 0) >= 1
    assert article_row.get("Question", 0) >= 1


def test_subgraph_includes_referenced_entity(tmp_path):
    # ex:entity/topic (schema:Thing) is linked via schema:about → included
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path))
    article_row = next(r for r in rows if r["url"] == "https://example.com/article")
    assert article_row.get("Thing", 0) >= 1


# ---------------------------------------------------------------------------
# Unit: build_entity_matrix
# ---------------------------------------------------------------------------


def test_matrix_has_url_column(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path))
    assert all("url" in row for row in rows)


def test_matrix_article_row_has_faqpage(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path))
    article_row = next(r for r in rows if r["url"] == "https://example.com/article")
    # FAQPage is a child of the article → included in its subgraph
    assert article_row.get("FAQPage", 0) >= 1


def test_matrix_article_row_has_referenced_thing(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path))
    article_row = next(r for r in rows if r["url"] == "https://example.com/article")
    # ex:topic (schema:Thing) is directly referenced → included
    assert article_row.get("Thing", 0) >= 1


def test_matrix_exclude_type(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(
        EntityMatrixOptions(filename=path, exclude_types=["FAQPage"])
    )
    assert all("FAQPage" not in row for row in rows)


def test_matrix_file_not_found():
    with pytest.raises(FileNotFoundError):
        build_entity_matrix(EntityMatrixOptions(filename="/no/such/file.ttl"))


def test_matrix_counts_are_integers(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path))
    for row in rows:
        for k, v in row.items():
            if k != "url":
                assert isinstance(v, int)


# ---------------------------------------------------------------------------
# Unit: clustering
# ---------------------------------------------------------------------------


def test_cluster_collapses_identical_type_signature(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_BLOG)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path, cluster=True))
    urls = [r["url"] for r in rows]
    # post-1 and post-2 share identical types → collapsed into /blog/*
    assert "https://example.com/blog/*" in urls
    # post-3 has an extra BlogPosting → kept separate
    assert "https://example.com/blog/post-3" in urls
    # total rows: one wildcard + one individual
    assert len(urls) == 2


def test_cluster_keeps_different_signatures_separate(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_BLOG)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path, cluster=True))
    urls = [r["url"] for r in rows]
    # post-3 has BlogPosting in addition to Article → kept separate
    assert "https://example.com/blog/post-3" in urls


def test_cluster_sums_counts(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_BLOG)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path, cluster=True))
    wildcard = next((r for r in rows if r["url"].endswith("/*")), None)
    assert wildcard is not None
    # post-1 and post-2 each have 1 Article → sum = 2
    assert wildcard.get("Article", 0) == 2


def test_cluster_no_duplicate_wildcard_urls(tmp_path):
    # Two distinct signature sub-groups under /mixed/, each with 2 URLs.
    # The SDK emits one wildcard row per sub-group; the CLI must merge them
    # so there is exactly one "https://example.com/mixed/*" row.
    path = _write_ttl(tmp_path, _TURTLE_MIXED)
    rows = build_entity_matrix(EntityMatrixOptions(filename=path, cluster=True))
    wildcard_urls = [r["url"] for r in rows if r["url"].endswith("/*")]
    assert wildcard_urls.count("https://example.com/mixed/*") == 1
    # Merged row must carry both Article and BlogPosting counts
    merged = next(r for r in rows if r["url"] == "https://example.com/mixed/*")
    assert merged.get("Article", 0) == 2
    assert merged.get("BlogPosting", 0) == 2


# ---------------------------------------------------------------------------
# CLI integration
# ---------------------------------------------------------------------------

_runner = CliRunner()


def test_cli_table_output(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    result = _runner.invoke(app, ["entity-matrix", str(path)])
    assert result.exit_code == 0
    assert "url" in result.output


def test_cli_csv_to_file(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    out = tmp_path / "out.csv"
    result = _runner.invoke(
        app, ["entity-matrix", str(path), "--format", "csv", "--output", str(out)]
    )
    assert result.exit_code == 0
    content = out.read_text()
    assert "url" in content
    assert "Article" in content


def test_cli_json_output(tmp_path):
    import json

    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    result = _runner.invoke(app, ["entity-matrix", str(path), "--format", "json"])
    assert result.exit_code == 0
    data = json.loads(result.output)
    assert isinstance(data, list)
    assert all("url" in row for row in data)


def test_cli_default_presence_markers(tmp_path):
    import json

    # _TURTLE_BLOG: post-1/post-2 have Article only; post-3 has Article+BlogPosting.
    # The BlogPosting column is absent (0) for post-1 → should render as "".
    path = _write_ttl(tmp_path, _TURTLE_BLOG)
    result = _runner.invoke(app, ["entity-matrix", str(path), "--format", "json"])
    assert result.exit_code == 0
    data = json.loads(result.output)
    post1 = next(r for r in data if r["url"] == "https://example.com/blog/post-1")
    post3 = next(r for r in data if r["url"] == "https://example.com/blog/post-3")
    # Present types → ✅
    assert post1.get("Article") == "✅"
    assert post3.get("BlogPosting") == "✅"
    # Absent type → ""
    assert post1.get("BlogPosting") == ""


def test_cli_count_flag_shows_integers(tmp_path):
    import json

    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    result = _runner.invoke(
        app, ["entity-matrix", str(path), "--format", "json", "--count"]
    )
    assert result.exit_code == 0
    data = json.loads(result.output)
    for row in data:
        for k, v in row.items():
            if k != "url":
                assert isinstance(v, int)


def test_cli_exclude_type(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_SIMPLE)
    result = _runner.invoke(
        app, ["entity-matrix", str(path), "--format", "json", "--exclude-type", "FAQPage"]
    )
    import json

    assert result.exit_code == 0
    data = json.loads(result.output)
    assert all("FAQPage" not in row for row in data)


def test_cli_cluster_flag(tmp_path):
    path = _write_ttl(tmp_path, _TURTLE_BLOG)
    result = _runner.invoke(
        app, ["entity-matrix", str(path), "--format", "json", "--cluster"]
    )
    import json

    assert result.exit_code == 0
    data = json.loads(result.output)
    assert any(r["url"].endswith("/*") for r in data)


def test_cli_missing_file():
    result = _runner.invoke(app, ["entity-matrix", "/no/such/file.ttl"])
    assert result.exit_code != 0
