import hashlib
import time
import random
import string
import threading
import struct
from datetime import datetime

class BlockHeader:
    def __init__(self, version, prev_block_hash, merkle_root, timestamp, bits, nonce):
        self.version = version
        self.prev_block_hash = prev_block_hash
        self.merkle_root = merkle_root
        self.timestamp = timestamp
        self.bits = bits
        self.nonce = nonce

    def serialize(self):
        # Proper Bitcoin block header serialization (little-endian)
        return (
            struct.pack("<I", self.version) +  # 4 bytes version
            bytes.fromhex(self.prev_block_hash)[::-1] +  # 32 bytes prev_block_hash (reversed)
            bytes.fromhex(self.merkle_root)[::-1] +  # 32 bytes merkle_root (reversed)
            struct.pack("<I", self.timestamp) +  # 4 bytes timestamp
            struct.pack("<I", self.bits) +  # 4 bytes bits
            struct.pack("<I", self.nonce)  # 4 bytes nonce
        )

def calculate_target(bits):
    # Convert compressed target (bits) to full 256-bit target
    size = bits >> 24
    word = bits & 0x007fffff
    if size <= 3:
        word >>= 8 * (3 - size)
        target = word
    else:
        target = word
        target <<= 8 * (size - 3)
    return target

def bits_to_difficulty(bits):
    # Convert bits to human-readable difficulty
    current_target = calculate_target(bits)
    if current_target <= 0:
        return 0
    difficulty = (0x00ffff * 2**208) / current_target
    return difficulty

def create_mock_network_data():
    # Mock network data similar to real Bitcoin network
    return {
        'prev_block_hash': '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
        'version': 2,
        'bits': 0x1d00ffff,  # Starting difficulty (easier than real network for testing)
        'height': 1,
        'coinbase_message': b'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
    }

def create_coinbase_tx(height, coinbase_message):
    # Create a proper coinbase transaction
    script = (
        bytes([0x03]) +  # Push next 3 bytes
        height.to_bytes(3, 'little') +  # Block height (BIP34)
        coinbase_message  # Arbitrary data
    )
    tx = (
        struct.pack("<I", 1) +  # Version
        bytes([1]) +  # Input count
        bytes.fromhex('0' * 64) +  # Previous transaction hash
        struct.pack("<I", 0xFFFFFFFF) +  # Previous output index
        bytes([len(script)]) + script +  # Script length and script
        struct.pack("<I", 0xFFFFFFFF) +  # Sequence
        bytes([1]) +  # Output count
        (50 * 100000000).to_bytes(8, 'little') +  # 50 BTC reward
        bytes([25]) + bytes.fromhex('76a914') + bytes(20) + bytes.fromhex('88ac') +  # P2PKH script
        struct.pack("<I", 0)  # Lock time
    )
    return hashlib.sha256(hashlib.sha256(tx).digest()).hexdigest()

def create_merkle_root(coinbase_tx_hash):
    # In a real implementation, this would combine all transaction hashes
    # For this mock version, we just use the coinbase transaction
    return coinbase_tx_hash

def mine_block(thread_id, start_nonce, step, network_data, stop_event, result, stats):
    version = network_data['version']
    prev_block_hash = network_data['prev_block_hash']
    bits = network_data['bits']
    target = calculate_target(bits)
    
    # Create block template
    coinbase_tx = create_coinbase_tx(network_data['height'], network_data['coinbase_message'])
    merkle_root = create_merkle_root(coinbase_tx)
    
    # Initialize mining stats
    start_time = time.time()
    hashes = 0
    nonce = start_nonce

    while not stop_event.is_set():
        timestamp = int(time.time())
        header = BlockHeader(version, prev_block_hash, merkle_root, timestamp, bits, nonce)
        header_bytes = header.serialize()
        hash_result = hashlib.sha256(hashlib.sha256(header_bytes).digest()).digest()
        hash_int = int.from_bytes(hash_result, 'little')
        
        if hash_int < target:
            result['found'] = True
            result['header'] = header
            result['hash'] = hash_result[::-1].hex()
            result['thread'] = thread_id
            stop_event.set()
            return
        
        hashes += 1
        if hashes % 100000 == 0:
            current_time = time.time()
            hashrate = hashes / (current_time - start_time)
            stats[thread_id] = hashrate
            print(f"[Thread {thread_id}] Hashrate: {hashrate/1000:.2f} kH/s")
        
        nonce += step

def run_miner():
    import multiprocessing
    num_threads = multiprocessing.cpu_count()
    print(f"Starting mining with {num_threads} threads...")
    
    # Get mock network data
    network_data = create_mock_network_data()
    difficulty = bits_to_difficulty(network_data['bits'])
    print(f"Current difficulty: {difficulty:.2f}")
    
    stop_event = threading.Event()
    result = {'found': False}
    stats = {i: 0 for i in range(num_threads)}
    threads = []
    
    # Distribute nonce space across threads
    for i in range(num_threads):
        t = threading.Thread(
            target=mine_block,
            args=(i, i, num_threads, network_data, stop_event, result, stats)
        )
        threads.append(t)
        t.start()
    
    try:
        while not stop_event.is_set():
            time.sleep(1)
            total_hashrate = sum(stats.values())
            print(f"Total hashrate: {total_hashrate/1000:.2f} kH/s")
    except KeyboardInterrupt:
        print("\nStopping miners...")
        stop_event.set()
    
    for t in threads:
        t.join()
    
    if result['found']:
        header = result['header']
        print("\nBlock found!")
        print(f"Thread: {result['thread']}")
        print(f"Block hash: {result['hash']}")
        print(f"Nonce: {header.nonce}")
        print(f"Timestamp: {datetime.fromtimestamp(header.timestamp)}")
    else:
        print("\nMining stopped without finding a block.")

if __name__ == "__main__":
    try:
        run_miner()
    except KeyboardInterrupt:
        print("\nMiner stopped by user.")
