# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Licensed under the MIT License

"""
Mesh Usage Tracker — Borrowed-Use Tracking via Dross

Tracks cross-peer resource usage (memories, markdown, models) and
connects to the Dross token economy. When peer B uses peer A's
resource, A earns Dross — all anchored to the genesis chain for
verifiable provenance.

Storage: SQLite at MESH_DIR/usage.db
Dependencies: None (pure stdlib + optional Dross/chain integration)
"""

from __future__ import annotations

import logging
import sqlite3
import threading
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional

logger = logging.getLogger(__name__)

# Usage multipliers for Dross minting
USAGE_MULTIPLIERS = {
    "memory_query": 0.1,
    "memory_push_used": 0.5,
    "markdown_push_used": 0.5,
    "markdown_pull": 0.3,
    "model_inference": 1.0,
    "model_download": 5.0,
}


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


# ============================================================
# USAGE RECORD
# ============================================================


@dataclass
class UsageRecord:
    """A single cross-peer usage event."""

    provider_node: str
    provider_genesis_hash: str
    consumer_node: str
    consumer_genesis_hash: str
    resource_type: str  # key in USAGE_MULTIPLIERS
    resource_id: str  # memory key, topic slug, or model_id
    usage_count: int = 1
    dross_amount: float = 0.0  # computed from multiplier
    chain_block_hash: str = ""  # set after anchoring
    timestamp: str = ""
    synced: bool = False  # whether counterpart notified

    def __post_init__(self):
        if not self.timestamp:
            self.timestamp = _utcnow().isoformat()

    def to_dict(self) -> dict:
        d = asdict(self)
        d["synced"] = int(d["synced"])
        return d

    @classmethod
    def from_dict(cls, d: dict) -> "UsageRecord":
        d = d.copy()
        if "synced" in d:
            d["synced"] = bool(d["synced"])
        known = {f.name for f in cls.__dataclass_fields__.values()}
        return cls(**{k: v for k, v in d.items() if k in known})


# ============================================================
# USAGE TRACKER
# ============================================================


class UsageTracker:
    """
    SQLite-backed usage tracker for cross-peer resource usage.

    Records usage events, computes Dross amounts from multipliers,
    optionally anchors to the genesis chain and mints Dross tokens.
    """

    def __init__(
        self,
        node_id: str,
        mesh_dir: Path,
        dross_ledger: Any = None,
        hipaa_enabled: bool = False,
    ):
        self.node_id = node_id
        self._dross_ledger = dross_ledger
        self.hipaa_enabled = hipaa_enabled

        mesh_dir.mkdir(parents=True, exist_ok=True)
        self._db_path = mesh_dir / "usage.db"
        self._lock = threading.Lock()
        self._init_db()

        # Gateway reference for sending notifications
        self._gateway = None

    def set_gateway(self, gateway):
        """Set reference to MeshGateway for sending usage notifications."""
        self._gateway = gateway

    def _init_db(self):
        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS usage (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    provider_node TEXT NOT NULL,
                    provider_genesis_hash TEXT DEFAULT '',
                    consumer_node TEXT NOT NULL,
                    consumer_genesis_hash TEXT DEFAULT '',
                    resource_type TEXT NOT NULL,
                    resource_id TEXT NOT NULL,
                    usage_count INTEGER DEFAULT 1,
                    dross_amount REAL DEFAULT 0.0,
                    chain_block_hash TEXT DEFAULT '',
                    timestamp TEXT NOT NULL,
                    synced INTEGER DEFAULT 0
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_usage_provider
                ON usage(provider_node)
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_usage_consumer
                ON usage(consumer_node)
            """)

    # ── Recording ──

    def record_usage(
        self,
        provider_node: str,
        consumer_node: str,
        resource_type: str,
        resource_id: str,
        count: int = 1,
        provider_genesis_hash: str = "",
        consumer_genesis_hash: str = "",
    ) -> Optional[UsageRecord]:
        """
        Record a cross-peer usage event.

        Computes Dross amount from USAGE_MULTIPLIERS, anchors to chain,
        and mints Dross to the provider.

        Returns UsageRecord or None if HIPAA mode.
        """
        if self.hipaa_enabled:
            return None

        multiplier = USAGE_MULTIPLIERS.get(resource_type, 0.0)
        dross_amount = round(multiplier * count, 4)

        # Anchor to genesis chain
        block_hash = ""
        try:
            block_hash = self._anchor_usage(
                {
                    "provider_node": provider_node,
                    "provider_genesis_hash": provider_genesis_hash,
                    "consumer_node": consumer_node,
                    "consumer_genesis_hash": consumer_genesis_hash,
                    "resource_type": resource_type,
                    "resource_id": resource_id,
                    "usage_count": count,
                    "dross_amount": dross_amount,
                }
            )
        except Exception as e:
            logger.debug(f"Chain anchor failed: {e}")

        now = _utcnow().isoformat()
        record = UsageRecord(
            provider_node=provider_node,
            provider_genesis_hash=provider_genesis_hash,
            consumer_node=consumer_node,
            consumer_genesis_hash=consumer_genesis_hash,
            resource_type=resource_type,
            resource_id=resource_id,
            usage_count=count,
            dross_amount=dross_amount,
            chain_block_hash=block_hash,
            timestamp=now,
        )

        # Store in SQLite
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                conn.execute(
                    "INSERT INTO usage "
                    "(provider_node, provider_genesis_hash, consumer_node, "
                    "consumer_genesis_hash, resource_type, resource_id, "
                    "usage_count, dross_amount, chain_block_hash, timestamp, synced) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (
                        provider_node,
                        provider_genesis_hash,
                        consumer_node,
                        consumer_genesis_hash,
                        resource_type,
                        resource_id,
                        count,
                        dross_amount,
                        block_hash,
                        now,
                        0,
                    ),
                )

        # Mint Dross to provider
        if dross_amount > 0 and self._dross_ledger and provider_genesis_hash:
            try:
                self._dross_ledger.mint_usage(
                    address=provider_genesis_hash,
                    amount=dross_amount,
                    block_hash=block_hash,
                )
            except Exception as e:
                logger.debug(f"Dross mint failed: {e}")

        # Send notification to provider via mesh
        self._notify_provider(record)

        logger.debug(
            "Usage recorded: %s used %s from %s (%.4f Dross)",
            consumer_node[:8],
            resource_type,
            provider_node[:8],
            dross_amount,
        )
        return record

    def handle_remote_usage(self, message: dict):
        """
        Store provider-side copy of usage record.
        Called when we receive a mesh.usage.record notification.
        """
        if self.hipaa_enabled:
            return

        provider_node = message.get("provider_node", "")
        if provider_node != self.node_id:
            # Not for us
            return

        now = _utcnow().isoformat()
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                conn.execute(
                    "INSERT INTO usage "
                    "(provider_node, provider_genesis_hash, consumer_node, "
                    "consumer_genesis_hash, resource_type, resource_id, "
                    "usage_count, dross_amount, chain_block_hash, timestamp, synced) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (
                        message.get("provider_node", ""),
                        message.get("provider_genesis_hash", ""),
                        message.get("consumer_node", ""),
                        message.get("consumer_genesis_hash", ""),
                        message.get("resource_type", ""),
                        message.get("resource_id", ""),
                        message.get("usage_count", 1),
                        message.get("dross_amount", 0.0),
                        message.get("chain_block_hash", ""),
                        message.get("timestamp", now),
                        1,  # already synced since it came from counterpart
                    ),
                )

    def _notify_provider(self, record: UsageRecord):
        """Send a mesh.usage.record notification to the provider."""
        if not self._gateway or record.provider_node == self.node_id:
            return

        from .memory_bridge import MESH_USAGE_RECORD

        try:
            import asyncio

            from .gateway import Message, MsgType

            msg = Message(
                type=MsgType.TOOL_REQUEST,
                payload={
                    "mesh_event": True,
                    "topic": MESH_USAGE_RECORD,
                    "data": record.to_dict(),
                },
            )

            ws = self._gateway.peer_gateways.get(record.provider_node)
            if ws:
                try:
                    loop = asyncio.get_event_loop()
                    if loop.is_running():
                        asyncio.ensure_future(ws.send(msg.to_json()))
                    else:
                        loop.run_until_complete(ws.send(msg.to_json()))
                except RuntimeError:
                    asyncio.run(ws.send(msg.to_json()))
        except Exception as e:
            logger.debug(f"Failed to notify provider: {e}")

    def _anchor_usage(self, usage_entry: dict) -> str:
        """Anchor usage event to genesis chain. Returns block hash or ''."""
        try:
            from ...skills.training.bridge import anchor_usage

            return anchor_usage(usage_entry)
        except (ImportError, Exception):
            return ""

    # ── Queries ──

    def get_usage_as_provider(self, limit: int = 50) -> list:
        """Get usage records where we are the provider."""
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                rows = conn.execute(
                    "SELECT provider_node, provider_genesis_hash, consumer_node, "
                    "consumer_genesis_hash, resource_type, resource_id, "
                    "usage_count, dross_amount, chain_block_hash, timestamp, synced "
                    "FROM usage WHERE provider_node = ? "
                    "ORDER BY id DESC LIMIT ?",
                    (self.node_id, limit),
                ).fetchall()
        return [self._row_to_dict(r) for r in rows]

    def get_usage_as_consumer(self, limit: int = 50) -> list:
        """Get usage records where we are the consumer."""
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                rows = conn.execute(
                    "SELECT provider_node, provider_genesis_hash, consumer_node, "
                    "consumer_genesis_hash, resource_type, resource_id, "
                    "usage_count, dross_amount, chain_block_hash, timestamp, synced "
                    "FROM usage WHERE consumer_node = ? "
                    "ORDER BY id DESC LIMIT ?",
                    (self.node_id, limit),
                ).fetchall()
        return [self._row_to_dict(r) for r in rows]

    def get_earnings(self) -> float:
        """Get total Dross earned as a provider."""
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                row = conn.execute(
                    "SELECT COALESCE(SUM(dross_amount), 0) FROM usage WHERE provider_node = ?",
                    (self.node_id,),
                ).fetchone()
        return round(row[0], 4) if row else 0.0

    def get_spending(self) -> float:
        """Get total Dross consumed."""
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                row = conn.execute(
                    "SELECT COALESCE(SUM(dross_amount), 0) FROM usage WHERE consumer_node = ?",
                    (self.node_id,),
                ).fetchone()
        return round(row[0], 4) if row else 0.0

    def get_usage_summary(self) -> dict:
        """Get a summary of all usage activity."""
        with self._lock:
            with sqlite3.connect(str(self._db_path)) as conn:
                # Total provided
                row = conn.execute(
                    "SELECT COUNT(*), COALESCE(SUM(dross_amount), 0) "
                    "FROM usage WHERE provider_node = ?",
                    (self.node_id,),
                ).fetchone()
                total_provided = row[0] if row else 0
                earnings = round(row[1], 4) if row else 0.0

                # Total consumed
                row = conn.execute(
                    "SELECT COUNT(*), COALESCE(SUM(dross_amount), 0) "
                    "FROM usage WHERE consumer_node = ?",
                    (self.node_id,),
                ).fetchone()
                total_consumed = row[0] if row else 0
                spending = round(row[1], 4) if row else 0.0

                # By type
                rows = conn.execute(
                    "SELECT resource_type, COUNT(*), COALESCE(SUM(dross_amount), 0) "
                    "FROM usage GROUP BY resource_type"
                ).fetchall()
                by_type = {r[0]: {"count": r[1], "dross": round(r[2], 4)} for r in rows}

                # Top peers
                rows = conn.execute(
                    "SELECT provider_node, COALESCE(SUM(dross_amount), 0) as total "
                    "FROM usage WHERE consumer_node = ? "
                    "GROUP BY provider_node ORDER BY total DESC LIMIT 10",
                    (self.node_id,),
                ).fetchall()
                top_peers = [{"node_id": r[0], "dross": round(r[1], 4)} for r in rows]

        return {
            "total_provided": total_provided,
            "total_consumed": total_consumed,
            "earnings": earnings,
            "spending": spending,
            "by_type": by_type,
            "top_peers": top_peers,
        }

    @staticmethod
    def _row_to_dict(row: tuple) -> dict:
        return {
            "provider_node": row[0],
            "provider_genesis_hash": row[1],
            "consumer_node": row[2],
            "consumer_genesis_hash": row[3],
            "resource_type": row[4],
            "resource_id": row[5],
            "usage_count": row[6],
            "dross_amount": row[7],
            "chain_block_hash": row[8],
            "timestamp": row[9],
            "synced": bool(row[10]),
        }
