"""Cryptographic utilities for 0b1 protocol."""

from typing import Tuple

from eth_account import Account
from eth_account.messages import encode_defunct
import ecies

from .protocol import MAGIC_HEADER_HEX, ProtocolError, prepend_header, strip_header


class DecryptionError(Exception):
    """Raised when decryption fails."""
    pass


def generate_keypair() -> Tuple[str, str]:
    """
    Generate new secp256k1 keypair.
    
    Returns:
        Tuple[private_key_hex, public_key_hex]
        - private_key_hex: "0x" + 64 hex chars
        - public_key_hex: "0x04" + 128 hex chars (uncompressed)
    """
    account = Account.create()
    private_key = account.key.hex()
    if not private_key.startswith("0x"):
        private_key = "0x" + private_key
    
    public_key = private_to_public(private_key)
    return private_key, public_key


def private_to_public(private_key: str) -> str:
    """
    Derive public key from private key.
    
    Args:
        private_key: "0x" + 64 hex chars
    
    Returns:
        str: "0x04" + 128 hex chars (uncompressed)
    """
    # Normalize key
    if private_key.startswith("0x"):
        private_key = private_key[2:]
    
    pk_bytes = bytes.fromhex(private_key)
    account = Account.from_key(pk_bytes)
    
    # Get uncompressed public key (65 bytes = 04 prefix + 64 bytes)
    # The to_bytes() already includes the 04 prefix for uncompressed keys
    pub_key_bytes = account._key_obj.public_key.to_bytes()
    hex_key = pub_key_bytes.hex()
    
    # Ensure it starts with 04 (uncompressed indicator)
    if not hex_key.startswith("04"):
        hex_key = "04" + hex_key
    
    return "0x" + hex_key


def private_to_address(private_key: str) -> str:
    """
    Derive checksummed ETH address from private key.
    
    Args:
        private_key: "0x" + 64 hex chars
    
    Returns:
        str: "0x" + 40 hex chars (checksummed)
    """
    if private_key.startswith("0x"):
        private_key = private_key[2:]
    
    pk_bytes = bytes.fromhex(private_key)
    account = Account.from_key(pk_bytes)
    return account.address


def encrypt(plaintext: str, recipient_public_key: str) -> str:
    """
    ECIES encrypt message for recipient.
    
    Args:
        plaintext: UTF-8 string to encrypt
        recipient_public_key: "0x04..." uncompressed pubkey
    
    Returns:
        str: Hex-encoded blob WITH 0b01 header prepended
    """
    # Normalize public key
    if recipient_public_key.startswith("0x"):
        recipient_public_key = recipient_public_key[2:]
    
    pub_key_bytes = bytes.fromhex(recipient_public_key)
    plaintext_bytes = plaintext.encode("utf-8")
    
    # ECIES encrypt
    ciphertext = ecies.encrypt(pub_key_bytes, plaintext_bytes)
    
    # Prepend protocol header
    full_message = prepend_header(ciphertext)
    
    return full_message.hex()


def decrypt(blob_hex: str, private_key: str) -> str:
    """
    ECIES decrypt message.
    
    Args:
        blob_hex: Hex string starting with "0b01"
        private_key: Recipient's private key
    
    Returns:
        str: Decrypted plaintext
    
    Raises:
        ProtocolError: If header invalid
        DecryptionError: If decryption fails
    """
    # Normalize blob
    if blob_hex.startswith("0x"):
        blob_hex = blob_hex[2:]
    
    blob_bytes = bytes.fromhex(blob_hex)
    
    # Validate and strip header
    ciphertext = strip_header(blob_bytes)
    
    # Normalize private key
    if private_key.startswith("0x"):
        private_key = private_key[2:]
    
    pk_bytes = bytes.fromhex(private_key)
    
    try:
        plaintext_bytes = ecies.decrypt(pk_bytes, ciphertext)
        return plaintext_bytes.decode("utf-8")
    except Exception as e:
        raise DecryptionError(f"Decryption failed: {e}")


def sign_message(message: str, private_key: str) -> str:
    """
    Sign message using EIP-191 personal_sign.
    
    Args:
        message: String to sign
        private_key: Signer's private key
    
    Returns:
        str: "0x" + 130 hex chars (r + s + v)
    """
    if private_key.startswith("0x"):
        private_key = private_key[2:]
    
    pk_bytes = bytes.fromhex(private_key)
    encoded = encode_defunct(text=message)
    signed = Account.sign_message(encoded, pk_bytes)
    
    sig_hex = signed.signature.hex()
    if not sig_hex.startswith("0x"):
        sig_hex = "0x" + sig_hex
    return sig_hex


def verify_signature(message: str, signature: str, address: str) -> bool:
    """
    Verify EIP-191 signature.
    
    Args:
        message: Original message
        signature: Signature hex
        address: Expected signer address
    
    Returns:
        bool: True if valid
    """
    try:
        recovered = recover_address(message, signature)
        return recovered.lower() == address.lower()
    except Exception:
        return False


def recover_address(message: str, signature: str) -> str:
    """
    Recover signer address from signature.
    
    Args:
        message: Original message
        signature: Signature hex
    
    Returns:
        str: Recovered checksummed address
    """
    if signature.startswith("0x"):
        signature = signature[2:]
    
    sig_bytes = bytes.fromhex(signature)
    encoded = encode_defunct(text=message)
    
    return Account.recover_message(encoded, signature=sig_bytes)
