"""
Data models — pure data classes with no business logic.

Depends on: config
"""

import asyncio
from collections import deque
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Optional

from darkmatter.config import ANTIMATTER_MAX_HOPS, ANTIMATTER_LOG_MAX, CONVERSATION_LOG_MAX


# =============================================================================
# Enums
# =============================================================================

class AgentStatus(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"


class RouterAction(str, Enum):
    """Actions a router can take on an incoming message."""
    HANDLE = "handle"      # Keep in queue; spawn agent if in spawn mode
    FORWARD = "forward"    # Auto-forward to specified peer(s)
    DROP = "drop"          # Remove from queue silently
    PASS = "pass"          # No opinion — try next router in chain


# =============================================================================
# Connection & Trust
# =============================================================================

@dataclass
class Connection:
    """A connection to another agent in the mesh."""
    agent_id: str
    agent_url: str
    agent_bio: str
    connected_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    # Local telemetry
    messages_sent: int = 0
    messages_received: int = 0
    messages_declined: int = 0
    total_response_time_ms: float = 0.0
    last_activity: Optional[str] = None
    # Cryptographic identity
    agent_public_key_hex: Optional[str] = None
    agent_display_name: Optional[str] = None
    # Wallets (chain -> address, exchanged during handshake)
    wallets: dict[str, str] = field(default_factory=dict)
    # Transport-aware addresses (transport_name -> address)
    addresses: dict[str, str] = field(default_factory=dict)
    # Peer's passport creation time (for elder selection)
    peer_created_at: Optional[str] = None
    # Per-connection rate limit (0 = use global default, -1 = unlimited)
    rate_limit: int = 0
    # Ephemeral — never persisted
    _request_timestamps: deque = field(default_factory=deque)
    health_failures: int = 0
    transport: str = "http"              # "http" | "webrtc"
    webrtc_pc: Optional[object] = None   # RTCPeerConnection
    webrtc_channel: Optional[object] = None  # RTCDataChannel
    # Security
    identity_verified: bool = False   # True if challenge-response proof passed
    tls_secure: bool = False          # True if peer URL uses HTTPS
    # NAT traversal connectivity tracking (ephemeral)
    connectivity_level: int = 0    # 0=unknown, 1=direct, 2=lan_webrtc, 3=peer_relay
    connectivity_method: str = ""  # human label set alongside level
    # Peer delivery capabilities advertised during handshake
    capabilities: dict = field(default_factory=dict)

    @property
    def avg_response_time_ms(self) -> float:
        if self.messages_received == 0:
            return 0.0
        return self.total_response_time_ms / self.messages_received


@dataclass
class Impression:
    """A scored trust impression of another agent.

    The `note` field supports typed context (e.g. interaction summaries,
    role tags, or free-form observations) to give trust scores meaning.
    """
    score: float       # -1.0 (avoid) to 1.0 (fully trusted)
    note: str = ""
    negative_since: Optional[str] = None  # ISO timestamp when score crossed below 0


# =============================================================================
# AntiMatter Economy
# =============================================================================

@dataclass
class AntiMatterSignal:
    """A antimatter fee signal routed through the network via the match game."""
    signal_id: str
    original_tx: str            # tx_signature that generated this signal
    sender_agent_id: str        # A (who sent the payment)
    amount: float               # antimatter fee amount (1% of original)
    token: str                  # "SOL" or mint address
    token_decimals: int         # 9 for SOL, 6 for USDC, etc.
    sender_superagent_wallet: str  # where fee goes on timeout
    callback_url: str           # B's /__darkmatter__/antimatter_result endpoint
    hops: int = 0
    max_hops: int = ANTIMATTER_MAX_HOPS
    created_at: str = ""
    path: list[str] = field(default_factory=list)  # agent_ids visited (loop prevention)


# =============================================================================
# Conversation Memory
# =============================================================================

@dataclass
class ConversationEntry:
    """A logged conversation event for persistent agent memory."""
    message_id: str
    content: str
    from_agent_id: str
    to_agent_ids: list[str]
    timestamp: str              # ISO UTC
    entry_type: str             # "direct" | "broadcast" | "reply" | "forward"
    direction: str              # "inbound" | "outbound"
    trust_at_time: float        # sender's trust score when logged
    metadata: dict = field(default_factory=dict)


# =============================================================================
# Shared Shards
# =============================================================================

@dataclass
class SharedShard:
    """A live code shard, trust-gated and push-synced.

    Anchored to a file region via from_text/to_text, resolved live on view.
    When the code changes, updates are automatically pushed to peers.
    """
    shard_id: str
    author_agent_id: str
    content: str                # snapshot of resolved content
    tags: list[str]
    share_with_top_n: int       # -1 = all peers, N = top N by trust score
    created_at: str
    updated_at: str
    summary: Optional[str] = None
    signature_hex: Optional[str] = None
    file: str = ""
    from_text: str = ""
    to_text: str = ""
    function_anchor: Optional[str] = None
    original_content: Optional[str] = None
    original_hash: Optional[str] = None
    stale_views: int = 0


# =============================================================================
# Pools
# =============================================================================

@dataclass
class PoolProvider:
    """An API provider registered in a pool."""
    provider_id: str          # "pv-{hex12}"
    agent_id: str             # who registered this
    base_url: str             # "https://api.openai.com"
    credential_header: str    # "Authorization"
    credential_value: str     # never exposed to consumers
    allowed_paths: list[str]  # ["/v1/chat/completions"]
    price_per_call: float
    price_token: str          # "SOL" or mint address
    enabled: bool = True
    calls_served: int = 0
    failures: int = 0
    added_at: str = ""
    handler_config: dict = field(default_factory=dict)


@dataclass
class PoolAccessToken:
    """A consumer's access token with prepaid balance."""
    token_id: str             # "at-{hex16}", bearer token for consumer
    consumer_agent_id: str
    balance: float
    token_mint: str
    total_deposited: float
    total_spent: float
    calls_made: int = 0
    created_at: str = ""
    last_used: Optional[str] = None
    revoked: bool = False


@dataclass
class Pool:
    """A DarkMatter pool — credentialless API access for agents."""
    pool_id: str              # "pool-{hex12}"
    name: str
    tags: list[str]           # ["pool:llm", "pool:openai"]
    description: str = ""
    providers: list[PoolProvider] = field(default_factory=list)
    access_tokens: list[PoolAccessToken] = field(default_factory=list)
    created_at: str = ""
    shard_id: Optional[str] = None
    handler_type: str = "passthrough"


# =============================================================================
# Connection Requests
# =============================================================================

@dataclass
class PendingConnectionRequest:
    """An incoming connection request awaiting acceptance."""
    request_id: str
    from_agent_id: str
    from_agent_url: str
    from_agent_bio: str
    requested_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    from_agent_public_key_hex: Optional[str] = None
    from_agent_display_name: Optional[str] = None
    from_agent_wallets: dict[str, str] = field(default_factory=dict)
    from_agent_created_at: Optional[str] = None
    peer_trust: Optional[dict] = None
    mutual: bool = False
    # Challenge-response fields
    challenge_id: Optional[str] = None
    challenge_hex: Optional[str] = None


# =============================================================================
# Messages
# =============================================================================

@dataclass
class QueuedMessage:
    """A message waiting to be processed."""
    message_id: str
    content: str
    hops_remaining: int
    metadata: dict
    received_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    from_agent_id: Optional[str] = None
    verified: bool = False



# =============================================================================
# Routing
# =============================================================================

@dataclass
class RouterDecision:
    """Result of a router evaluating a message."""
    action: RouterAction
    forward_to: list[str] = field(default_factory=list)
    response: Optional[str] = None
    reason: Optional[str] = None


@dataclass
class RoutingRule:
    """A declarative routing rule configured via MCP tools."""
    rule_id: str
    action: str  # RouterAction value
    priority: int = 0
    enabled: bool = True
    # Match conditions (AND logic)
    keyword: Optional[str] = None
    from_agent_id: Optional[str] = None
    metadata_key: Optional[str] = None
    metadata_value: Optional[str] = None
    # Action parameters
    forward_to: list[str] = field(default_factory=list)
    response_text: Optional[str] = None


# =============================================================================
# Agent State (top-level)
# =============================================================================

@dataclass
class AgentState:
    """The complete state of this agent node."""
    agent_id: str
    bio: str
    status: AgentStatus
    port: int
    connections: dict[str, Connection] = field(default_factory=dict)
    pending_requests: dict[str, PendingConnectionRequest] = field(default_factory=dict)
    message_queue: list[QueuedMessage] = field(default_factory=list)
    messages_handled: int = 0
    created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    impressions: dict[str, Impression] = field(default_factory=dict)
    # Cryptographic identity
    private_key_hex: Optional[str] = None
    public_key_hex: Optional[str] = None
    display_name: Optional[str] = None
    # Wallets (chain -> address, derived from passport, not persisted)
    wallets: dict[str, str] = field(default_factory=dict)
    # Track outbound connection requests: url → agent_id (not persisted)
    pending_outbound: dict[str, str] = field(default_factory=dict)
    # LAN-discovered peers (ephemeral)
    discovered_peers: dict[str, dict] = field(default_factory=dict)
    # Rate limiting (global)
    rate_limit_global: int = 0
    _global_request_timestamps: deque = field(default_factory=deque)
    # Network resilience (ephemeral)
    public_url: Optional[str] = None
    _upnp_mapping: Optional[tuple] = None
    # Auto-reactivation timer
    inactive_until: Optional[str] = None
    # Inbox waiters — agents blocking on wait_for_message for new inbound messages
    _inbox_events: list[asyncio.Event] = field(default_factory=list)
    # Extensible message routing
    routing_rules: list = field(default_factory=list)
    router_mode: str = "spawn"
    # AntiMatter economy
    superagent_url: Optional[str] = None
    antimatter_log: list[dict] = field(default_factory=list)
    # Conversation memory
    conversation_log: list[ConversationEntry] = field(default_factory=list)
    # Shared shards
    shared_shards: list[SharedShard] = field(default_factory=list)
    # Pools
    pools: list = field(default_factory=list)  # list[Pool]
    # Security settings (persisted)
    security_settings: dict = field(default_factory=lambda: {
        "pin_hash": "",
        "auto_accept_local": True,
        "sandbox_enabled": False,
        "sandbox_network": True,
    })
