import dataclasses

import coincurve

from .pbc_types import (
    Address,
    HashSha256,
    Signature,
    SignedTransaction,
    SignedTransactionInnerPart,
    Transaction,
)


def find_recovery_id(
    der_sig: bytes,
    message_hash: HashSha256,
    expected_public_key: coincurve.PublicKey,
) -> int:
    r, s = coincurve.der.parse_signature(der_sig)

    for recovery_id in range(4):
        recovered_public_key = coincurve.PublicKey.from_signature_and_message(
            signature=r + s + bytes([recovery_id]),
            message=message_hash._bytes,
            hasher=None,
        )

        if recovered_public_key.format() == expected_public_key.format():
            return recovery_id

    return None


@dataclasses.dataclass(frozen=True)
class SenderAuthentication:
    _private_key_hex: str

    def sender_address(self) -> Address:
        verifying_key_repr = self._public_key().format(False)
        assert len(verifying_key_repr) == 65
        hashed = HashSha256.of_bytes(verifying_key_repr)
        return Address(b'\0' + hashed._bytes[-20:])

    def sign_hash(self, hash_to_sign: HashSha256) -> Signature:
        private_key = self._private_key()
        signature_wrong_location = private_key.sign_recoverable(
            hash_to_sign._bytes,
            hasher=lambda x: x,
        )

        signature = signature_wrong_location[64:] + signature_wrong_location[:64]

        return Signature(signature)

    def _public_key(self) -> coincurve.PublicKey:
        return self._private_key().public_key

    def _private_key(self) -> coincurve.PrivateKey:
        return coincurve.PrivateKey.from_hex(
            self._private_key_hex,
        )


def sign_transaction(
    sender_authentication: SenderAuthentication,
    nonce: int,
    valid_to_time: int,
    gas_cost: int,
    chain_id: str,
    contract_address: Address | str,
    transaction_rpc: bytes,
) -> SignedTransaction:
    sender = sender_authentication.sender_address

    inner: SignedTransactionInnerPart = SignedTransactionInnerPart(
        nonce,
        valid_to_time,
        gas_cost,
        Transaction(Address.from_string(contract_address), transaction_rpc),
    )

    signature: Signature = sender_authentication.sign_hash(inner.hash(chain_id))
    return SignedTransaction(inner, signature)