from __future__ import annotations
import asyncio
import hashlib
import time
from concurrent.futures import Executor
from dataclasses import dataclass
from ._exceptions import PoWError

MAX_HASH = 2**256
@dataclass(frozen=True, slots=True)
class PoWResult:
    nonce: int
    duration: float
    attempts: int
    difficulty: float
    target: int
    digest_hex: str
def compute_target(difficulty: float) -> int:
    if not isinstance(difficulty, (int, float)):
        raise TypeError("difficulty must be a number")
    difficulty = float(difficulty)
    difficulty = max(0.0, min(64.0, difficulty))
    bits = int(4.0 * difficulty)
    if bits >= 256:
        return 1
    return MAX_HASH >> bits
def _validate_public_bytes(public_bytes: bytes) -> bytes:
    if not isinstance(public_bytes, (bytes, bytearray, memoryview)):
        raise TypeError("public_bytes must be bytes-like")
    data = bytes(public_bytes)
    if not data:
        raise ValueError("public_bytes cannot be empty")
    return data
def verify_pow(public_bytes: bytes, nonce: int, difficulty: float) -> bool:
    public_bytes = _validate_public_bytes(public_bytes)
    if not isinstance(nonce, int) or nonce < 0:
        return False
    target = compute_target(difficulty)
    digest = hashlib.sha256(public_bytes + nonce.to_bytes(8, "big", signed=False)).digest()
    digest_int = int.from_bytes(digest, "big")
    return digest_int < target
def solve_pow_sync(
    public_bytes: bytes,
    difficulty: float,
    *,
    start_nonce: int = 0,
    max_nonce: int | None = None,
) -> PoWResult:
    public_bytes = _validate_public_bytes(public_bytes)
    if not isinstance(start_nonce, int) or start_nonce < 0:
        raise ValueError("start_nonce must be a non-negative integer")
    if max_nonce is not None and (not isinstance(max_nonce, int) or max_nonce < start_nonce):
        raise ValueError("max_nonce must be None or an integer >= start_nonce")
    target = compute_target(difficulty)
    nonce = start_nonce
    attempts = 0
    start = time.perf_counter()
    sha256 = hashlib.sha256
    while True:
        if max_nonce is not None and nonce > max_nonce:
            raise PoWError("No valid nonce found in the provided nonce range.")
        digest = sha256(public_bytes + nonce.to_bytes(8, "big", signed=False)).digest()
        digest_int = int.from_bytes(digest, "big")
        attempts += 1
        if digest_int < target:
            duration = time.perf_counter() - start
            return PoWResult(
                nonce=nonce,
                duration=duration,
                attempts=attempts,
                difficulty=float(difficulty),
                target=target,
                digest_hex=digest.hex(),
            )
        nonce += 1
async def solve_pow_worker(
    public_bytes: bytes,
    difficulty: float,
    *,
    start_nonce: int = 0,
    max_nonce: int | None = None,
    executor: Executor | None = None,
) -> PoWResult:
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(
        executor,
        lambda: solve_pow_sync(
            public_bytes,
            difficulty,
            start_nonce=start_nonce,
            max_nonce=max_nonce,
        ),
    )