"""Spec: storage pod naporem (extended scenarios).

## Logika systemu

Patrz ``test_event_storage.py`` — to ten sam mapping eventu NIP-01 na
tabele DuckDB, ale testowany w batchu. Wiele autorów, wiele eventów,
miks valid + invalid, shuffle replaceable. Asercja TYLKO na końcu na
finalnym stanie tabel — NIP-01 nie definiuje kolejności dostawy, tylko
końcowy snapshot ma znaczenie.

Sentinel: ostatni event sekwencji. Dla testów z fake-events sentinel
musi być na ``fake_events`` (bo bad/malformed eventy nie wpadają do
``events``). ``fake_events`` jest append-only, więc asercje finalne
używają ``DISTINCT``. Patrz ``README.md``.
"""

from __future__ import annotations

import json

from ._helpers import DbHelper, new_keypair, new_pub, signed_event


def test_many_events_from_many_authors_all_persist_correctly(
    running_core, mock_relay,
):
    """Trzech autorów publikuje po cztery różne eventy (różne ``created_at``)
    — wszystkie 12 lądują w ``events`` z poprawnym ``pubkey``, ``kind``,
    ``content`` i ``created_at``. Każdy event jest osobnym wierszem (PK na
    ``id`` zapobiega kolizji)."""
    authors = [new_keypair() for _ in range(3)]
    expected: list[dict] = []
    for ai, (prv, pub) in enumerate(authors):
        for ei in range(4):
            evt = signed_event(
                prv, kind=1,
                content=f"author-{ai}-event-{ei}",
                created_at=1000 + ai * 10 + ei,
            )
            mock_relay.stage_event(evt)
            expected.append({
                "id": evt["id"],
                "pubkey": pub,
                "content": evt["content"],
                "created_at": evt["created_at"],
            })

    # All authors share the same relay; subscribe one as primary and the
    # relay will replay every staged event on its discovery REQs.
    running_core.subscribe_to_pubkey(authors[0][1], limit=50)

    # Sentinel: when the last staged event lands, the earlier ones already
    # did too (ingest is serialized).
    DbHelper.expect_row(
        running_core,
        "SELECT 1 FROM events WHERE id = ?",
        [expected[-1]["id"]],
    )

    actual = running_core.run_sql(
        "SELECT id, pubkey, content, created_at FROM events "
        "WHERE kind = 1 "
        "ORDER BY created_at",
    )
    assert actual == sorted(expected, key=lambda r: r["created_at"])


def test_replaceable_kind0_shuffle_keeps_only_newest_in_profiles(
    running_core, mock_relay,
):
    """Pięć kind-0 eventów dla tego samego pubkey, stage'owanych w losowej
    kolejności względem ``created_at`` — w ``profiles`` zostaje tylko ten
    z największym ``created_at`` (NIP-01 replaceable contract). Ordering
    dostawy nie ma znaczenia: liczy się tylko ``created_at`` na evencie."""
    prv, pub = new_keypair()
    # Build 5 versions, then stage in non-monotonic order to make sure
    # ordering of arrival doesn't decide the winner.
    versions = [
        (1000, "first", "early bio"),
        (5000, "fifth", "latest bio"),  # winner
        (2000, "second", "still early"),
        (4000, "fourth", "almost there"),
        (3000, "third", "middle"),
    ]
    for created_at, name, about in versions:
        evt = signed_event(
            prv, kind=0,
            content=json.dumps({"name": name, "about": about}),
            created_at=created_at,
        )
        mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=10)

    row = DbHelper.expect_row(
        running_core,
        "SELECT name, about, created_at FROM profiles "
        "WHERE pubkey = ? AND created_at = ?",
        [pub, 5000],
    )
    assert row == {
        "name": "fifth",
        "about": "latest bio",
        "created_at": 5000,
    }


def test_mixed_valid_and_invalid_events_route_to_correct_tables(
    running_core, mock_relay,
):
    """Mieszanka eventów: część poprawnych, część z każdym typem
    naruszenia (``malformed``, ``id_mismatch``, ``bad_sig``). Poprawne
    lądują w ``events``, niepoprawne w ``fake_events`` z właściwym
    ``reason``. ``run_sql.md`` precyzuje: ``reason ∈ {'malformed',
    'id_mismatch', 'bad_sig'}``."""
    prv, pub = new_keypair()

    valid_a = signed_event(prv, kind=1, content="valid one", created_at=1)
    valid_b = signed_event(prv, kind=1, content="valid two", created_at=2)

    # malformed: signature wiped out (length != 128 → 'malformed' branch)
    malformed = signed_event(prv, kind=1, content="malformed", created_at=3)
    malformed["sig"] = ""

    # id_mismatch: keep the (id, sig) pair from the original sign, but
    # mutate `content` so the recomputed canonical id no longer matches.
    id_mismatch = signed_event(prv, kind=1, content="signed-content", created_at=4)
    id_mismatch["content"] = "tampered-content"

    # bad_sig: keep id consistent with the canonical, but replace the sig
    # with a syntactically-valid-looking but cryptographically-wrong one.
    bad_sig = signed_event(prv, kind=1, content="bad sig", created_at=5)
    bad_sig["sig"] = "0" * 128

    sequence = [valid_a, malformed, valid_b, id_mismatch, bad_sig]
    for evt in sequence:
        mock_relay.stage_event(evt)

    running_core.subscribe_to_pubkey(pub, limit=20)

    # Sentinel must be the LAST event in the staged sequence so all earlier
    # ones are guaranteed processed. The last one (`bad_sig`) is invalid, so
    # we wait for it to appear in `fake_events` rather than `events`.
    DbHelper.expect_row(
        running_core,
        "SELECT 1 FROM fake_events WHERE claimed_id = ? LIMIT 1",
        [sequence[-1]["id"]],
    )

    valid_rows = running_core.run_sql(
        "SELECT id, content FROM events WHERE kind = 1 ORDER BY created_at",
    )
    assert valid_rows == [
        {"id": valid_a["id"], "content": "valid one"},
        {"id": valid_b["id"], "content": "valid two"},
    ]

    # `fake_events` is append-only by design (per run_sql.md: "Bez PK — ten
    # sam fałszywy id z różnych relayów daje wiele wierszy"). Each redelivery
    # adds another row, so dedup with DISTINCT before comparing.
    fake_rows = running_core.run_sql(
        "SELECT DISTINCT claimed_id, reason FROM fake_events "
        "ORDER BY claimed_id",
    )
    expected_fake = sorted([
        {"claimed_id": malformed["id"], "reason": "malformed"},
        {"claimed_id": id_mismatch["id"], "reason": "id_mismatch"},
        {"claimed_id": bad_sig["id"], "reason": "bad_sig"},
    ], key=lambda r: r["claimed_id"])
    assert fake_rows == expected_fake
