# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Dross Token Ledger

Token accounting for training data contributions. Contributors earn
Dross tokens based on:

  Formula: quality_score × record_count × consent_multiplier

  Consent multipliers:
    local_training = 1.0
    coop_sharing   = 1.5
    public_dataset = 2.0

Dross tokens can be synced to an L2 chain anchor when configured.

Storage: SQLite at ~/.familiar/data/dross/dross.db (tenant-scoped)

Dependencies: None (pure stdlib)
"""

import logging
import sqlite3
from dataclasses import asdict, dataclass
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)

# Consent multipliers for Dross minting
CONSENT_MULTIPLIERS = {
    "local_training": 1.0,
    "coop_sharing": 1.5,
    "public_dataset": 2.0,
    "none": 0.0,
}


@dataclass
class DrossLedgerEntry:
    """A single Dross token ledger entry."""

    address: str  # genesis_hash of contributor
    amount: float
    reason: str  # "contribution" | "quality_bonus" | "consent_bonus"
    block_hash: str = ""
    timestamp: str = ""


class DrossLedger:
    """
    SQLite-backed Dross token ledger.

    Tracks token minting and balances for training data contributors.
    """

    def __init__(self, db_path: Path = None):
        if db_path is None:
            try:
                from familiar.core.paths import DROSS_DIR

                DROSS_DIR.mkdir(parents=True, exist_ok=True)
                db_path = DROSS_DIR / "dross.db"
            except ImportError:
                d = Path.home() / ".familiar" / "data" / "dross"
                d.mkdir(parents=True, exist_ok=True)
                db_path = d / "dross.db"

        db_path.parent.mkdir(parents=True, exist_ok=True)
        self._db_path = db_path
        self._init_db()

    def _init_db(self):
        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS ledger (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    address TEXT NOT NULL,
                    amount REAL NOT NULL,
                    reason TEXT NOT NULL,
                    block_hash TEXT DEFAULT '',
                    timestamp TEXT NOT NULL
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_ledger_address
                ON ledger(address)
            """)

    def mint(self, address: str, contribution: dict) -> DrossLedgerEntry:
        """
        Mint Dross tokens for a contribution.

        Formula: quality_score × record_count × consent_multiplier

        Args:
            address: Contributor's genesis hash or user ID
            contribution: Dict with keys:
                - quality_score: float (0-1)
                - record_count: int
                - consent_scope: str (local_training, coop_sharing, public_dataset)
                - block_hash: str (optional, from chain anchor)

        Returns:
            DrossLedgerEntry with minted amount
        """
        quality = contribution.get("quality_score", 0.5)
        count = contribution.get("record_count", 0)
        consent_scope = contribution.get("consent_scope", "local_training")
        block_hash = contribution.get("block_hash", "")

        multiplier = CONSENT_MULTIPLIERS.get(consent_scope, 1.0)
        amount = round(quality * count * multiplier, 4)

        if amount <= 0:
            return DrossLedgerEntry(
                address=address,
                amount=0.0,
                reason="contribution",
                block_hash=block_hash,
                timestamp=datetime.now().isoformat(),
            )

        now = datetime.now().isoformat()
        entry = DrossLedgerEntry(
            address=address,
            amount=amount,
            reason="contribution",
            block_hash=block_hash,
            timestamp=now,
        )

        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute(
                "INSERT INTO ledger (address, amount, reason, block_hash, timestamp) "
                "VALUES (?, ?, ?, ?, ?)",
                (address, amount, "contribution", block_hash, now),
            )

        logger.info(
            "Minted %.4f Dross for %s (q=%.2f, n=%d, m=%.1f)",
            amount,
            address[:16],
            quality,
            count,
            multiplier,
        )
        return entry

    def mint_quality_bonus(
        self, address: str, quality_score: float, block_hash: str = ""
    ) -> DrossLedgerEntry:
        """
        Mint bonus tokens for high-quality contributions.

        Bonus: quality_score × 10 for scores above 0.8
        """
        if quality_score < 0.8:
            return DrossLedgerEntry(
                address=address,
                amount=0.0,
                reason="quality_bonus",
                timestamp=datetime.now().isoformat(),
            )

        amount = round(quality_score * 10, 4)
        now = datetime.now().isoformat()

        entry = DrossLedgerEntry(
            address=address,
            amount=amount,
            reason="quality_bonus",
            block_hash=block_hash,
            timestamp=now,
        )

        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute(
                "INSERT INTO ledger (address, amount, reason, block_hash, timestamp) "
                "VALUES (?, ?, ?, ?, ?)",
                (address, amount, "quality_bonus", block_hash, now),
            )

        return entry

    def mint_usage(self, address: str, amount: float, block_hash: str = "") -> DrossLedgerEntry:
        """
        Mint Dross for cross-peer resource usage.

        Unlike mint() which uses quality × count × consent formula,
        this directly mints a specified amount (pre-computed by
        UsageTracker from USAGE_MULTIPLIERS).
        """
        if amount <= 0:
            return DrossLedgerEntry(
                address=address,
                amount=0.0,
                reason="usage",
                block_hash=block_hash,
                timestamp=datetime.now().isoformat(),
            )

        now = datetime.now().isoformat()
        entry = DrossLedgerEntry(
            address=address,
            amount=amount,
            reason="usage",
            block_hash=block_hash,
            timestamp=now,
        )

        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute(
                "INSERT INTO ledger (address, amount, reason, block_hash, timestamp) "
                "VALUES (?, ?, ?, ?, ?)",
                (address, amount, "usage", block_hash, now),
            )

        logger.info("Minted %.4f Dross (usage) for %s", amount, address[:16])
        return entry

    def transfer(
        self, from_address: str, to_address: str, amount: float, block_hash: str = ""
    ) -> tuple | None:
        """
        Transfer Dross from one address to another.

        Returns (debit_entry, credit_entry) or None if insufficient balance.
        """
        if amount <= 0:
            return None
        if self.balance(from_address) < amount:
            return None

        now = datetime.now().isoformat()

        debit = DrossLedgerEntry(
            address=from_address,
            amount=-amount,
            reason="transfer_out",
            block_hash=block_hash,
            timestamp=now,
        )
        credit = DrossLedgerEntry(
            address=to_address,
            amount=amount,
            reason="transfer_in",
            block_hash=block_hash,
            timestamp=now,
        )

        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute(
                "INSERT INTO ledger (address, amount, reason, block_hash, timestamp) "
                "VALUES (?, ?, ?, ?, ?)",
                (from_address, -amount, "transfer_out", block_hash, now),
            )
            conn.execute(
                "INSERT INTO ledger (address, amount, reason, block_hash, timestamp) "
                "VALUES (?, ?, ?, ?, ?)",
                (to_address, amount, "transfer_in", block_hash, now),
            )

        logger.info("Transferred %.4f Dross: %s -> %s", amount, from_address[:16], to_address[:16])
        return debit, credit

    def spend(
        self, address: str, amount: float, reason: str = "spend", block_hash: str = ""
    ) -> DrossLedgerEntry | None:
        """
        Spend (debit) Dross from an address.

        Returns the debit entry or None if insufficient balance.
        """
        if amount <= 0:
            return None
        if self.balance(address) < amount:
            return None

        now = datetime.now().isoformat()
        entry = DrossLedgerEntry(
            address=address,
            amount=-amount,
            reason=reason,
            block_hash=block_hash,
            timestamp=now,
        )

        with sqlite3.connect(str(self._db_path)) as conn:
            conn.execute(
                "INSERT INTO ledger (address, amount, reason, block_hash, timestamp) "
                "VALUES (?, ?, ?, ?, ?)",
                (address, -amount, reason, block_hash, now),
            )

        logger.info("Spent %.4f Dross from %s (reason=%s)", amount, address[:16], reason)
        return entry

    def balance(self, address: str) -> float:
        """Get total Dross balance for an address."""
        with sqlite3.connect(str(self._db_path)) as conn:
            row = conn.execute(
                "SELECT COALESCE(SUM(amount), 0) FROM ledger WHERE address = ?",
                (address,),
            ).fetchone()
        return round(row[0], 4) if row else 0.0

    def ledger(self, address: str = None, limit: int = 50) -> list:
        """
        Get ledger entries, optionally filtered by address.

        Returns list of DrossLedgerEntry dicts, newest first.
        """
        with sqlite3.connect(str(self._db_path)) as conn:
            if address:
                rows = conn.execute(
                    "SELECT address, amount, reason, block_hash, timestamp "
                    "FROM ledger WHERE address = ? ORDER BY id DESC LIMIT ?",
                    (address, limit),
                ).fetchall()
            else:
                rows = conn.execute(
                    "SELECT address, amount, reason, block_hash, timestamp "
                    "FROM ledger ORDER BY id DESC LIMIT ?",
                    (limit,),
                ).fetchall()

        return [
            asdict(
                DrossLedgerEntry(
                    address=r[0],
                    amount=r[1],
                    reason=r[2],
                    block_hash=r[3] or "",
                    timestamp=r[4],
                )
            )
            for r in rows
        ]

    def total_supply(self) -> float:
        """Get total Dross supply across all addresses."""
        with sqlite3.connect(str(self._db_path)) as conn:
            row = conn.execute("SELECT COALESCE(SUM(amount), 0) FROM ledger").fetchone()
        return round(row[0], 4) if row else 0.0

    def top_contributors(self, limit: int = 10) -> list:
        """Get top contributors by Dross balance."""
        with sqlite3.connect(str(self._db_path)) as conn:
            rows = conn.execute(
                "SELECT address, SUM(amount) as total "
                "FROM ledger GROUP BY address ORDER BY total DESC LIMIT ?",
                (limit,),
            ).fetchall()

        return [{"address": r[0], "balance": round(r[1], 4)} for r in rows]

    def sync_to_l2(self, anchor) -> dict:
        """
        Sync local Dross balances to an L2 chain anchor.

        Args:
            anchor: L2Anchor instance (from chain_anchor module)

        Returns:
            Dict with sync results
        """
        contributors = self.top_contributors(limit=1000)

        if not contributors:
            return {"synced": 0, "total_supply": 0.0}

        synced = 0
        for c in contributors:
            try:
                anchor.update_balance(c["address"], c["balance"])
                synced += 1
            except (NotImplementedError, AttributeError):
                break
            except Exception as e:
                logger.debug("L2 balance sync failed for %s: %s", c["address"][:16], e)

        return {
            "synced": synced,
            "total_supply": self.total_supply(),
            "contributors": len(contributors),
        }
