import pytest
from click.testing import CliRunner
from unittest.mock import MagicMock, patch
from owl_dsl.cli import match_object_sparql_expression, main, run_subprocess
from owlready2 import ThingClass


def test_match_object_sparql_expression():
    # Single resource, not just filter
    res = match_object_sparql_expression("var", ["http://example.org/A"])
    assert res == "<http://example.org/A>"

    # Multiple resources, not just filter
    res = match_object_sparql_expression(
        "var", ["http://example.org/A", "http://example.org/B"]
    )
    assert (
        "?var FILTER(?var = <http://example.org/A> || ?var = <http://example.org/B>)"
        in res
    )

    # Multiple resources, just filter
    res = match_object_sparql_expression(
        "var", ["http://example.org/A", "http://example.org/B"], just_filter=True
    )
    assert (
        "FILTER(?var = <http://example.org/A> || ?var = <http://example.org/B>)" in res
    )

    # Empty resources
    res = match_object_sparql_expression("var", [])
    assert res == ""


@patch("owl_dsl.cli.default_world")
def test_cli_find_classes(mock_default_world):
    runner = CliRunner()

    # Mock sparql_query
    mock_class = MagicMock()
    mock_class.iri = "http://example.org/Person"
    mock_default_world.sparql_query.return_value = [(mock_class, "Person")]

    result = runner.invoke(
        main,
        [
            "--action",
            "find_classes",
            "--class-search",
            "Person",
            "--ontology-uri",
            "http://test.org/onto.owl",
            "--ontology-namespace-baseuri",
            "http://test.org/onto.owl#",
            "--sqlite-file",
            "/tmp/test.sqlite",
            "--configuration-file",
            "config.yaml",  # We might need to mock open(config.yaml) if setup_configuration is called, but find_classes doesn't call it.
        ],
    )

    assert result.exit_code == 0
    assert "http://example.org/Person 'Person'" in result.output


@patch("owl_dsl.cli.default_world")
@patch("owl_dsl.cli.get_ontology")
@patch("owl_dsl.cli.CNLRenderer")
@patch("owl_dsl.cli.setup_configuration")
def test_cli_render_class(
    mock_setup, mock_renderer_cls, mock_get_ontology, mock_default_world
):
    runner = CliRunner()

    # Mock ontology loading
    mock_onto = MagicMock()
    mock_default_world.ontologies = {"http://test.org/onto.owl": mock_onto}

    # Mock renderer instance
    mock_renderer = MagicMock()
    mock_renderer_cls.return_value = mock_renderer
    mock_renderer.ontology_lookup.Person.iri = "http://example.org/Person"

    # Mock setup configuration
    mock_setup.return_value = []

    # Mock sparql_query for render_class
    mock_class = MagicMock(spec=ThingClass)
    mock_class.iri = "http://example.org/Person"
    mock_class.label = ["Person"]
    mock_default_world.sparql_query.return_value = [
        (mock_class, "Definition of Person")
    ]

    # Mock handle_owl_class
    mock_renderer.handle_owl_class.return_value = "Person is a mammal."

    result = runner.invoke(
        main,
        [
            "--action",
            "render_class",
            "--class-reference",
            "Person",
            "--ontology-uri",
            "http://test.org/onto.owl",
            "--ontology-namespace-baseuri",
            "http://test.org/onto.owl#",
            "--sqlite-file",
            "/tmp/test.sqlite",
            "--configuration-file",
            "config.yaml",
            "--by-id",  # because we mocked ontology_lookup access
        ],
    )

    if result.exit_code != 0:
        print(result.output)
        print(result.exception)

    assert result.exit_code == 0
    assert "# http://example.org/Person (Person) #" in result.output
    assert "## Textual definition ##" in result.output
    assert "Definition of Person" in result.output
    assert "## Logical definition ##" in result.output
    assert "Person is a mammal." in result.output


@patch("subprocess.run")
def test_run_subprocess(mock_run):
    mock_resp = MagicMock()
    mock_resp.stdout.decode.return_value = "output"
    mock_run.return_value = mock_resp

    run_subprocess(["ls", "-l"], verbose=True)

    mock_run.assert_called_with(["ls", "-l"], capture_output=True)
