from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal
from sqlite3 import Connection

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.imports.common import stable_id, utc_now
from jarvis_finance.quality.alerts import create_alert

VALID_VERIFICATION_STATUS = {"verified", "stale", "unverified", "estimated"}


@dataclass
class CryptoHolding:
    wallet_id: str | None
    asset_id: str
    quantity: Decimal = Decimal("0")
    verification_status: str = "unverified"
    last_verified_at: str | None = None
    legacy_snapshot_value_original: Decimal | None = None
    legacy_snapshot_value_chf: Decimal | None = None
    current_value_chf: Decimal | None = None
    quality_warnings: list[str] = field(default_factory=list)


@dataclass
class CryptoHoldingsResult:
    wallet_holdings: dict[tuple[str, str], CryptoHolding] = field(default_factory=dict)
    total_by_asset: dict[str, CryptoHolding] = field(default_factory=dict)


def _d(value: object, default: str = "0") -> Decimal:
    if value is None or str(value).strip() == "":
        return Decimal(default)
    return Decimal(str(value))


def _ds(value: Decimal | None) -> str | None:
    return format(value, "f") if value is not None else None


def _ensure_holding(result: CryptoHoldingsResult, wallet_id: str, asset_id: str) -> CryptoHolding:
    return result.wallet_holdings.setdefault((wallet_id, asset_id), CryptoHolding(wallet_id=wallet_id, asset_id=asset_id))


def _ensure_total(result: CryptoHoldingsResult, asset_id: str) -> CryptoHolding:
    return result.total_by_asset.setdefault(asset_id, CryptoHolding(wallet_id=None, asset_id=asset_id))


def create_initial_holding_snapshot(
    conn: Connection,
    *,
    asset_id: str,
    wallet_id: str,
    quantity: Decimal,
    verification_status: str,
    note: str,
    last_verified_at: str | None = None,
    legacy_snapshot_value_original: Decimal | None = None,
    legacy_snapshot_value_chf: Decimal | None = None,
    legacy_snapshot_currency: str | None = None,
    legacy_snapshot_date: str | None = None,
) -> str:
    if quantity < 0:
        raise ValueError("initial holding quantity must be >= 0")
    if verification_status not in VALID_VERIFICATION_STATUS:
        raise ValueError("invalid verification_status")
    if not note.strip():
        raise ValueError("initial holding snapshot requires a note")
    now = utc_now()
    holding_id = stable_id("cryptoholding", asset_id, wallet_id)
    conn.execute(
        """
        INSERT OR REPLACE INTO crypto_holdings(
            crypto_holding_id, asset_id, wallet_id, quantity, acquisition_source,
            last_verified_at, verification_status, legacy_snapshot_value_original,
            legacy_snapshot_value_chf, legacy_snapshot_currency, legacy_snapshot_date,
            notes, created_at, updated_at
        ) VALUES (?, ?, ?, ?, 'initial_snapshot', ?, ?, ?, ?, ?, ?, ?,
                  COALESCE((SELECT created_at FROM crypto_holdings WHERE crypto_holding_id=?), ?), ?)
        """,
        (
            holding_id, asset_id, wallet_id, _ds(quantity), last_verified_at, verification_status,
            _ds(legacy_snapshot_value_original),
            _ds(legacy_snapshot_value_chf),
            legacy_snapshot_currency, legacy_snapshot_date, note, holding_id, now, now,
        ),
    )
    record_audit_event(
        conn,
        source="crypto_holdings",
        action="initial_crypto_holding_snapshot",
        entity_type="crypto_holding",
        entity_id=holding_id,
        new_values={"asset_id": asset_id, "wallet_id": wallet_id, "quantity": _ds(quantity), "verification_status": verification_status},
        user_text_note=note,
        confirmed=True,
        created_by="system",
    )
    conn.commit()
    return holding_id


def _apply_delta(result: CryptoHoldingsResult, wallet_id: str, asset_id: str, delta: Decimal) -> None:
    holding = _ensure_holding(result, wallet_id, asset_id)
    holding.quantity += delta
    total = _ensure_total(result, asset_id)
    total.quantity += delta


def calculate_crypto_holdings(conn: Connection) -> CryptoHoldingsResult:
    result = CryptoHoldingsResult()
    for row in conn.execute("SELECT * FROM crypto_holdings ORDER BY wallet_id, asset_id").fetchall():
        holding = _ensure_holding(result, row["wallet_id"], row["asset_id"])
        holding.quantity += _d(row["quantity"])
        holding.verification_status = row["verification_status"]
        holding.last_verified_at = row["last_verified_at"]
        holding.legacy_snapshot_value_original = _d(row["legacy_snapshot_value_original"]) if row["legacy_snapshot_value_original"] is not None else None
        holding.legacy_snapshot_value_chf = _d(row["legacy_snapshot_value_chf"]) if row["legacy_snapshot_value_chf"] is not None else None
        total = _ensure_total(result, row["asset_id"])
        total.quantity += _d(row["quantity"])
    rows = conn.execute("SELECT * FROM crypto_transactions ORDER BY transaction_datetime, created_at, crypto_transaction_id").fetchall()
    for row in rows:
        asset_id = row["asset_id"]
        qty = _d(row["quantity"])
        fee_qty = _d(row["fee_quantity"])
        t = row["transaction_type"]
        if t == "transfer":
            _apply_delta(result, row["from_wallet_id"], asset_id, -(qty + fee_qty))
            _apply_delta(result, row["to_wallet_id"], asset_id, qty)
        elif t == "buy":
            _apply_delta(result, row["to_wallet_id"], asset_id, qty - fee_qty)
        elif t == "sell":
            _apply_delta(result, row["from_wallet_id"], asset_id, -(qty + fee_qty))
        elif t == "fee":
            _apply_delta(result, row["from_wallet_id"], asset_id, -fee_qty)
        elif t == "manual_adjustment":
            if row["to_wallet_id"]:
                _apply_delta(result, row["to_wallet_id"], asset_id, qty)
            elif row["from_wallet_id"]:
                _apply_delta(result, row["from_wallet_id"], asset_id, -qty)
    for asset in conn.execute("SELECT asset_id, symbol, coingecko_id FROM crypto_assets").fetchall():
        if not asset["coingecko_id"]:
            msg = "Crypto asset has no CoinGecko ID; current valuation is unavailable."
            for holding in [h for h in result.wallet_holdings.values() if h.asset_id == asset["asset_id"]]:
                holding.quality_warnings.append("missing_coingecko_id")
            create_alert(
                conn,
                priority="warnung",
                category="crypto",
                entity_type="crypto_asset",
                entity_id=asset["asset_id"],
                rule_id="missing_coingecko_id",
                message=msg,
                evidence={"symbol": asset["symbol"]},
                fingerprint="missing_coingecko_id",
            )
    conn.commit()
    return result
