"""Unit tests for infrastructure.nostr.event."""

from __future__ import annotations

import hashlib
import json
import time

import pytest

from infrastructure.nostr.event import (
    _event_hash,
    build_event,
    schnorr_sign,
    schnorr_verify,
)
from infrastructure.nostr.keypair import generate_keypair


class TestSchorrSign:
    def test_returns_64_bytes(self):
        kp = generate_keypair()
        msg = b"\x00" * 32
        sig = schnorr_sign(msg, kp.privkey_bytes)
        assert len(sig) == 64

    def test_different_msg_different_sig(self):
        kp = generate_keypair()
        sig1 = schnorr_sign(b"\x01" * 32, kp.privkey_bytes)
        sig2 = schnorr_sign(b"\x02" * 32, kp.privkey_bytes)
        assert sig1 != sig2

    def test_raises_on_wrong_msg_length(self):
        kp = generate_keypair()
        with pytest.raises(ValueError, match="32 bytes"):
            schnorr_sign(b"too short", kp.privkey_bytes)

    def test_raises_on_wrong_key_length(self):
        msg = b"\x00" * 32
        with pytest.raises(ValueError, match="32 bytes"):
            schnorr_sign(msg, b"too short")

    def test_nondeterministic_due_to_randomness(self):
        # BIP-340 uses auxiliary randomness; repeated calls produce different sigs
        kp = generate_keypair()
        msg = b"\x42" * 32
        sig1 = schnorr_sign(msg, kp.privkey_bytes)
        sig2 = schnorr_sign(msg, kp.privkey_bytes)
        # With different random nonces these should differ (astronomically unlikely to collide)
        # We just verify both are valid
        assert schnorr_verify(msg, kp.pubkey_bytes, sig1)
        assert schnorr_verify(msg, kp.pubkey_bytes, sig2)


class TestSchnorrVerify:
    def test_valid_signature_verifies(self):
        kp = generate_keypair()
        msg = hashlib.sha256(b"hello nostr").digest()
        sig = schnorr_sign(msg, kp.privkey_bytes)
        assert schnorr_verify(msg, kp.pubkey_bytes, sig) is True

    def test_wrong_pubkey_fails(self):
        kp1 = generate_keypair()
        kp2 = generate_keypair()
        msg = b"\x00" * 32
        sig = schnorr_sign(msg, kp1.privkey_bytes)
        assert schnorr_verify(msg, kp2.pubkey_bytes, sig) is False

    def test_tampered_sig_fails(self):
        kp = generate_keypair()
        msg = b"\x00" * 32
        sig = bytearray(schnorr_sign(msg, kp.privkey_bytes))
        sig[0] ^= 0xFF
        assert schnorr_verify(msg, kp.pubkey_bytes, bytes(sig)) is False

    def test_tampered_msg_fails(self):
        kp = generate_keypair()
        msg = b"\x00" * 32
        sig = schnorr_sign(msg, kp.privkey_bytes)
        bad_msg = b"\xff" * 32
        assert schnorr_verify(bad_msg, kp.pubkey_bytes, sig) is False

    def test_wrong_lengths_return_false(self):
        kp = generate_keypair()
        msg = b"\x00" * 32
        sig = schnorr_sign(msg, kp.privkey_bytes)
        assert schnorr_verify(msg[:16], kp.pubkey_bytes, sig) is False
        assert schnorr_verify(msg, kp.pubkey_bytes[:16], sig) is False
        assert schnorr_verify(msg, kp.pubkey_bytes, sig[:32]) is False

    def test_never_raises(self):
        # Should return False for any garbage input, not raise
        assert schnorr_verify(b"x", b"y", b"z") is False


class TestEventHash:
    def test_returns_32_bytes(self):
        h = _event_hash("aabbcc", 0, 1, [], "")
        assert len(h) == 32

    def test_deterministic(self):
        h1 = _event_hash("aa", 1, 1, [], "hello")
        h2 = _event_hash("aa", 1, 1, [], "hello")
        assert h1 == h2

    def test_different_content_different_hash(self):
        h1 = _event_hash("aa", 1, 1, [], "hello")
        h2 = _event_hash("aa", 1, 1, [], "world")
        assert h1 != h2


class TestBuildEvent:
    def test_returns_required_fields(self):
        kp = generate_keypair()
        ev = build_event(kind=1, content="hello", keypair=kp)
        assert set(ev) >= {"id", "pubkey", "created_at", "kind", "tags", "content", "sig"}

    def test_kind_matches(self):
        kp = generate_keypair()
        ev = build_event(kind=0, content="{}", keypair=kp)
        assert ev["kind"] == 0

    def test_pubkey_matches_keypair(self):
        kp = generate_keypair()
        ev = build_event(kind=1, content="x", keypair=kp)
        assert ev["pubkey"] == kp.pubkey_hex

    def test_id_is_64_char_hex(self):
        kp = generate_keypair()
        ev = build_event(kind=1, content="x", keypair=kp)
        assert len(ev["id"]) == 64
        assert all(c in "0123456789abcdef" for c in ev["id"])

    def test_sig_is_128_char_hex(self):
        kp = generate_keypair()
        ev = build_event(kind=1, content="x", keypair=kp)
        assert len(ev["sig"]) == 128
        assert all(c in "0123456789abcdef" for c in ev["sig"])

    def test_signature_verifies(self):
        kp = generate_keypair()
        ev = build_event(kind=1, content="test", keypair=kp)
        sig_bytes = bytes.fromhex(ev["sig"])
        id_bytes = bytes.fromhex(ev["id"])
        assert schnorr_verify(id_bytes, kp.pubkey_bytes, sig_bytes)

    def test_id_matches_canonical_hash(self):
        kp = generate_keypair()
        ts = int(time.time())
        ev = build_event(kind=1, content="hi", keypair=kp, created_at=ts)
        expected_hash = _event_hash(kp.pubkey_hex, ts, 1, [], "hi").hex()
        assert ev["id"] == expected_hash

    def test_custom_tags(self):
        kp = generate_keypair()
        tags = [["t", "gaming"], ["r", "wss://relay.example.com"]]
        ev = build_event(kind=1, content="x", keypair=kp, tags=tags)
        assert ev["tags"] == tags

    def test_default_tags_empty(self):
        kp = generate_keypair()
        ev = build_event(kind=1, content="x", keypair=kp)
        assert ev["tags"] == []

    def test_custom_created_at(self):
        kp = generate_keypair()
        ts = 1700000000
        ev = build_event(kind=1, content="x", keypair=kp, created_at=ts)
        assert ev["created_at"] == ts

    def test_kind0_profile_content_is_json(self):
        kp = generate_keypair()
        profile = {"name": "Timmy", "about": "test"}
        ev = build_event(kind=0, content=json.dumps(profile), keypair=kp)
        assert ev["kind"] == 0
        parsed = json.loads(ev["content"])
        assert parsed["name"] == "Timmy"
