"""
Oracle Node - Decentralized Data Provider

This service acts as one node in the oracle network. It:
1. Listens for OracleRequested events from BetOracle contract
2. Fetches data from external APIs (sports, entertainment, etc.)
3. Determines the winner based on API results
4. Signs the result with node's private key
5. Submits the signed result to oracle aggregator

Multiple independent oracle nodes run this code to create a decentralized
oracle network with consensus-based settlement.

NOTE: This is pseudocode/skeleton showing the architecture.
"""

import asyncio
import hashlib
from typing import Dict, Any, Optional
from datetime import datetime
import json


class OracleNode:
    """
    Oracle node for fetching and submitting external data.

    Each node operates independently and signs its results.
    The aggregator collects results from all nodes and achieves consensus.
    """

    def __init__(
        self,
        node_id: str,
        private_key: str,
        blockchain_service,
        aggregator_url: str
    ):
        """
        Initialize oracle node.

        Args:
            node_id: Unique identifier for this node
            private_key: Node's private key for signing results
            blockchain_service: Instance of BlockchainService
            aggregator_url: URL of oracle aggregator service
        """
        self.node_id = node_id
        self.private_key = private_key
        self.blockchain_service = blockchain_service
        self.aggregator_url = aggregator_url
        self.is_running = False

        # API adapters for different event types
        self.api_adapters = {
            'sports': self._fetch_sports_result,
            'entertainment': self._fetch_entertainment_result,
            'politics': self._fetch_politics_result,
            'custom': self._fetch_custom_result
        }

    async def start(self):
        """
        Start the oracle node.

        Continuously listens for OracleRequested events.
        """
        self.is_running = True
        print(f"Oracle node {self.node_id} started")

        try:
            while self.is_running:
                await self._listen_for_oracle_requests()
                await asyncio.sleep(5)
        except Exception as e:
            print(f"Oracle node error: {e}")
            self.is_running = False

    async def stop(self):
        """Stop the oracle node."""
        self.is_running = False
        print(f"Oracle node {self.node_id} stopped")

    async def _listen_for_oracle_requests(self):
        """
        Listen for OracleRequested events from blockchain.

        When an event is detected, process the oracle request.
        """
        # Pseudocode:
        #
        # # Create event filter for OracleRequested
        # event_filter = self.blockchain_service.bet_oracle_contract.events.OracleRequested.create_filter(
        #     fromBlock='latest'
        # )
        #
        # # Get new events
        # events = event_filter.get_new_entries()
        #
        # for event in events:
        #     request_id = event['args']['requestId']
        #     bet_id = event['args']['betId']
        #     event_id = event['args']['eventId']
        #     api_endpoint = event['args']['apiEndpoint']
        #
        #     print(f"[Oracle {self.node_id}] Received request {request_id} for bet {bet_id}")
        #
        #     # Process request asynchronously
        #     asyncio.create_task(
        #         self._process_oracle_request(request_id, bet_id, event_id, api_endpoint)
        #     )

        # Placeholder
        await asyncio.sleep(0.1)

    async def _process_oracle_request(
        self,
        request_id: int,
        bet_id: int,
        event_id: str,
        api_endpoint: str
    ):
        """
        Process an oracle request.

        Steps:
        1. Fetch bet details from blockchain
        2. Call external API
        3. Parse result to determine winner
        4. Sign the result
        5. Submit to aggregator

        Args:
            request_id: Oracle request ID
            bet_id: On-chain bet ID
            event_id: External event identifier
            api_endpoint: API URL to fetch result from
        """
        try:
            # Get bet details
            bet = await self.blockchain_service.get_bet_from_chain(bet_id)

            # Determine API adapter type from event_id
            # event_id format: "sports:nfl-super-bowl-2024"
            adapter_type = event_id.split(':')[0] if ':' in event_id else 'custom'

            # Fetch API result
            api_result = await self._fetch_api_result(adapter_type, api_endpoint, event_id)

            if not api_result:
                print(f"[Oracle {self.node_id}] Failed to fetch API result for {event_id}")
                return

            # Determine winner from API result
            winner_address = await self._determine_winner(api_result, bet)

            if not winner_address:
                print(f"[Oracle {self.node_id}] Could not determine winner from API result")
                return

            # Sign the result
            signature = self._sign_result(request_id, winner_address, api_result)

            # Submit to aggregator
            await self._submit_to_aggregator(
                request_id,
                winner_address,
                api_result,
                signature
            )

            print(f"[Oracle {self.node_id}] Submitted result for request {request_id}")

        except Exception as e:
            print(f"[Oracle {self.node_id}] Error processing request {request_id}: {e}")

    async def _fetch_api_result(
        self,
        adapter_type: str,
        api_endpoint: str,
        event_id: str
    ) -> Optional[Dict[str, Any]]:
        """
        Fetch result from external API.

        Args:
            adapter_type: Type of API (sports, entertainment, etc.)
            api_endpoint: API URL
            event_id: Event identifier

        Returns:
            Parsed API response dict or None if failed
        """
        adapter_function = self.api_adapters.get(adapter_type, self._fetch_custom_result)
        return await adapter_function(api_endpoint, event_id)

    async def _fetch_sports_result(
        self,
        api_endpoint: str,
        event_id: str
    ) -> Optional[Dict[str, Any]]:
        """
        Fetch sports event result from ESPN API or similar.

        Example ESPN API response:
        {
            "event_id": "nfl-super-bowl-2024",
            "status": "final",
            "winner": {
                "team_name": "San Francisco 49ers",
                "score": 24
            },
            "loser": {
                "team_name": "Kansas City Chiefs",
                "score": 21
            }
        }

        Args:
            api_endpoint: ESPN API URL
            event_id: Event identifier

        Returns:
            Parsed result dict
        """
        # Pseudocode:
        #
        # import httpx
        #
        # async with httpx.AsyncClient() as client:
        #     response = await client.get(api_endpoint, timeout=10.0)
        #
        #     if response.status_code != 200:
        #         return None
        #
        #     data = response.json()
        #
        #     # Extract winner from response
        #     # Path depends on API structure
        #     winner_team = data['events'][0]['competitions'][0]['winner']['team']['displayName']
        #
        #     return {
        #         'event_id': event_id,
        #         'winner': winner_team,
        #         'status': 'final',
        #         'timestamp': datetime.utcnow().isoformat()
        #     }

        # Placeholder for pseudocode
        await asyncio.sleep(0.1)
        return {
            'event_id': event_id,
            'winner': 'San Francisco 49ers',
            'status': 'final'
        }

    async def _fetch_entertainment_result(
        self,
        api_endpoint: str,
        event_id: str
    ) -> Optional[Dict[str, Any]]:
        """
        Fetch entertainment event result (awards, box office, etc.).

        Example Oscars API response:
        {
            "year": 2024,
            "category": "Best Picture",
            "winner": "Oppenheimer",
            "nominees": ["Oppenheimer", "Killers of the Flower Moon", ...]
        }
        """
        # Pseudocode:
        #
        # async with httpx.AsyncClient() as client:
        #     response = await client.get(api_endpoint, timeout=10.0)
        #     data = response.json()
        #
        #     winner = data['categories']['best_picture']['winner']
        #
        #     return {
        #         'event_id': event_id,
        #         'winner': winner,
        #         'category': 'Best Picture'
        #     }

        await asyncio.sleep(0.1)
        return {
            'event_id': event_id,
            'winner': 'Oppenheimer',
            'category': 'Best Picture'
        }

    async def _fetch_politics_result(
        self,
        api_endpoint: str,
        event_id: str
    ) -> Optional[Dict[str, Any]]:
        """Fetch political event result (elections, votes, etc.)."""
        await asyncio.sleep(0.1)
        return {
            'event_id': event_id,
            'winner': 'Candidate A'
        }

    async def _fetch_custom_result(
        self,
        api_endpoint: str,
        event_id: str
    ) -> Optional[Dict[str, Any]]:
        """Fetch result from custom user-specified API."""
        await asyncio.sleep(0.1)
        return {
            'event_id': event_id,
            'result': 'Custom result'
        }

    async def _determine_winner(
        self,
        api_result: Dict[str, Any],
        bet: Dict[str, Any]
    ) -> Optional[str]:
        """
        Map API result to bet participant address.

        Compares API result with bet positions to determine winner.

        Args:
            api_result: Result from external API
            bet: Bet details from blockchain

        Returns:
            Winner's wallet address or None if cannot determine
        """
        # Pseudocode:
        #
        # # Get creator and opponent positions from bet metadata (stored off-chain)
        # bet_metadata = await get_bet_metadata_from_db(bet['bet_id'])
        #
        # creator_position = bet_metadata['creator_position']  # e.g., "49ers win"
        # opponent_position = bet_metadata['opponent_position']  # e.g., "Chiefs win"
        #
        # api_winner = api_result.get('winner')
        #
        # # Simple string matching (production would use NLP/fuzzy matching)
        # if "49ers" in creator_position and "49ers" in api_winner:
        #     return bet['creator']
        # elif "Chiefs" in creator_position and "Chiefs" in api_winner:
        #     return bet['creator']
        # else:
        #     return bet['opponent']

        # Placeholder - assume creator won
        return bet['creator']

    def _sign_result(
        self,
        request_id: int,
        winner_address: str,
        result_data: Dict[str, Any]
    ) -> str:
        """
        Sign the oracle result with node's private key.

        This proves that this specific oracle node submitted this result.

        Args:
            request_id: Oracle request ID
            winner_address: Winner's address
            result_data: API result data

        Returns:
            Hex-encoded signature
        """
        # Pseudocode:
        #
        # from eth_account.messages import encode_defunct
        # from eth_account import Account
        #
        # # Create message hash
        # message = f"{request_id}{winner_address}{json.dumps(result_data, sort_keys=True)}"
        # message_hash = hashlib.sha256(message.encode()).hexdigest()
        #
        # # Sign with private key
        # signable_message = encode_defunct(text=message_hash)
        # signed_message = Account.sign_message(signable_message, private_key=self.private_key)
        #
        # return signed_message.signature.hex()

        # Placeholder
        message = f"{request_id}{winner_address}{json.dumps(result_data, sort_keys=True)}"
        signature_hash = hashlib.sha256(message.encode()).hexdigest()
        return f"0x{signature_hash}"

    async def _submit_to_aggregator(
        self,
        request_id: int,
        winner_address: str,
        result_data: Dict[str, Any],
        signature: str
    ):
        """
        Submit signed result to oracle aggregator.

        The aggregator collects results from all nodes and checks consensus.

        Args:
            request_id: Oracle request ID
            winner_address: Winner's address
            result_data: API result data
            signature: Node's signature
        """
        # Pseudocode:
        #
        # import httpx
        #
        # payload = {
        #     'request_id': request_id,
        #     'node_id': self.node_id,
        #     'node_address': self.node_address,  # Derived from private_key
        #     'winner_address': winner_address,
        #     'result_data': result_data,
        #     'signature': signature
        # }
        #
        # async with httpx.AsyncClient() as client:
        #     response = await client.post(
        #         f"{self.aggregator_url}/oracle/submit",
        #         json=payload,
        #         timeout=10.0
        #     )
        #
        #     if response.status_code == 200:
        #         print(f"[Oracle {self.node_id}] Result submitted successfully")
        #     else:
        #         print(f"[Oracle {self.node_id}] Failed to submit: {response.text}")

        print(f"[Oracle {self.node_id}] Submitting result to aggregator...")
        await asyncio.sleep(0.1)


# Example: Running multiple oracle nodes
async def start_oracle_node(node_id: str, private_key: str):
    """
    Start an oracle node instance.

    In production, you would run 3-5 nodes on different servers
    with different API keys and infrastructure for redundancy.

    Example:
        # Node 1 (AWS us-east-1)
        await start_oracle_node("oracle-node-1", "PRIVATE_KEY_1")

        # Node 2 (GCP us-west-2)
        await start_oracle_node("oracle-node-2", "PRIVATE_KEY_2")

        # Node 3 (Azure eu-west-1)
        await start_oracle_node("oracle-node-3", "PRIVATE_KEY_3")
    """
    from .blockchain_service import get_blockchain_service

    blockchain_service = get_blockchain_service()

    node = OracleNode(
        node_id=node_id,
        private_key=private_key,
        blockchain_service=blockchain_service,
        aggregator_url="https://aggregator.h2h.com"
    )

    await node.start()
