from collections import OrderedDict

import pytest

from pdftl.core.core_types import Compatibility, FeatureType, HelpExample, Status
from pdftl.core.registry import Registry, registry, register_operation, register_option


@pytest.fixture(autouse=True)
def reset_registry(monkeypatch):
    """Reset the global registry before each test."""
    registry.operations.clear()
    registry.options.clear()
    yield
    registry.operations.clear()
    registry.options.clear()


def test_registry_initial_state():
    """Registry starts with empty OrderedDicts."""
    r = Registry()
    assert isinstance(r.operations, OrderedDict)
    assert isinstance(r.options, OrderedDict)
    assert not r.operations
    assert not r.options


def test_getitem_and_contains_valid_keys():
    r = Registry()
    assert "operations" in r
    assert "options" in r
    assert r["operations"] == r.operations
    assert r["options"] == r.options


def test_getitem_invalid_key_raises():
    r = Registry()
    with pytest.raises(KeyError):
        _ = r["invalid"]


def test_filter_basic():
    """filter() should select keys by predicate and optionally transform."""
    r = Registry()
    r.operations.update(
        {
            "op1": {"category": "A"},
            "op2": {"category": "B"},
            "op3": {"category": "A"},
        }
    )

    result = r.filter("operations", "category", lambda x: x == "A")
    assert result == {"op1", "op3"}

    # With transform
    result2 = r.filter("operations", "category", lambda x: x == "A", transform=str.upper)
    assert result2 == {"OP1", "OP3"}


def test_register_operation_decorator_registers():
    """@register_operation should add function to global registry."""

    @register_operation("say_hello", category="utility")
    def say_hello():
        return "hi"

    entry = registry.operations["say_hello"]
    assert entry["function"] is say_hello
    assert entry["category"] == "utility"


def test_register_option_decorator_registers():
    """@register_option should add metadata to global registry.options."""

    @register_option("verbose", type=bool, default=False)
    def dummy():
        pass

    opt = registry.options["verbose"]
    assert opt["type"] is bool
    assert opt["default"] is False


def test_register_operation_decorator_stackable():
    """Multiple decorators can stack and still register properly."""
    calls = []

    @register_operation("outer", tag="x")
    @register_operation("inner", tag="y")
    def f():
        calls.append("ran")

    assert "outer" in registry.operations
    assert "inner" in registry.operations
    # Ensure both point to the same function
    assert registry.operations["outer"]["function"] is f
    assert registry.operations["inner"]["function"] is f
    f()
    assert calls == ["ran"]


def test_global_helper_functions_delegation(monkeypatch):
    """register_operation and register_option should delegate to registry methods."""
    called = {}

    def fake_reg_op(name, **meta):
        called["op"] = (name, meta)
        return lambda f: f

    def fake_reg_opt(name, **meta):
        called["opt"] = (name, meta)
        return lambda f: f

    monkeypatch.setattr(registry, "register_operation", fake_reg_op)
    monkeypatch.setattr(registry, "register_option", fake_reg_opt)

    register_operation("hello", x=1)
    register_option("quiet", y=2)

    assert called["op"] == ("hello", {"x": 1})
    assert called["opt"] == ("quiet", {"y": 2})


# --- merged from test_registry_coverage.py ---


@pytest.fixture(autouse=True)
def clean_global_registry():
    """Clear the global registry between tests to prevent pollution."""
    registry.operations.clear()
    registry.options.clear()
    registry.help_topics.clear()
    yield


def test_registry_filter_attribute_access():
    """Tests lines 55-56: Registry.filter using object attribute access via LegacyDictAccess."""

    # Register a real operation which uses LegacyDictAccess
    @registry.register_operation("attr-op", tags=["search-me"])
    def mock_func():
        pass

    # This triggers the hasattr(item, "tags") check in Registry.filter
    result = registry.filter("operations", "tags", lambda x: "search-me" in x)
    assert "attr-op" in result


def test_register_operation_compatibility_dict():
    """Tests line 117: Automatic conversion of dict to Compatibility object."""
    # Now using the actual mandatory fields defined in types.py
    compat_data = {
        "type": FeatureType.PDFTL_EXTENSION,
        "status": Status.STABLE,
        "notes": "Verified fields",
    }

    @registry.register_operation("compat-op", compatibility=compat_data)
    def mock_func():
        pass

    op = registry.operations["compat-op"]
    assert isinstance(op.compatibility, Compatibility)
    assert op.compatibility.status == Status.STABLE


def test_register_help_topic_examples():
    """Tests lines 157 & 161: Help topic example validation."""

    @registry.register_help_topic(
        "topic1", "Title", "Desc", examples=[{"cmd": "pdftl", "desc": "d"}]
    )
    def func1():
        pass

    assert isinstance(registry.help_topics["topic1"].examples[0], HelpExample)

    with pytest.raises(ValueError, match="Invalid example format in topic 'topic2'"):

        @registry.register_help_topic("topic2", "Title", "Desc", examples=[123])
        def func2():
            pass


def test_to_help_example_helper_branches():
    """Tests lines 185-188: _to_help_example_helper logic."""
    existing_ex = HelpExample(cmd="cmd", desc="desc")

    @registry.register_operation("op-existing", examples=[existing_ex])
    def func3():
        pass

    assert registry.operations["op-existing"].examples[0] is existing_ex

    with pytest.raises(ValueError, match="Invalid example format in operation 'op-bad'"):

        @registry.register_operation("op-bad", examples=[None])
        def func4():
            pass


def test_registry_getitem_error():
    """Tests line 34: Unknown registry key error."""
    reg = Registry()
    with pytest.raises(KeyError, match="Unknown registry key"):
        _ = reg["invalid_key"]
