from __future__ import annotations

import json
import urllib.error

import eightmem.services.webhook_service as webhook_service
from eightmem.services.webhook_service import (
    Connector,
    build_signed_request,
    list_connectors,
    notify_connectors,
    register_connector,
    sign_payload,
)


def test_register_connector_persists_secret_and_public_listing(fake_home):
    connector, error = register_connector(
        connector_id="hermes",
        url="http://127.0.0.1:8766/8mem/sync",
        description="Hermes agent",
        user_id="ashish",
    )

    assert error is None
    assert connector is not None
    assert connector.id == "hermes"
    assert len(connector.secret) == 64
    assert list_connectors(user_id="ashish")[0].url == "http://127.0.0.1:8766/8mem/sync"


def test_register_connector_replaces_existing_and_rotates_secret(fake_home):
    first, _ = register_connector(connector_id="agent", url="http://127.0.0.1:1/8mem/sync")
    second, error = register_connector(connector_id="agent", url="http://127.0.0.1:2/8mem/sync")

    assert error is None
    assert first is not None
    assert second is not None
    assert second.secret != first.secret
    connectors = list_connectors()
    assert len(connectors) == 1
    assert connectors[0].url == "http://127.0.0.1:2/8mem/sync"
    assert connectors[0].registered_at == first.registered_at
    assert connectors[0].updated_at is not None


def test_register_connector_can_use_installer_owned_secret(fake_home):
    connector, error = register_connector(
        connector_id="local-agents",
        url="http://127.0.0.1:18792/8mem/sync",
        secret="installer-secret",
    )

    assert error is None
    assert connector is not None
    assert connector.secret == "installer-secret"


def test_build_signed_request_uses_canonical_json_and_hmac(fake_home):
    connector = Connector(
        id="agent",
        url="http://127.0.0.1:1/8mem/sync",
        secret="secret",
        description=None,
        user_id=None,
        enabled=True,
        registered_at="2026-05-19T00:00:00+00:00",
    )
    payload = {"event": "memory.created", "user_id": "default", "timestamp": 123}

    request = build_signed_request(connector, payload)
    body = request.data or b""
    expected_body = json.dumps(payload, ensure_ascii=True, separators=(",", ":"), sort_keys=True).encode("utf-8")

    assert body == expected_body
    assert request.headers["X-8mem-signature"] == sign_payload(expected_body, "secret")
    assert request.headers["Content-type"] == "application/json"


def test_notify_connectors_starts_background_pushes(fake_home, monkeypatch):
    started: list[tuple[str, dict[str, object]]] = []

    def _fake_start(connector, payload):
        started.append((connector.id, payload))

    monkeypatch.setattr(webhook_service, "_start_push_thread", _fake_start)
    register_connector(connector_id="openclaw", url="http://127.0.0.1:18789/8mem/sync", user_id="ashish")
    register_connector(connector_id="hermes", url="http://127.0.0.1:8766/8mem/sync", user_id="ashish")

    count = notify_connectors("memory.created", user_id="ashish")

    assert count == 2
    assert {item[0] for item in started} == {"openclaw", "hermes"}
    assert all(item[1]["event"] == "memory.created" for item in started)
    assert all(item[1]["user_id"] == "ashish" for item in started)


def test_connector_push_failure_logs_without_secret(fake_home, monkeypatch, caplog):
    connector = Connector(
        id="local-agents",
        url="http://127.0.0.1:18792/8mem/sync",
        secret="super-secret-token",
        description=None,
        user_id=None,
        enabled=True,
        registered_at="2026-05-19T00:00:00+00:00",
    )
    payload = {"event": "memory.created", "user_id": "default", "timestamp": 123}

    def _failing_urlopen(*_args, **_kwargs):
        raise urllib.error.URLError("receiver down")

    monkeypatch.setattr(webhook_service, "MAX_RETRIES", 1)
    monkeypatch.setattr(webhook_service.urllib.request, "urlopen", _failing_urlopen)

    with caplog.at_level("WARNING", logger="eightmem.services.webhook_service"):
        webhook_service._push_one(connector, payload)

    assert "8mem_connector_push_failed" in caplog.text
    assert "connector_id=local-agents" in caplog.text
    assert "event=memory.created" in caplog.text
    assert "super-secret-token" not in caplog.text
