"""Spec: mapowanie eventu NIP-01 na tabele DuckDB.

## Logika systemu

Bus odbiera event z relay-a, ``_verify_event`` sprawdza ``id == sha256(
canonical)`` + schnorr-sig, po czym ingest rozkłada event na kilka tabel
(źródło prawdy: ``run_sql.md`` — sekcja „Schemat — 12 tabel"):

- **events** — id, kind, content, pubkey, sig, raw, created_at (sekundy).
  Stuby (kind IS NULL) tworzone dla ``e``-tagów wskazujących nieznane id.
- **event_tags** — płaski, jednorodny store tagów: jeden wiersz na element
  NIP-01 (``event_id``, ``tag_idx``, ``elem_idx``, ``name``, ``value``).
  Bez typowania referencji; wartość główna na ``elem_idx=1``, ``elem_idx=0``
  powtarza nazwę tagu.
- **event_seen_on** — set par (event_id, relay_url).
- **profiles** — kind 0 zdenormalizowane do kolumn (replaceable: nowszy
  created_at zastępuje starszy).
- **addressables** — WIDOK nad ``event_tags``: coordinate ``kind:pubkey:d``
  z ``a``-tagów (NIP-33) rozcinany przy odczycie na trzy kolumny.
- **pubkey_relays** — NIP-65 kind 10002, jedna para ``(pubkey, relay_url)``
  z flagami ``read`` / ``write`` (relay bez markera ma oba TRUE).
- **fake_events** — forensics log dla eventów, które nie przeszły
  weryfikacji. ``reason ∈ {malformed, id_mismatch, bad_sig}``. **Bez PK**
  — redelivery dodaje wiersz (asercje końcowe używają ``DISTINCT``).

Patrz ``README.md`` dla wzorców testowych.
"""

from __future__ import annotations

import json

from ._helpers import DbHelper, new_keypair, new_pub, signed_event


def test_event_from_subscribed_primary_lands_in_events_table(
    running_core, mock_relay,
):
    prv, pub = new_keypair()
    evt = signed_event(prv, kind=1, content="hello from primary")
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT * FROM events WHERE id = ?", [evt["id"]],
    )
    assert row["kind"] == 1
    assert row["content"] == "hello from primary"
    assert row["pubkey"] == pub


def test_p_tag_on_ingested_event_lands_in_tags_table(running_core, mock_relay):
    primary_prv, primary_pub = new_keypair()
    target_pub = new_pub()

    evt = signed_event(
        primary_prv, kind=1,
        content="referencing @target",
        tags=[["p", target_pub]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(primary_pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT name, value, tag_idx FROM event_tags "
        "WHERE event_id = ? AND name = ? AND elem_idx = 1",
        [evt["id"], "p"],
    )
    assert row == {"name": "p", "value": target_pub, "tag_idx": 0}


def test_event_seen_on_records_delivering_relay(running_core, mock_relay):
    """Każdy zaingestowany event ma wiersz w ``event_seen_on(event_id,
    relay_url)`` wskazujący relay, z którego przyszedł."""
    prv, pub = new_keypair()
    evt = signed_event(prv, kind=1, content="hi")
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT event_id, relay_url FROM event_seen_on WHERE event_id = ?",
        [evt["id"]],
    )
    assert row == {"event_id": evt["id"], "relay_url": mock_relay.url}


def test_event_tags_reconstructs_full_nip01_array(running_core, mock_relay):
    """``event_tags`` mapuje surową tablicę NIP-01 1:1 — ``elem_idx=0``
    to nazwa tagu (duplikat ``name``), kolejne pozycje to oryginalne
    elementy eventu (np. relay-hint w p-tagu)."""
    prv, pub = new_keypair()
    target_pub = new_pub()
    relay_hint = "wss://relay.example.com"
    evt = signed_event(
        prv, kind=1, content="hi",
        tags=[["p", target_pub, relay_hint]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    elements = running_core.run_sql(
        "SELECT elem_idx, value FROM event_tags "
        "WHERE event_id = ? AND name = 'p' "
        "ORDER BY elem_idx",
        [evt["id"]],
    )
    assert elements == [
        {"elem_idx": 0, "value": "p"},
        {"elem_idx": 1, "value": target_pub},
        {"elem_idx": 2, "value": relay_hint},
    ]


def test_non_reference_tag_is_stored_uniformly(running_core, mock_relay):
    """Brak typowania referencji — tag nie-referencyjny (hashtag ``t``)
    ląduje w ``event_tags`` tak samo jak każdy inny: wartość główna na
    ``elem_idx=1``, czytelna przez ``name`` + ``value``."""
    prv, pub = new_keypair()
    evt = signed_event(
        prv, kind=1, content="check this out",
        tags=[["t", "nostr"]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    row = DbHelper.expect_row(
        running_core,
        "SELECT name, value FROM event_tags "
        "WHERE event_id = ? AND name = ? AND elem_idx = 1",
        [evt["id"], "t"],
    )
    assert row == {"name": "t", "value": "nostr"}


def test_kind0_metadata_event_populates_profiles_table(running_core, mock_relay):
    """Event ``kind=0`` (NIP-01 metadata) ma content w postaci JSON-a; ingest
    rozparsowuje go i zdenormalizuje do kolumn ``profiles``
    (``name``, ``display_name``, ``about``, ``picture``, …)."""
    prv, pub = new_keypair()
    metadata = {
        "name": "alice",
        "display_name": "Alice",
        "about": "Nostr enjoyer",
        "picture": "https://example.com/alice.jpg",
    }
    evt = signed_event(
        prv, kind=0, content=json.dumps(metadata), created_at=1700000000,
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT pubkey, created_at, name, display_name, about, picture "
        "FROM profiles WHERE pubkey = ?",
        [pub],
    )
    assert row == {
        "pubkey": pub,
        "created_at": 1700000000,
        "name": "alice",
        "display_name": "Alice",
        "about": "Nostr enjoyer",
        "picture": "https://example.com/alice.jpg",
    }


def test_kind10002_relay_list_splits_into_publishes_and_reads(
    running_core, mock_relay,
):
    """NIP-65 (``kind=10002``) z tagami ``['r', url, 'write']`` ustawia
    ``write=TRUE`` w ``pubkey_relays`` (gdzie autor publikuje), a
    ``['r', url, 'read']`` ustawia ``read=TRUE`` (skąd czyta). To źródło prawdy
    o outbox/inbox autora — wykorzystywane przez discovery secondary."""
    prv, pub = new_keypair()
    write_url = "wss://outbox.example.com"
    read_url = "wss://inbox.example.com"
    evt = signed_event(
        prv, kind=10002, content="",
        tags=[
            ["r", write_url, "write"],
            ["r", read_url, "read"],
        ],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    write_row = DbHelper.expect_row(
        running_core,
        "SELECT pubkey, relay_url FROM pubkey_relays WHERE pubkey = ? AND write",
        [pub],
    )
    assert write_row == {"pubkey": pub, "relay_url": write_url}

    read_row = DbHelper.expect_row(
        running_core,
        "SELECT pubkey, relay_url FROM pubkey_relays WHERE pubkey = ? AND read",
        [pub],
    )
    assert read_row == {"pubkey": pub, "relay_url": read_url}


def test_e_tag_event_reference_lands_in_event_tags(running_core, mock_relay):
    """Event z ``['e', event_id]`` zapisuje w ``event_tags`` wiersz
    ``name='e'`` z referowanym id jako wartością główną (``elem_idx=1``).
    Graf czyta to joinem ``value = events.id``."""
    prv, pub = new_keypair()
    referenced_id = "f" * 64
    evt = signed_event(
        prv, kind=1, content="quoted",
        tags=[["e", referenced_id]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    row = DbHelper.expect_row(
        running_core,
        "SELECT name, value FROM event_tags "
        "WHERE event_id = ? AND name = 'e' AND elem_idx = 1",
        [evt["id"]],
    )
    assert row == {"name": "e", "value": referenced_id}


def test_e_tag_pointing_to_unknown_event_creates_stub(running_core, mock_relay):
    """Gdy event ma ``['e', X]`` a ``X`` nie jest jeszcze w bazie, ingest
    wpisuje *stub* do ``events`` (``id=X``, ``kind IS NULL``, brak
    pubkey/content) — placeholder do uzupełnienia gdy referowany event
    kiedyś dotrze."""
    prv, pub = new_keypair()
    stub_id = "1" * 64
    evt = signed_event(
        prv, kind=1, content="reply to ghost",
        tags=[["e", stub_id]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT id, kind, content, pubkey FROM events WHERE id = ?",
        [stub_id],
    )
    assert row == {"id": stub_id, "kind": None, "content": None, "pubkey": None}


def test_a_tag_creates_addressable_row_with_parsed_coordinate(
    running_core, mock_relay,
):
    """Event z ``['a', 'kind:pubkey:d']`` tworzy wiersz w ``addressables``
    z ``value`` = pełny coordinate oraz rozparsowanymi kolumnami
    ``kind``, ``pubkey``, ``d`` — to wpis dla NIP-33 parametrized
    replaceable eventu, do którego ten tag wskazuje."""
    prv, pub = new_keypair()
    target_pub = new_pub()
    a_coord = f"30023:{target_pub}:my-article"
    evt = signed_event(
        prv, kind=1, content="see this article",
        tags=[["a", a_coord]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    row = DbHelper.expect_row(
        running_core,
        "SELECT value, kind, pubkey, d FROM addressables WHERE value = ?",
        [a_coord],
    )
    assert row == {
        "value": a_coord,
        "kind": 30023,
        "pubkey": target_pub,
        "d": "my-article",
    }


def test_duplicate_event_delivery_is_idempotent(running_core, mock_relay):
    """Powtórna dostawa tego samego event_id z tego samego relaya nie
    duplikuje wpisów: dokładnie jeden wiersz w ``events`` (event_id jest PK)
    i jeden w ``event_seen_on`` (composite PK na event_id+relay_url)."""
    prv, pub = new_keypair()
    evt = signed_event(prv, kind=1, content="hi")

    # Same event staged twice — the default fixture handler replays both
    # copies on every REQ, so the bus delivers it back-to-back.
    mock_relay.stage_event(evt)
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    events_rows = running_core.run_sql(
        "SELECT id FROM events WHERE id = ?", [evt["id"]],
    )
    seen_rows = running_core.run_sql(
        "SELECT event_id, relay_url FROM event_seen_on WHERE event_id = ?",
        [evt["id"]],
    )
    assert events_rows == [{"id": evt["id"]}]
    assert seen_rows == [{"event_id": evt["id"], "relay_url": mock_relay.url}]


def test_newer_kind0_replaces_older_kind0_in_profiles(running_core, mock_relay):
    """Drugi event ``kind=0`` z większym ``created_at`` zastępuje pierwszy
    w ``profiles`` (NIP-01 replaceable). Pola w wierszu odpowiadają
    najnowszemu eventowi, nie pierwszemu zaingestowanemu."""
    prv, pub = new_keypair()
    old = signed_event(
        prv, kind=0,
        content=json.dumps({"name": "old-name", "about": "old bio"}),
        created_at=1000,
    )
    new = signed_event(
        prv, kind=0,
        content=json.dumps({"name": "new-name", "about": "new bio"}),
        created_at=2000,
    )
    mock_relay.stage_event(old)
    mock_relay.stage_event(new)

    running_core.subscribe_to_pubkey(pub, limit=10)

    # SQL filters on the newer name so `expect_row` waits until replace
    # has actually happened (the older row exists transiently first).
    row = DbHelper.expect_row(
        running_core,
        "SELECT name, about, created_at FROM profiles "
        "WHERE pubkey = ? AND created_at = ?",
        [pub, 2000],
    )
    assert row == {
        "name": "new-name",
        "about": "new bio",
        "created_at": 2000,
    }


def test_events_raw_column_holds_original_nip01_json(running_core, mock_relay):
    """Kolumna ``events.raw`` przechowuje oryginalny event NIP-01 jako JSON
    string — pełen obiekt z id/pubkey/created_at/kind/tags/content/sig,
    bez modyfikacji. Klient SQL może z tego odtworzyć event 1:1."""
    prv, pub = new_keypair()
    evt = signed_event(prv, kind=1, content="payload", created_at=1700000000)
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT raw FROM events WHERE id = ?", [evt["id"]],
    )
    parsed = json.loads(row["raw"])
    assert parsed == evt


def test_two_distinct_events_from_same_author_both_persist(
    running_core, mock_relay,
):
    """Autor publikuje dwa różne eventy (różne created_at → różne id) —
    oba lądują w ``events`` jako osobne wiersze. Brak konfliktu na PK,
    każdy event ma własny content."""
    prv, pub = new_keypair()
    evt_a = signed_event(prv, kind=1, content="first", created_at=1000)
    evt_b = signed_event(prv, kind=1, content="second", created_at=2000)
    mock_relay.stage_event(evt_a)
    mock_relay.stage_event(evt_b)

    running_core.subscribe_to_pubkey(pub, limit=10)

    rows = DbHelper.expect_rows(
        running_core,
        "SELECT id, content, created_at FROM events "
        "WHERE pubkey = ? ORDER BY created_at",
        [pub],
        count=2,
    )
    assert rows == [
        {"id": evt_a["id"], "content": "first", "created_at": 1000},
        {"id": evt_b["id"], "content": "second", "created_at": 2000},
    ]


def test_two_p_tags_on_one_event_persist_as_distinct_rows(
    running_core, mock_relay,
):
    """Event z dwoma tagami tej samej nazwy (``['p', A]``, ``['p', B]``)
    daje dwa wiersze ``elem_idx=1`` różniące się ``tag_idx`` i ``value``.
    Klucz ``(event_id, tag_idx, elem_idx)`` jest unikalny per pozycja."""
    prv, pub = new_keypair()
    target_a = new_pub()
    target_b = new_pub()
    evt = signed_event(
        prv, kind=1, content="hi both",
        tags=[["p", target_a], ["p", target_b]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    rows = running_core.run_sql(
        "SELECT tag_idx, value FROM event_tags "
        "WHERE event_id = ? AND name = 'p' AND elem_idx = 1 "
        "ORDER BY tag_idx",
        [evt["id"]],
    )
    assert rows == [
        {"tag_idx": 0, "value": target_a},
        {"tag_idx": 1, "value": target_b},
    ]


def test_event_with_mixed_tag_types_stored_uniformly_per_tag(
    running_core, mock_relay,
):
    """Event mieszający różne typy tagów (``['p', X]`` + ``['e', Y]``)
    daje po jednym wierszu ``elem_idx=1`` na tag — ten sam, jednorodny
    kształt dla każdej litery, rozróżniany tylko przez ``name``."""
    prv, pub = new_keypair()
    target_pub = new_pub()
    referenced_id = "a" * 64
    evt = signed_event(
        prv, kind=1, content="multi-ref",
        tags=[["p", target_pub], ["e", referenced_id]],
    )
    mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)
    DbHelper.expect_row(
        running_core, "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )

    rows = running_core.run_sql(
        "SELECT name, value FROM event_tags "
        "WHERE event_id = ? AND elem_idx = 1 ORDER BY tag_idx",
        [evt["id"]],
    )
    assert rows == [
        {"name": "p", "value": target_pub},
        {"name": "e", "value": referenced_id},
    ]


def test_event_with_invalid_signature_is_recorded_as_fake_event(
    running_core, mock_relay,
):
    """Event z nieprawidłowym podpisem schnorr nie wchodzi do ``events`` —
    trafia do ``fake_events`` (forensics) z ``reason='bad_sig'``. Real
    podpis schnorr jest twardo wymagany przez ``_verify_event``."""
    prv, pub = new_keypair()
    evt = signed_event(prv, kind=1, content="forged")
    # Replace the real schnorr signature with a syntactically-valid-looking
    # but cryptographically-wrong value — schnorr verify will reject it.
    evt["sig"] = "0" * 128

    mock_relay.stage_event(evt)
    running_core.subscribe_to_pubkey(pub, limit=10)

    # `fake_events` is append-only by design — redelivery adds another
    # row, so DISTINCT is mandatory for expect_row's exactly-one contract.
    row = DbHelper.expect_row(
        running_core,
        "SELECT DISTINCT claimed_id, claimed_pubkey, reason FROM fake_events "
        "WHERE claimed_id = ?",
        [evt["id"]],
    )
    assert row == {
        "claimed_id": evt["id"],
        "claimed_pubkey": pub,
        "reason": "bad_sig",
    }
    events_rows = running_core.run_sql(
        "SELECT 1 FROM events WHERE id = ?", [evt["id"]],
    )
    assert events_rows == []
