"""
Integrates the mining system with Bitcoin mainnet
"""
from typing import Dict, Any, Optional
import time
import requests
import hashlib
import json
import logging
import struct

# Configure detailed logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('network_debug.log'),
        logging.StreamHandler()
    ]
)

class NetworkIntegration:
    def __init__(self, wallet_address: str = None):
        self.api_base = "https://api.bitaps.com/btc/v1"
        self.node = "seed.bitcoin.sipa.be"  # Bitcoin mainnet seed node
        # Use the provided wallet address or load from my_wallet.json
        if wallet_address:
            self.wallet_address = wallet_address
        else:
            try:
                with open('my_wallet.json', 'r') as f:
                    wallet_data = json.load(f)
                self.wallet_address = wallet_data['address']
                print(f"Using wallet address: {self.wallet_address}")
            except Exception as e:
                print(f"Error loading wallet: {e}")
                self.wallet_address = "1Ks4WtCEK96BaBF7HSuCGt3rEpVKPqcJKf"  # Your default address
        
    def connect(self) -> bool:
        """Connect to Bitcoin mainnet"""
        try:
            # Test connection by getting latest block
            response = requests.get(f"{self.api_base}/blockchain/blocks/last")
            return response.status_code == 200
        except Exception as e:
            print(f"Failed to connect to mainnet: {e}")
            return False
        
    def get_block_template(self) -> Dict[str, Any]:
        """Get current block template from mainnet"""
        try:
            # Cache the blockchain API response for 5 minutes
            current_time = time.time()
            if not hasattr(self, '_template_cache') or current_time - self._last_cache_time > 300:
                logging.debug("Cache expired, fetching new block template")
                # Get latest block info from a more reliable API
                response = requests.get("https://blockchain.info/latestblock")
                logging.debug(f"Latest block API response status: {response.status_code}")
                
                if response.status_code != 200:
                    logging.error(f"Failed to get latest block. Status code: {response.status_code}")
                    if hasattr(self, '_template_cache'):
                        logging.info("Using cached template")
                        return self._template_cache
                    raise Exception("Failed to get latest block")
                    
                latest = response.json()
                logging.debug(f"Latest block response: {latest}")
                
                height = latest['height']
                current_block = latest['hash']
                logging.info(f"Current block height: {height}, hash: {current_block}")
                
                # Get current network stats
                logging.debug("Fetching network stats...")
                stats_response = requests.get("https://blockchain.info/stats?format=json")
                logging.debug(f"Network stats API response status: {stats_response.status_code}")
                stats = stats_response.json()
                
                # Convert difficulty to target
                difficulty = float(stats.get('difficulty', 1))
                max_target = int('00000000FFFF0000000000000000000000000000000000000000000000000000', 16)
                current_target = int(max_target / difficulty)
                
                bits = stats.get('bits', '1d00ffff')
                if isinstance(bits, str):
                    bits = int(bits, 16)
                
                # Construct template with required fields
                template = {
                    'version': 2,  # Current Bitcoin version
                    'previousblockhash': current_block,  # Use current block as previous for next block
                    'merkleroot': '0' * 64,  # Placeholder merkle root
                    'time': int(time.time()),
                    'bits': bits,  # Use converted bits
                    'height': int(height),  # Ensure height is integer
                    'target': current_target  # Calculate based on current difficulty
                }
                
                # Update cache
                self._template_cache = template
                self._last_cache_time = current_time
                
            return self._template_cache
            
        except Exception as e:
            logging.error(f"Error getting block template: {str(e)}")
            # Return fallback template
            return {
                'version': 2,
                'previousblockhash': '0' * 64,
                'merkleroot': '0' * 64,
                'time': int(time.time()),
                'bits': '1d00ffff',
                'height': 0,
                'target': int('00000000FFFF0000000000000000000000000000000000000000000000000000', 16)
            }
            if not stats:
                raise Exception("Failed to get network stats")
            
            network_difficulty = float(stats['difficulty'])
            bits = stats.get('bits', '1d00ffff')  # Default to testnet bits if missing
            
            # Parse bits to target
            bits_int = int(bits, 16)  # Convert hex bits to int
            exp = ((bits_int >> 24) & 0xff)
            coeff = bits_int & 0x00ffffff
            target = coeff * (2 ** (8 * (exp - 3)))
            
            print(f"Mining at difficulty: {network_difficulty}")
            print(f"Network target: {hex(target)}")
            
            # Create proper coinbase input script
            block_height_hex = hex(height)[2:].zfill(6)  # BIP34: Block height
            coinbase_script = (
                "03" +  # Push 3 bytes
                block_height_hex +  # BIP34: Block height
                "0000000000000000" +  # Extra nonce space
                "2f4d696e656420627920426974436f696e2d436f70696c6f742f"  # /Mined by BitCoin-Copilot/
            )

            # Create coinbase transaction
            coinbase_tx = {
                'version': 1,
                'vin': [{
                    'txid': '0' * 64,  # Null hash for coinbase
                    'vout': 0xFFFFFFFF,  # -1 (4 bytes) for coinbase
                    'scriptSig': coinbase_script,  # Block height + extra nonce + miner tag
                    'sequence': 0xFFFFFFFF
                }],
                'vout': [{
                    'value': 625000000,  # 6.25 BTC reward
                    'scriptPubKey': '76a914' + hashlib.new("ripemd160", hashlib.sha256(self.wallet_address.encode()).digest()).hexdigest() + '88ac'  # P2PKH to miner address
                }]
            }
            
            # Construct proper block template with real network data
            template = {
                'version': 0x20000000,  # Version 2 with BIP9 bits
                'previousblockhash': prev_block,  # Changed to match Bitcoin Core naming
                'merkleroot': current_block['mrkl_root'],  # Changed to match Bitcoin Core naming
                'time': int(time.time()),  # Changed to match Bitcoin Core naming
                'bits': bits_int,  # Using parsed bits value
                'height': height,
                'target': target,
                'difficulty': network_difficulty,  # Changed to match Bitcoin Core naming
                'coinbasetx': coinbase_tx,  # Changed to match Bitcoin Core naming
                'sizelimit': 4000000,  # Changed to match Bitcoin Core naming
                'transactions': []  # Pending transactions (empty for now)
            }
            
            return template
            
        except Exception as e:
            print(f"Error getting block template: {str(e)}")
            # Get real latest block as fallback
            latest_url = "https://blockchain.info/latestblock"
            latest_response = requests.get(latest_url)
            if latest_response.status_code == 200:
                latest_data = latest_response.json()
                block_height = latest_data['height']
                prev_block = latest_data['hash']
            else:
                block_height = 800_000
                prev_block = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
            
            # Get real network difficulty even in fallback
            diff_url = "https://blockchain.info/q/getdifficulty"
            try:
                diff_response = requests.get(diff_url)
                if diff_response.status_code == 200:
                    network_difficulty = float(diff_response.text)
                    target = int((0xffff * 2**(8*(0x1d - 3))) / network_difficulty)
                else:
                    target = 0x00000000ffff0000000000000000000000000000000000000000000000000000
            except:
                target = 0x00000000ffff0000000000000000000000000000000000000000000000000000
            
            template = {
                'version': 0x20000000,
                'previous_block': prev_block,
                'merkle_root': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
                'timestamp': int(time.time()),
                'bits': 0x1d00ffff,
                'target': target,
                'height': block_height,
                'network_difficulty': network_difficulty if 'network_difficulty' in locals() else None
            }
            return template
        try:
            # Get latest block info
            response = requests.get(self.bitcoin_network.latest_block_url)
            if response.status_code != 200:
                raise Exception("Failed to get latest block")
            
            latest = response.json()
            # Construct proper block template with real network data
            template = {
                'version': 0x20000000,  # Version 2 with BIP9 bits
                'previousblockhash': prev_block,  # Changed to match Bitcoin Core naming
                'merkleroot': '0' * 64,  # Will be calculated from transactions
                'time': int(time.time()),  # Current time
                'bits': bits_int,  # Using parsed bits value
                'height': height,
                'target': target,
                'difficulty': network_difficulty,
                'coinbasetx': coinbase_tx,
                'sizelimit': 4000000,  # 4MB block size limit
                'transactions': []  # Pending transactions (empty for now)
            }
            return template
        except Exception as e:
            print(f"Error getting block template: {str(e)}")
            # Create fallback template
            template = {
                'version': 0x20000000,  # Version 2 with BIP9 bits
                'previousblockhash': '000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d',
                'merkleroot': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
                'time': int(time.time()),
                'bits': 0x1d00ffff,
                'target': 0x00000000ffff0000000000000000000000000000000000000000000000000000,
                'height': 2_500_000,
                'difficulty': 1.0,
                'coinbasetx': {
                    'version': 1,
                    'vin': [{
                        'txid': '0' * 64,
                        'vout': 0xFFFFFFFF,
                        'scriptSig': '03' + hex(2_500_000)[2:].zfill(6) + '0000000000000000',
                        'sequence': 0xFFFFFFFF
                    }],
                    'vout': [{
                        'value': 625000000,
                        'scriptPubKey': '76a914' + hashlib.new("ripemd160", hashlib.sha256(self.wallet_address.encode()).digest()).hexdigest() + '88ac'
                    }]
                },
                'sizelimit': 4000000,
                'transactions': []
            }
            return template
        
    def submit_block(self, block_header: bytes, nonce: int) -> bool:
        """Submit found block to network"""
        try:
            # Get current template
            template = self.get_block_template()
            
            # Create block data starting with header including nonce
            block_data = bytearray(block_header[:-4] + struct.pack('<I', nonce))
            
            # Add transaction count varint (1 for coinbase only)
            block_data.extend(bytes([1]))
            
            # Create proper coinbase transaction
            block_height = template['height']
            block_height_hex = hex(block_height)[2:].zfill(6)  # BIP34: Block height
            
            # Create proper coinbase script
            coinbase_script = bytes.fromhex(
                "03" +  # Push 3 bytes
                block_height_hex +  # Block height (BIP34)
                "0000000000000000" +  # Extra nonce
                "2f4d696e656420627920426974436f696e2d436f70696c6f742f"  # /Mined by BitCoin-Copilot/
            )
            
            # Start serializing the coinbase transaction
            tx_data = struct.pack('<I', 1)  # Version 1
            
            # Input count (always 1 for coinbase)
            tx_data += bytes([1])
            
            # Coinbase input with proper null txid (must be exactly 32 bytes)
            tx_data += b'\x00' * 32  # Previous txid (null for coinbase)
            tx_data += struct.pack('<I', 0xFFFFFFFF)  # Previous output index (-1 for coinbase)
            
            # Create proper coinbase input script with proper length prefix
            script_len = len(coinbase_script)
            if script_len < 0xfd:
                tx_data += bytes([script_len])
            elif script_len <= 0xffff:
                tx_data += bytes([0xfd]) + struct.pack('<H', script_len)
            elif script_len <= 0xffffffff:
                tx_data += bytes([0xfe]) + struct.pack('<I', script_len)
            else:
                tx_data += bytes([0xff]) + struct.pack('<Q', script_len)
            
            tx_data += coinbase_script  # Coinbase script
            tx_data += struct.pack('<I', 0xFFFFFFFF)  # Sequence
            
            # Output count (1 output paying the miner)
            tx_data += bytes([1])
            
            # Miner's reward output (6.25 BTC)
            tx_data += struct.pack('<Q', 625000000)  # Value in satoshis
            
            # Create proper P2PKH script for payout
            # First decode the base58 address to get the pubkey hash
            from base58 import b58decode_check
            decoded = b58decode_check(self.wallet_address)
            pubkey_hash = decoded[1:]  # Skip version byte
            
            # Build P2PKH script
            script_pubkey = bytes([0x76, 0xa9, 0x14]) + pubkey_hash + bytes([0x88, 0xac])
            tx_data += bytes([len(script_pubkey)])  # Script length 
            tx_data += script_pubkey  # P2PKH script
            
            # Add locktime 
            tx_data += struct.pack('<I', 0)  # nLockTime
            
            # Add serialized coinbase transaction to block
            block_data.extend(tx_data)
            
            # Submit block using blockchain.info API
            submit_url = 'https://api.blockchain.info/haskoin-store/btc/block'
            headers = {'Content-Type': 'application/x-www-form-urlencoded'}
            response = requests.post(submit_url, data={'block': block_data.hex()}, headers=headers)
            
            if response.status_code == 200:
                print(f"Block successfully submitted!")
                logging.info("Block submission successful")
                return True
            elif response.status_code == 400 and 'bad-txns-vin-empty' in response.text:
                print("Block rejected: Invalid coinbase transaction structure")
                logging.error("Block rejected due to invalid coinbase transaction")
                return False
            else:
                error_msg = response.text if response.text else f"Status code: {response.status_code}"
                print(f"Block submission failed: {error_msg}")
                logging.error(f"Block submission failed: {error_msg}")
                return False
            
        except Exception as e:
            print(f"Error submitting block: {str(e)}")
            return False
        try:
            block_hash = hashlib.sha256(hashlib.sha256(block_header).digest()).digest()
            return self.bitcoin_network.submit_block(block_header, nonce)
        except Exception as e:
            print(f"Block submission error: {e}")
            return False
            
    def _bits_to_target(self, bits: str) -> int:
        """Convert compact bits to target"""
        bits = int(bits, 16)
        shift = (bits >> 24) & 0xff
        target = (bits & 0x00ffffff) * (2 ** (8 * (shift - 3)))
        return target