"""Unit tests for infrastructure.nostr.keypair."""

from __future__ import annotations

import pytest

from infrastructure.nostr.keypair import (
    NostrKeypair,
    _bech32_decode,
    _bech32_encode,
    generate_keypair,
    load_keypair,
    pubkey_from_privkey,
)


class TestGenerateKeypair:
    def test_returns_nostr_keypair(self):
        kp = generate_keypair()
        assert isinstance(kp, NostrKeypair)

    def test_privkey_hex_is_64_chars(self):
        kp = generate_keypair()
        assert len(kp.privkey_hex) == 64
        assert all(c in "0123456789abcdef" for c in kp.privkey_hex)

    def test_pubkey_hex_is_64_chars(self):
        kp = generate_keypair()
        assert len(kp.pubkey_hex) == 64
        assert all(c in "0123456789abcdef" for c in kp.pubkey_hex)

    def test_nsec_starts_with_nsec1(self):
        kp = generate_keypair()
        assert kp.nsec.startswith("nsec1")

    def test_npub_starts_with_npub1(self):
        kp = generate_keypair()
        assert kp.npub.startswith("npub1")

    def test_two_keypairs_are_different(self):
        kp1 = generate_keypair()
        kp2 = generate_keypair()
        assert kp1.privkey_hex != kp2.privkey_hex
        assert kp1.pubkey_hex != kp2.pubkey_hex

    def test_privkey_bytes_matches_hex(self):
        kp = generate_keypair()
        assert kp.privkey_bytes == bytes.fromhex(kp.privkey_hex)

    def test_pubkey_bytes_matches_hex(self):
        kp = generate_keypair()
        assert kp.pubkey_bytes == bytes.fromhex(kp.pubkey_hex)


class TestLoadKeypair:
    def test_round_trip_via_privkey_hex(self):
        kp1 = generate_keypair()
        kp2 = load_keypair(privkey_hex=kp1.privkey_hex)
        assert kp2.privkey_hex == kp1.privkey_hex
        assert kp2.pubkey_hex == kp1.pubkey_hex

    def test_round_trip_via_nsec(self):
        kp1 = generate_keypair()
        kp2 = load_keypair(nsec=kp1.nsec)
        assert kp2.privkey_hex == kp1.privkey_hex
        assert kp2.pubkey_hex == kp1.pubkey_hex

    def test_raises_if_both_supplied(self):
        kp = generate_keypair()
        with pytest.raises(ValueError, match="either"):
            load_keypair(privkey_hex=kp.privkey_hex, nsec=kp.nsec)

    def test_raises_if_neither_supplied(self):
        with pytest.raises(ValueError, match="either"):
            load_keypair()

    def test_raises_on_invalid_hex(self):
        with pytest.raises((ValueError, Exception)):
            load_keypair(privkey_hex="zzzz")

    def test_raises_on_wrong_length_hex(self):
        with pytest.raises(ValueError):
            load_keypair(privkey_hex="deadbeef")  # too short

    def test_raises_on_wrong_hrp_bech32(self):
        kp = generate_keypair()
        # npub is bech32 but with hrp "npub", not "nsec"
        with pytest.raises(ValueError):
            load_keypair(nsec=kp.npub)

    def test_npub_derived_from_privkey(self):
        kp1 = generate_keypair()
        kp2 = load_keypair(privkey_hex=kp1.privkey_hex)
        assert kp2.npub == kp1.npub


class TestPubkeyFromPrivkey:
    def test_derives_correct_pubkey(self):
        kp = generate_keypair()
        derived = pubkey_from_privkey(kp.privkey_hex)
        assert derived == kp.pubkey_hex

    def test_is_deterministic(self):
        kp = generate_keypair()
        assert pubkey_from_privkey(kp.privkey_hex) == pubkey_from_privkey(kp.privkey_hex)


class TestBech32:
    def test_encode_decode_round_trip(self):
        data = bytes(range(32))
        encoded = _bech32_encode("test", data)
        hrp, decoded = _bech32_decode(encoded)
        assert hrp == "test"
        assert decoded == data

    def test_invalid_checksum_raises(self):
        kp = generate_keypair()
        mangled = kp.npub[:-1] + ("q" if kp.npub[-1] != "q" else "p")
        with pytest.raises(ValueError, match="checksum"):
            _bech32_decode(mangled)

    def test_npub_roundtrip(self):
        kp = generate_keypair()
        hrp, pub = _bech32_decode(kp.npub)
        assert hrp == "npub"
        assert pub.hex() == kp.pubkey_hex
