"""
Bitcoin Transfer Manager
Handles cryptocurrency transfers and transaction management with full UTXO support
"""
from typing import Dict, Optional, List, Set, Tuple
from decimal import Decimal
import hashlib
import time
from dataclasses import dataclass
import struct
from binascii import hexlify, unhexlify
import ecdsa
from .wallet_db import WalletDB
from .wallet import BTCWallet
import requests

@dataclass
class UTXO:
    txid: str
    vout: int
    amount: Decimal
    script_pubkey: str
    address: str
    confirmations: int

@dataclass
class TransactionInput:
    txid: str
    vout: int
    script_sig: str = ''
    sequence: int = 0xffffffff

@dataclass
class TransactionOutput:
    amount: Decimal
    script_pubkey: str

class BitcoinTransaction:
    def __init__(self):
        self.version = 2
        self.inputs: List[TransactionInput] = []
        self.outputs: List[TransactionOutput] = []
        self.locktime = 0
        
    def serialize(self) -> bytes:
        """Serialize the transaction to bytes following Bitcoin protocol"""
        raw = struct.pack("<L", self.version)
        
        # Serialize inputs
        raw += self.serialize_vector(self.inputs)
        
        # Serialize outputs
        raw += self.serialize_vector(self.outputs)
        
        # Add locktime
        raw += struct.pack("<L", self.locktime)
        return raw
        
    @staticmethod
    def serialize_vector(items: List) -> bytes:
        """Serialize a vector of transaction inputs or outputs"""
        raw = bytes([len(items)])
        for item in items:
            if isinstance(item, TransactionInput):
                raw += (
                    unhexlify(item.txid)[::-1] +  # Reverse for little-endian
                    struct.pack("<L", item.vout) +
                    bytes([len(item.script_sig)]) +
                    unhexlify(item.script_sig) +
                    struct.pack("<L", item.sequence)
                )
            elif isinstance(item, TransactionOutput):
                amount_satoshis = int(item.amount * 100000000)
                raw += (
                    struct.pack("<Q", amount_satoshis) +
                    bytes([len(item.script_pubkey)]) +
                    unhexlify(item.script_pubkey)
                )
        return raw

class TransferManager:
    def __init__(self, wallet_db: WalletDB):
        self.wallet_db = wallet_db

    def send_bitcoin(self, 
                    from_wallet: BTCWallet,
                    to_address: str,
                    amount: Decimal) -> Dict:
        """
        Send Bitcoin from one wallet to another
        Returns transaction details including hash
        """
        try:
            # Validate addresses
            if not self._validate_bitcoin_address(to_address):
                raise ValueError("Invalid recipient address")

            # Check balance
            sender_info = self.wallet_db.get_wallet(from_wallet.get_wallet_info()['address'])
            if not sender_info or Decimal(sender_info['btc_balance']) < amount:
                raise ValueError("Insufficient balance")

            # Create transaction
            tx_data = self._create_transaction(
                from_wallet=from_wallet,
                to_address=to_address,
                amount=amount)

            # Record in database
            self.wallet_db.record_transaction({
                'tx_hash': tx_data['tx_hash'],
                'from_address': sender_info['address'],
                'to_address': to_address,
                'amount': float(amount),
                'raw_tx': tx_data['raw_transaction']
            })

            # Update balances
            new_balance = Decimal(sender_info['btc_balance']) - amount
            self.wallet_db.update_balance(sender_info['address'], 'BTC', new_balance)

            # Broadcast transaction
            self._broadcast_transaction(tx_data['raw_transaction'])

            return {
                'status': 'success',
                'tx_hash': tx_data['tx_hash'],
                'amount': str(amount),
                'from_address': sender_info['address'],
                'to_address': to_address,
                'timestamp': time.time()
            }

        except Exception as e:
            raise Exception(f"Transfer failed: {str(e)}")

    def _validate_bitcoin_address(self, address: str) -> bool:
        """Validate Bitcoin address format"""
        try:
            # Check basic format
            if not address.startswith('1') and not address.startswith('3') and not address.startswith('bc1'):
                return False

            # Decode base58 (for legacy addresses)
            if not address.startswith('bc1'):
                try:
                    import base58
                    decoded = base58.b58decode(address)
                    if len(decoded) != 25:
                        return False
                except:
                    return False

            return True
        except:
            return False

    def _get_utxos(self, address: str) -> List[UTXO]:
        """Fetch unspent transaction outputs (UTXOs) for an address"""
        # In production, you would query the Bitcoin network or your node
        # For now, we'll get it from our local database
        utxos = self.wallet_db.get_utxos(address)
        return [
            UTXO(
                txid=utxo['txid'],
                vout=utxo['vout'],
                amount=Decimal(utxo['amount']),
                script_pubkey=utxo['script_pubkey'],
                address=utxo['address'],
                confirmations=utxo['confirmations']
            ) for utxo in utxos
        ]

    def _select_utxos(self, utxos: List[UTXO], target_amount: Decimal) -> Tuple[List[UTXO], Decimal]:
        """Select optimal UTXOs for the transaction using a simple coin selection algorithm"""
        selected = []
        total_amount = Decimal('0')
        
        # Sort UTXOs by amount, largest first (greedy approach)
        sorted_utxos = sorted(utxos, key=lambda u: u.amount, reverse=True)
        
        for utxo in sorted_utxos:
            selected.append(utxo)
            total_amount += utxo.amount
            if total_amount >= target_amount:
                break
                
        if total_amount < target_amount:
            raise ValueError("Insufficient funds in UTXOs")
            
        return selected, total_amount

    def _create_p2pkh_script_pubkey(self, address: str) -> str:
        """Create a P2PKH (Pay to Public Key Hash) script"""
        # Decode the Bitcoin address
        import base58
        decoded = base58.b58decode(address)
        pubkey_hash = hexlify(decoded[1:21]).decode('ascii')  # Skip version byte and take 20 bytes
        
        # Create script: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
        return f'76a914{pubkey_hash}88ac'

    def _sign_input(self, tx: BitcoinTransaction, input_index: int, 
                   utxo: UTXO, private_key: str) -> str:
        """Sign a specific input of the transaction"""
        # Create signing key
        signing_key = ecdsa.SigningKey.from_string(
            unhexlify(private_key),
            curve=ecdsa.SECP256k1
        )
        
        # Create signature hash
        signature_hash = self._create_signature_hash(tx, input_index, utxo)
        
        # Sign
        signature = signing_key.sign_digest(
            unhexlify(signature_hash),
            sigencode=ecdsa.util.sigencode_der
        )
        
        # Add SIGHASH_ALL byte
        signature = hexlify(signature).decode('ascii') + '01'
        
        # Get public key
        public_key = hexlify(signing_key.get_verifying_key().to_string()).decode('ascii')
        
        # Create script sig: <signature> <pubkey>
        script_sig = bytes([len(signature)]).hex() + signature
        script_sig += bytes([len(public_key)]).hex() + public_key
        
        return script_sig

    def _create_signature_hash(self, tx: BitcoinTransaction, 
                             input_index: int, utxo: UTXO) -> str:
        """Create the hash that needs to be signed"""
        # Copy transaction and clear all script_sigs
        tx_copy = BitcoinTransaction()
        tx_copy.version = tx.version
        tx_copy.inputs = [
            TransactionInput(
                i.txid,
                i.vout,
                utxo.script_pubkey if idx == input_index else '',
                i.sequence
            ) for idx, i in enumerate(tx.inputs)
        ]
        tx_copy.outputs = tx.outputs
        tx_copy.locktime = tx.locktime
        
        # Serialize and add hash type
        serialized = tx_copy.serialize() + struct.pack("<L", 1)  # SIGHASH_ALL = 1
        return hashlib.double_sha256(serialized).hexdigest()

    def _create_transaction(self, 
                          from_wallet: BTCWallet,
                          to_address: str,
                          amount: Decimal) -> Dict:
        """Create a Bitcoin transaction with proper UTXO handling and signing"""
        # Get available UTXOs
        wallet_info = from_wallet.get_wallet_info()
        utxos = self._get_utxos(wallet_info['address'])
        
        # Select UTXOs for this transaction
        selected_utxos, total_input = self._select_utxos(utxos, amount)
        
        # Create transaction object
        tx = BitcoinTransaction()
        
        # Add inputs
        for utxo in selected_utxos:
            tx.inputs.append(TransactionInput(utxo.txid, utxo.vout))
            
        # Add outputs
        # Payment output
        tx.outputs.append(TransactionOutput(
            amount=amount,
            script_pubkey=self._create_p2pkh_script_pubkey(to_address)
        ))
        
        # Change output if necessary
        change_amount = total_input - amount
        if change_amount > Decimal('0'):
            tx.outputs.append(TransactionOutput(
                amount=change_amount,
                script_pubkey=self._create_p2pkh_script_pubkey(wallet_info['address'])
            ))
            
        # Sign all inputs
        for i, (utxo, tx_in) in enumerate(zip(selected_utxos, tx.inputs)):
            script_sig = self._sign_input(
                tx,
                i,
                utxo,
                from_wallet.keys.private_key
            )
            tx_in.script_sig = script_sig
            
        # Serialize final transaction
        raw_tx = tx.serialize()
        tx_hash = hashlib.double_sha256(raw_tx).hexdigest()
        
        return {
            'tx_hash': tx_hash,
            'raw_transaction': hexlify(raw_tx).decode('ascii')
        }

        # Create transaction hash
        tx_hash = hashlib.sha256(
            str(tx_data).encode()
        ).hexdigest()

        # Sign transaction
        signature = from_wallet.sign_message(tx_hash.encode())

        return {
            'tx_hash': tx_hash,
            'raw_transaction': {
                **tx_data,
                'signature': signature.hex()
            }
        }

    def _broadcast_transaction(self, raw_transaction: str) -> bool:
        """Broadcast transaction to the Bitcoin network"""
        try:
            # Try multiple Bitcoin nodes for redundancy
            nodes = [
                'https://api.blockcypher.com/v1/btc/main/txs/push',
                'https://blockstream.info/api/tx',
                'https://mempool.space/api/tx'
            ]
            
            tx_data = {'tx': raw_transaction}
            
            for node in nodes:
                try:
                    response = requests.post(node, json=tx_data)
                    if response.status_code == 200:
                        return True
                except requests.exceptions.RequestException:
                    continue
                    
            # If we get here, all nodes failed
            raise Exception("Failed to broadcast to all available nodes")
            
        except Exception as e:
            raise Exception(f"Broadcasting failed: {str(e)}")

    def get_transaction_status(self, tx_hash: str) -> Dict:
        """Get status of a transaction"""
        # Query local database first
        tx_history = self.wallet_db.get_transaction_history(tx_hash)
        if tx_history:
            return tx_history[0]

        # In production, you'd also query the Bitcoin network
        return {'status': 'unknown', 'tx_hash': tx_hash}

    def get_wallet_transactions(self, address: str, 
                              limit: int = 50, 
                              offset: int = 0) -> List[Dict]:
        """Get transaction history for a wallet"""
        return self.wallet_db.get_transaction_history(address)


