"""NIP-01 Nostr event construction and BIP-340 Schnorr signing.

Constructs and signs Nostr events using a pure-Python BIP-340 Schnorr
implementation over secp256k1 (no external crypto dependencies required).

Usage
-----
    from infrastructure.nostr.event import build_event, sign_event
    from infrastructure.nostr.keypair import load_keypair

    kp = load_keypair(privkey_hex="...")
    ev = build_event(kind=0, content='{"name":"Timmy"}', keypair=kp)
    print(ev["id"], ev["sig"])
"""

from __future__ import annotations

import hashlib
import json
import secrets
import time
from typing import Any

from infrastructure.nostr.keypair import (
    _G,
    _N,
    _P,
    NostrKeypair,
    Point,
    _has_even_y,
    _point_mul,
    _x_bytes,
)

# ── BIP-340 tagged hash ────────────────────────────────────────────────────────


def _tagged_hash(tag: str, data: bytes) -> bytes:
    """BIP-340 tagged SHA-256 hash: SHA256(SHA256(tag) || SHA256(tag) || data)."""
    tag_hash = hashlib.sha256(tag.encode()).digest()
    return hashlib.sha256(tag_hash + tag_hash + data).digest()


# ── BIP-340 Schnorr sign ───────────────────────────────────────────────────────


def schnorr_sign(msg: bytes, privkey_bytes: bytes) -> bytes:
    """Sign a 32-byte message with a 32-byte private key using BIP-340 Schnorr.

    Parameters
    ----------
    msg:
        The 32-byte message to sign (typically the event ID hash).
    privkey_bytes:
        The 32-byte private key.

    Returns
    -------
    bytes
        64-byte Schnorr signature (r || s).

    Raises
    ------
    ValueError
        If the key is invalid.
    """
    if len(msg) != 32:
        raise ValueError(f"Message must be 32 bytes, got {len(msg)}")
    if len(privkey_bytes) != 32:
        raise ValueError(f"Private key must be 32 bytes, got {len(privkey_bytes)}")

    d_int = int.from_bytes(privkey_bytes, "big")
    if not (1 <= d_int < _N):
        raise ValueError("Private key out of range")

    P = _point_mul(_G, d_int)
    assert P is not None

    # Negate d if P has odd y (BIP-340 requirement)
    a = d_int if _has_even_y(P) else _N - d_int

    # Deterministic nonce with auxiliary randomness (BIP-340 §Default signing)
    rand = secrets.token_bytes(32)
    t = bytes(
        x ^ y for x, y in zip(a.to_bytes(32, "big"), _tagged_hash("BIP0340/aux", rand), strict=True)
    )

    r_bytes = _tagged_hash("BIP0340/nonce", t + _x_bytes(P) + msg)
    k_int = int.from_bytes(r_bytes, "big") % _N
    if k_int == 0:  # Astronomically unlikely; retry would be cleaner but this is safe enough
        raise ValueError("Nonce derivation produced k=0; retry signing")

    R: Point = _point_mul(_G, k_int)
    assert R is not None
    k = k_int if _has_even_y(R) else _N - k_int

    e = (
        int.from_bytes(
            _tagged_hash("BIP0340/challenge", _x_bytes(R) + _x_bytes(P) + msg),
            "big",
        )
        % _N
    )
    s = (k + e * a) % _N

    sig = _x_bytes(R) + s.to_bytes(32, "big")
    assert len(sig) == 64
    return sig


def schnorr_verify(msg: bytes, pubkey_bytes: bytes, sig: bytes) -> bool:
    """Verify a BIP-340 Schnorr signature.

    Returns True if valid, False otherwise (never raises).
    """
    try:
        if len(msg) != 32 or len(pubkey_bytes) != 32 or len(sig) != 64:
            return False

        px = int.from_bytes(pubkey_bytes, "big")
        if px >= _P:
            return False

        # Lift x to curve point (even-y convention)
        y_sq = (pow(px, 3, _P) + 7) % _P
        y = pow(y_sq, (_P + 1) // 4, _P)
        if pow(y, 2, _P) != y_sq:
            return False
        P: Point = (px, y if y % 2 == 0 else _P - y)

        r = int.from_bytes(sig[:32], "big")
        s = int.from_bytes(sig[32:], "big")

        if r >= _P or s >= _N:
            return False

        e = (
            int.from_bytes(
                _tagged_hash("BIP0340/challenge", sig[:32] + pubkey_bytes + msg),
                "big",
            )
            % _N
        )

        R1 = _point_mul(_G, s)
        R2 = _point_mul(P, _N - e)
        # Point addition
        from infrastructure.nostr.keypair import _point_add

        R: Point = _point_add(R1, R2)
        if R is None or not _has_even_y(R) or R[0] != r:
            return False
        return True
    except Exception:
        return False


# ── NIP-01 event construction ─────────────────────────────────────────────────

NostrEvent = dict[str, Any]


def _event_hash(pubkey: str, created_at: int, kind: int, tags: list, content: str) -> bytes:
    """Compute the NIP-01 event ID (SHA-256 of canonical serialisation)."""
    serialized = json.dumps(
        [0, pubkey, created_at, kind, tags, content],
        separators=(",", ":"),
        ensure_ascii=False,
    )
    return hashlib.sha256(serialized.encode()).digest()


def build_event(
    *,
    kind: int,
    content: str,
    keypair: NostrKeypair,
    tags: list[list[str]] | None = None,
    created_at: int | None = None,
) -> NostrEvent:
    """Build and sign a NIP-01 Nostr event.

    Parameters
    ----------
    kind:
        NIP-01 event kind integer (e.g. 0 = profile, 1 = note).
    content:
        Event content string (often JSON for structured kinds).
    keypair:
        The signing keypair.
    tags:
        Optional list of tag arrays.
    created_at:
        Unix timestamp; defaults to ``int(time.time())``.

    Returns
    -------
    dict
        Fully signed NIP-01 event ready for relay publication.
    """
    _tags = tags or []
    _created_at = created_at if created_at is not None else int(time.time())

    msg = _event_hash(keypair.pubkey_hex, _created_at, kind, _tags, content)
    event_id = msg.hex()
    sig_bytes = schnorr_sign(msg, keypair.privkey_bytes)
    sig_hex = sig_bytes.hex()

    return {
        "id": event_id,
        "pubkey": keypair.pubkey_hex,
        "created_at": _created_at,
        "kind": kind,
        "tags": _tags,
        "content": content,
        "sig": sig_hex,
    }
