"""Tests for hook registry and @hook decorator."""
from __future__ import annotations

import pytest

from repo_core import HookRegistry, hook, BaseRepository


# ---------------------------------------------------------------------------
# HookRegistry unit tests
# ---------------------------------------------------------------------------

async def test_register_and_emit_async_handler():
    registry = HookRegistry()
    results = []

    async def handler(data):
        results.append(data)

    registry.on("create", handler)
    await registry.emit("create", "value_a")
    assert results == ["value_a"]


async def test_register_and_emit_sync_handler():
    registry = HookRegistry()
    results = []

    def handler(data):
        results.append(data)

    registry.on("update", handler)
    await registry.emit("update", 42)
    assert results == [42]


async def test_emit_unknown_event_is_noop():
    registry = HookRegistry()
    await registry.emit("no_such_event")


async def test_multiple_handlers_called_in_order():
    registry = HookRegistry()
    order = []

    registry.on("ev", lambda: order.append(1))
    registry.on("ev", lambda: order.append(2))
    registry.on("ev", lambda: order.append(3))

    await registry.emit("ev")
    assert order == [1, 2, 3]


async def test_off_removes_specific_handler():
    registry = HookRegistry()
    calls = []

    def handler_a():
        calls.append("a")

    def handler_b():
        calls.append("b")

    registry.on("ev", handler_a)
    registry.on("ev", handler_b)
    registry.off("ev", handler_a)

    await registry.emit("ev")
    assert calls == ["b"]


async def test_clear_specific_event():
    registry = HookRegistry()
    calls = []

    registry.on("ev1", lambda: calls.append(1))
    registry.on("ev2", lambda: calls.append(2))
    registry.clear("ev1")

    await registry.emit("ev1")
    await registry.emit("ev2")
    assert calls == [2]


async def test_clear_all_events():
    registry = HookRegistry()
    calls = []

    registry.on("ev1", lambda: calls.append(1))
    registry.on("ev2", lambda: calls.append(2))
    registry.clear()

    await registry.emit("ev1")
    await registry.emit("ev2")
    assert calls == []


async def test_handler_receives_kwargs():
    registry = HookRegistry()
    received = {}

    async def handler(**kwargs):
        received.update(kwargs)

    registry.on("ev", handler)
    await registry.emit("ev", table="users", id=99)
    assert received == {"table": "users", "id": 99}


# ---------------------------------------------------------------------------
# @hook decorator + BaseRepository integration
# ---------------------------------------------------------------------------

async def test_hook_decorator_single_event():
    class Repo(BaseRepository["dict"]):
        _backends = set()

        @hook("before_save")
        async def my_hook(self, data):
            pass

    repo = Repo()
    assert len(repo.hooks._handlers["before_save"]) == 1


async def test_hook_decorator_multiple_events():
    class Repo(BaseRepository["dict"]):
        _backends = set()

        @hook("before_create", "after_create")
        async def dual_hook(self, data):
            pass

    repo = Repo()
    assert len(repo.hooks._handlers["before_create"]) == 1
    assert len(repo.hooks._handlers["after_create"]) == 1


async def test_hook_decorator_is_called_on_emit():
    payloads = []

    class Repo(BaseRepository["dict"]):
        _backends = set()

        @hook("after_delete")
        async def capture(self, data):
            payloads.append(data)

    repo = Repo()
    await repo.emit("after_delete", {"id": 7})
    assert payloads == [{"id": 7}]


async def test_hook_not_registered_on_different_instance():
    """Each repo instance gets its own hook registry."""

    class Repo(BaseRepository["dict"]):
        _backends = set()

        @hook("before_find")
        async def on_find(self, data):
            pass

    repo1 = Repo()
    repo2 = Repo()

    # Both have the hook registered independently
    assert len(repo1.hooks._handlers["before_find"]) == 1
    assert len(repo2.hooks._handlers["before_find"]) == 1

    # They are different bound methods
    assert (
        repo1.hooks._handlers["before_find"][0].__self__
        is not repo2.hooks._handlers["before_find"][0].__self__
    )
