from web3 import Web3
from backoffice.models import Event,BlockchainSync,Ticket
from backoffice.views import doChecks
from concurrent.futures import ThreadPoolExecutor
from backoffice.models import Event
import time
from tixsell import settings
import logging
import asyncio
from asgiref.sync import sync_to_async
logger = logging.getLogger('nftmonitor')
 
async def process_nft_transfer(contract_address, from_address, to_address, token_id, tx_hash):
    tokens = Ticket.objects.filter(
        refTicketType__refEvent__ticketContract__iexact=contract_address
    )
    exists = await sync_to_async(tokens.exists)()
    if exists:
        logger.info(f"Processing NFT transfer for contract: {contract_address}, from: {from_address}, to: {to_address}, token_id: {token_id}, tx_hash: {tx_hash}")
        await sync_to_async(doChecks)(from_address, to_address, tokens, token_id, tx_hash)



class NFTMonitorManager:
    def __init__(self, max_workers=5, batch_size=50):
        logger.info(f"Initializing NFTMonitorManager with {max_workers} workers")
        self.executor = ThreadPoolExecutor(max_workers=max_workers)
        self.running = True
        self.web3 = Web3(Web3.HTTPProvider(settings.CONTRACT_NODE_URL))
        self.active_monitors = {}
        self.batch_size = batch_size

    
    def start_monitoring(self):
        logger.info("Starting NFT monitoring service")
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            loop.run_until_complete(self._monitor())
        finally:
            loop.close()

    
    async def get_sync_info(self, refEvent, contract_address):
        sync_info, _ = await sync_to_async(BlockchainSync.objects.get_or_create)(
            refEvent_id=str(refEvent),
            contract_address=contract_address,
            defaults={'last_block': self.web3.eth.block_number - 1}
        )
        return sync_info


    async def get_new_events(self, contract_address, from_block, to_block):
        transfer_event_signature = self.web3.keccak(text="Transfer(address,address,uint256)").hex()
        
        events = self.web3.eth.get_logs({
            'address': contract_address,
            'topics': [transfer_event_signature],
            'fromBlock': from_block,
            'toBlock': to_block
        })
        
        return events
    async def monitor_contract(self,refEvent,contract_address):
        # Single iteration of monitoring
        logger.info(f"Starting monitoring for contract: {contract_address}")
        try:
            sync_info = await self.get_sync_info(refEvent,contract_address)
            current_block = self.web3.eth.block_number
            # Add validation for block range
            if sync_info.last_block >= current_block:
                logger.info(f"No new blocks to check for {contract_address}")
                return True
            logger.info(f"Checking blocks {sync_info.last_block + 1} to {current_block}")
            events = await self.get_new_events(contract_address, sync_info.last_block + 1, current_block)
            
            for event in events:
                logger.info(f"Processing event: {event}")
                from_address = '0x' + event['topics'][1].hex()[26:]
                to_address = '0x' + event['topics'][2].hex()[26:]
                token_id = int(event['topics'][3].hex(), 16)
                tx_hash = event['transactionHash'].hex()
                await process_nft_transfer(contract_address, from_address, to_address, token_id, tx_hash)
        
            
            sync_info.last_block = current_block
            await sync_to_async(sync_info.save)()
            
            return True
        except Exception as e:
            logger.error(f"Error monitoring {contract_address}: {e}", exc_info=True)
            return False

    async def _monitor(self):
        while self.running:
            offset = 0
            while True:
                contracts = await sync_to_async(lambda: set(
                    Event.objects.filter(status=1)
                    .values_list('id', 'ticketContract')
                    .distinct()
                    .order_by('id')[offset:offset + self.batch_size]
                ))()
                
                if not contracts:
                    break

                for contract in contracts:
                    if contract[1] not in self.active_monitors:
                        self.active_monitors[contract[1]] = await self.monitor_contract(contract[0], contract[1])

                offset += self.batch_size

                # Check completed monitors and resubmit
                for ticketContract, completed in list(self.active_monitors.items()):
                    if completed:
                        self.active_monitors[ticketContract] = await self.monitor_contract(contract[0], ticketContract)
            # check every 30 seconds
            await asyncio.sleep(30)


 