from __future__ import annotations

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

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.crypto.assets import create_crypto_asset
from jarvis_finance.crypto.holdings import calculate_crypto_holdings, create_initial_holding_snapshot
from jarvis_finance.crypto.transactions import record_crypto_transfer
from jarvis_finance.imports.common import stable_id, utc_now
from jarvis_finance.quality.alerts import create_alert

CRYPTO_MANUAL_REVIEW_COIN_LABELS = [
    "ADA Cardano",
    "Amp",
    "Avalanche AVAX",
    "Bitcoin BTC",
    "Chainlink",
    "DOGE",
    "Decentraland MANA",
    "Ethereum ETH",
    "HIGH Highstreet",
    "IOTA IOT",
    "LUNA Terra",
    "LUNC Terra Classic",
    "NETVR",
    "NuCypher NU",
    "Ox ZRX",
    "Polygon MATIC",
    "Ripple XRP",
    "SAND The Sandbox",
    "SAROS",
    "SKY",
    "SOL Solana",
    "Sundae",
    "USTC Terra Classic",
]

VALID_ADD_TYPES = {"initial_snapshot_addition", "manual_adjustment", "crypto_buy"}


@dataclass(frozen=True)
class CryptoManageResult:
    asset_id: str
    wallet_id: str
    holding_id: str | None = None
    transaction_id: str | None = None
    warnings: list[str] = field(default_factory=list)


def parse_decimal_text(value: str, *, field_name: str = "quantity") -> Decimal:
    text = str(value).strip()
    if not text:
        raise ValueError(f"{field_name} is required")
    try:
        dec = Decimal(text)
    except (InvalidOperation, ValueError) as exc:
        raise ValueError(f"{field_name} must be a Decimal string") from exc
    if not dec.is_finite():
        raise ValueError(f"{field_name} must be finite")
    return dec


def decimal_text(value: Decimal) -> str:
    return format(value, "f")


def _require_confirm(confirm: bool) -> None:
    if not confirm:
        raise ValueError("explicit confirm is required before saving")


def _require_date(effective_date: str) -> str:
    text = str(effective_date).strip()
    if not text:
        raise ValueError("date is required")
    # Keep validation intentionally simple and deterministic; Streamlit date_input supplies ISO dates.
    parts = text.split("-")
    if len(parts) != 3 or not all(part.isdigit() for part in parts):
        raise ValueError("date must be ISO YYYY-MM-DD")
    return text


def _require_wallet(conn: Connection, wallet_id: str) -> str:
    wallet_id = str(wallet_id).strip()
    if not wallet_id:
        raise ValueError("wallet is required")
    row = conn.execute("SELECT wallet_id FROM crypto_wallets WHERE wallet_id=?", (wallet_id,)).fetchone()
    if row is None:
        raise ValueError("wallet not found")
    return wallet_id


def _require_coin_fields(coin_name: str, symbol: str) -> tuple[str, str]:
    coin_name = str(coin_name).strip()
    symbol = str(symbol).strip().upper()
    if not coin_name:
        raise ValueError("coin name is required")
    if not symbol:
        raise ValueError("symbol is required")
    return coin_name, symbol


def _require_note(note: str, *, reason: str) -> str:
    note = str(note or "").strip()
    if not note:
        raise ValueError(f"note is required for {reason}")
    return note


def _find_asset(conn: Connection, *, coin_name: str, symbol: str, coingecko_id: str | None) -> str | None:
    if coingecko_id:
        row = conn.execute("SELECT asset_id FROM crypto_assets WHERE coingecko_id=?", (coingecko_id.strip(),)).fetchone()
        if row:
            return row["asset_id"]
    row = conn.execute(
        "SELECT asset_id FROM crypto_assets WHERE lower(coin_name)=lower(?) AND upper(symbol)=upper(?)",
        (coin_name, symbol),
    ).fetchone()
    return row["asset_id"] if row else None


def _ensure_asset(
    conn: Connection,
    *,
    coin_name: str,
    symbol: str,
    coingecko_id: str | None,
    coingecko_status: str,
    note: str | None,
) -> tuple[str, list[str]]:
    warnings: list[str] = []
    cg = coingecko_id.strip() if coingecko_id and coingecko_id.strip() else None
    existing = _find_asset(conn, coin_name=coin_name, symbol=symbol, coingecko_id=cg)
    if existing:
        asset_id = existing
    else:
        asset_id = create_crypto_asset(conn, coin_name=coin_name, symbol=symbol, coingecko_id=cg, notes=note)
    if not cg:
        warnings.append("missing_coingecko_id")
    if coingecko_status in {"missing_confirmed", "uncertain"}:
        rule = "crypto_coingecko_mapping_uncertain" if coingecko_status == "uncertain" else "missing_coingecko_id"
        create_alert(
            conn,
            priority="warnung",
            category="crypto",
            entity_type="crypto_asset",
            entity_id=asset_id,
            rule_id=rule,
            message="Crypto asset CoinGecko mapping needs manual confirmation.",
            evidence={"symbol": symbol, "coin_name": coin_name, "coingecko_status": coingecko_status},
            fingerprint=coingecko_status,
        )
        conn.commit()
        if rule not in warnings:
            warnings.append(rule)
    return asset_id, warnings


def current_wallet_asset_quantity(conn: Connection, *, wallet_id: str, asset_id: str) -> Decimal:
    result = calculate_crypto_holdings(conn)
    holding = result.wallet_holdings.get((wallet_id, asset_id))
    return holding.quantity if holding else Decimal("0")


def _insert_manual_adjustment_transaction(
    conn: Connection,
    *,
    asset_id: str,
    wallet_id: str,
    delta: Decimal,
    effective_date: str,
    note: str,
) -> str:
    if delta == 0:
        raise ValueError("manual adjustment delta must not be zero")
    now = utc_now()
    qty = abs(delta)
    tx_id = stable_id("cryptotx", "manual_adjustment", asset_id, wallet_id, decimal_text(delta), effective_date, now)
    from_wallet_id = wallet_id if delta < 0 else None
    to_wallet_id = wallet_id if delta > 0 else None
    conn.execute(
        """
        INSERT INTO crypto_transactions(
            crypto_transaction_id, transaction_type, asset_id, quantity,
            from_wallet_id, to_wallet_id, transaction_datetime, source,
            confirmation_status, parse_confidence, notes, created_at
        ) VALUES (?, 'manual_adjustment', ?, ?, ?, ?, ?, 'manual_dashboard', 'confirmed', 1, ?, ?)
        """,
        (tx_id, asset_id, decimal_text(qty), from_wallet_id, to_wallet_id, effective_date, note, now),
    )
    return tx_id


def add_crypto_position(
    conn: Connection,
    *,
    coin_name: str,
    symbol: str,
    wallet_id: str,
    quantity_text: str,
    effective_date: str,
    operation_type: str,
    note: str,
    confirm: bool,
    coingecko_id: str | None = None,
    coingecko_status: str = "selected",
) -> CryptoManageResult:
    _require_confirm(confirm)
    if operation_type not in VALID_ADD_TYPES:
        raise ValueError("unsupported crypto management operation type")
    if operation_type == "crypto_buy":
        raise ValueError("crypto_buy requires the dedicated ledger buy workflow; use manual_adjustment or initial_snapshot_addition in this MVP")
    effective_date = _require_date(effective_date)
    wallet_id = _require_wallet(conn, wallet_id)
    coin_name, symbol = _require_coin_fields(coin_name, symbol)
    quantity = parse_decimal_text(quantity_text)
    if quantity <= 0:
        raise ValueError("quantity must be > 0")
    if operation_type == "manual_adjustment":
        note = _require_note(note, reason="manual_adjustment")
    else:
        note = str(note or "").strip() or "manual initial snapshot addition"
    asset_id, warnings = _ensure_asset(conn, coin_name=coin_name, symbol=symbol, coingecko_id=coingecko_id, coingecko_status=coingecko_status, note=note)
    existing_holding = conn.execute(
        "SELECT crypto_holding_id FROM crypto_holdings WHERE asset_id=? AND wallet_id=?",
        (asset_id, wallet_id),
    ).fetchone()
    if operation_type == "initial_snapshot_addition":
        if existing_holding:
            raise ValueError("initial snapshot addition would overwrite an existing holding; use manual_adjustment")
        holding_id = create_initial_holding_snapshot(
            conn,
            asset_id=asset_id,
            wallet_id=wallet_id,
            quantity=quantity,
            verification_status="verified",
            last_verified_at=effective_date,
            legacy_snapshot_date=effective_date,
            note=note,
        )
        return CryptoManageResult(asset_id=asset_id, wallet_id=wallet_id, holding_id=holding_id, warnings=warnings)
    before = current_wallet_asset_quantity(conn, wallet_id=wallet_id, asset_id=asset_id)
    tx_id = _insert_manual_adjustment_transaction(conn, asset_id=asset_id, wallet_id=wallet_id, delta=quantity, effective_date=effective_date, note=note)
    after = before + quantity
    record_audit_event(
        conn,
        source="crypto_manage",
        action="crypto_manual_adjustment",
        entity_type="crypto_transaction",
        entity_id=tx_id,
        old_values={"asset_id": asset_id, "wallet_id": wallet_id, "quantity": decimal_text(before)},
        new_values={"asset_id": asset_id, "wallet_id": wallet_id, "quantity": decimal_text(after), "delta": decimal_text(quantity)},
        user_text_note=note,
        confirmed=True,
        created_by="dashboard",
    )
    conn.commit()
    return CryptoManageResult(asset_id=asset_id, wallet_id=wallet_id, transaction_id=tx_id, warnings=warnings)


def adjust_crypto_position(
    conn: Connection,
    *,
    asset_id: str,
    wallet_id: str,
    new_quantity_text: str,
    effective_date: str,
    note: str,
    confirm: bool,
) -> CryptoManageResult:
    _require_confirm(confirm)
    effective_date = _require_date(effective_date)
    wallet_id = _require_wallet(conn, wallet_id)
    note = _require_note(note, reason="manual_adjustment")
    new_quantity = parse_decimal_text(new_quantity_text, field_name="new quantity")
    if new_quantity < 0:
        raise ValueError("new quantity must be >= 0")
    asset = conn.execute("SELECT asset_id FROM crypto_assets WHERE asset_id=?", (asset_id,)).fetchone()
    if asset is None:
        raise ValueError("asset not found")
    before = current_wallet_asset_quantity(conn, wallet_id=wallet_id, asset_id=asset_id)
    delta = new_quantity - before
    if before + delta < 0:
        raise ValueError("negative holding blocked")
    tx_id = _insert_manual_adjustment_transaction(conn, asset_id=asset_id, wallet_id=wallet_id, delta=delta, effective_date=effective_date, note=note)
    record_audit_event(
        conn,
        source="crypto_manage",
        action="crypto_manual_adjustment",
        entity_type="crypto_transaction",
        entity_id=tx_id,
        old_values={"asset_id": asset_id, "wallet_id": wallet_id, "quantity": decimal_text(before)},
        new_values={"asset_id": asset_id, "wallet_id": wallet_id, "quantity": decimal_text(new_quantity), "delta": decimal_text(delta)},
        user_text_note=note,
        confirmed=True,
        created_by="dashboard",
    )
    if before != 0 and (new_quantity / before >= Decimal("10") or new_quantity == 0):
        create_alert(
            conn,
            priority="hinweis",
            category="crypto",
            entity_type="crypto_transaction",
            entity_id=tx_id,
            rule_id="crypto_manual_adjustment_review",
            message="Manual crypto adjustment should be reviewed because it materially changes the holding.",
            evidence={"asset_id": asset_id, "wallet_id": wallet_id},
            fingerprint="manual_adjustment_review",
        )
    conn.commit()
    return CryptoManageResult(asset_id=asset_id, wallet_id=wallet_id, transaction_id=tx_id)


def reduce_crypto_position(
    conn: Connection,
    *,
    asset_id: str,
    wallet_id: str,
    reduction_quantity_text: str,
    effective_date: str,
    note: str,
    confirm: bool,
) -> CryptoManageResult:
    _require_confirm(confirm)
    reduction = parse_decimal_text(reduction_quantity_text, field_name="reduction quantity")
    if reduction <= 0:
        raise ValueError("reduction quantity must be > 0")
    current = current_wallet_asset_quantity(conn, wallet_id=wallet_id, asset_id=asset_id)
    new_quantity = current - reduction
    if new_quantity < 0:
        raise ValueError("negative holding blocked")
    return adjust_crypto_position(
        conn,
        asset_id=asset_id,
        wallet_id=wallet_id,
        new_quantity_text=decimal_text(new_quantity),
        effective_date=effective_date,
        note=note,
        confirm=confirm,
    )


def transfer_crypto_position(
    conn: Connection,
    *,
    asset_id: str,
    from_wallet_id: str,
    to_wallet_id: str,
    quantity_text: str,
    effective_date: str,
    note: str,
    confirm: bool,
    fee_quantity_text: str | None = None,
) -> CryptoManageResult:
    _require_confirm(confirm)
    _require_date(effective_date)
    from_wallet_id = _require_wallet(conn, from_wallet_id)
    to_wallet_id = _require_wallet(conn, to_wallet_id)
    note = str(note or "").strip() or "manual wallet transfer"
    asset = conn.execute("SELECT asset_id FROM crypto_assets WHERE asset_id=?", (asset_id,)).fetchone()
    if asset is None:
        raise ValueError("asset not found")
    quantity = parse_decimal_text(quantity_text)
    if quantity <= 0:
        raise ValueError("quantity must be > 0")
    fee_quantity = parse_decimal_text(fee_quantity_text or "0", field_name="fee quantity")
    if fee_quantity < 0:
        raise ValueError("fee quantity must be >= 0")
    tx_id = record_crypto_transfer(
        conn,
        asset_id=asset_id,
        from_wallet_id=from_wallet_id,
        to_wallet_id=to_wallet_id,
        quantity=quantity,
        fee_quantity=fee_quantity,
        note=note,
    )
    return CryptoManageResult(asset_id=asset_id, wallet_id=to_wallet_id, transaction_id=tx_id)



def get_crypto_management_options(conn: Connection) -> dict[str, list[dict[str, str]] | list[str]]:
    wallets = [
        {"wallet_id": row["wallet_id"], "wallet_name": row["wallet_name"], "wallet_type": row["wallet_type"]}
        for row in conn.execute("SELECT wallet_id, wallet_name, wallet_type FROM crypto_wallets WHERE is_active=1 ORDER BY wallet_name").fetchall()
    ]
    assets = [
        {"asset_id": row["asset_id"], "coin_name": row["coin_name"], "symbol": row["symbol"], "coingecko_id": row["coingecko_id"] or ""}
        for row in conn.execute("SELECT asset_id, coin_name, symbol, coingecko_id FROM crypto_assets WHERE is_active=1 ORDER BY coin_name, symbol").fetchall()
    ]
    holdings = [
        {
            "asset_id": row["asset_id"],
            "wallet_id": row["wallet_id"],
            "coin_name": row["coin_name"],
            "symbol": row["symbol"],
            "wallet_name": row["wallet_name"],
            "quantity": row["quantity"],
        }
        for row in conn.execute(
            """
            SELECT h.asset_id, h.wallet_id, a.coin_name, a.symbol, w.wallet_name, h.quantity
            FROM crypto_holdings h
            JOIN crypto_assets a ON a.asset_id=h.asset_id
            JOIN crypto_wallets w ON w.wallet_id=h.wallet_id
            ORDER BY a.coin_name, w.wallet_name
            """
        ).fetchall()
    ]
    return {
        "wallets": wallets,
        "assets": assets,
        "holdings": holdings,
        "manual_review_coin_labels": list(CRYPTO_MANUAL_REVIEW_COIN_LABELS),
    }
