from __future__ import annotations

from dataclasses import asdict
from typing import Any

from crypto_spot_bot.bybit import BybitClient, Instrument
from crypto_spot_bot.config import Settings
from crypto_spot_bot.storage import Storage


def reconciliation_snapshot(
    *,
    settings: Settings,
    storage: Storage,
    client: BybitClient,
    instruments: dict[str, Instrument],
) -> dict[str, Any]:
    local_positions = storage.open_positions()
    local = [
        {
            "id": position.id,
            "symbol": position.symbol,
            "qty": position.qty,
            "entry_price": position.entry_price,
            "notional_usdt": position.notional_usdt,
        }
        for position in local_positions
    ]
    if not settings.live_ready:
        return {
            "status": "paper",
            "live_ready": False,
            "local_positions": local,
            "remote_balances": [],
            "open_orders": [],
            "discrepancies": [],
            "message": "reconciliation requires unlocked live Bybit credentials",
        }
    try:
        coins = _coins_for_symbols(settings.symbols, instruments)
        wallet = client.wallet_balance(coin=",".join(coins))
        orders = client.realtime_orders(category="spot", open_only=0, limit=50)
    except Exception as exc:
        return {
            "status": "error",
            "live_ready": True,
            "local_positions": local,
            "remote_balances": [],
            "open_orders": [],
            "discrepancies": [{"severity": "error", "code": "bybit_read_failed", "message": str(exc)}],
        }

    balances = _balances(wallet)
    open_orders = orders.get("list", []) if isinstance(orders.get("list"), list) else []
    discrepancies = _discrepancies(local_positions, balances, instruments)
    return {
        "status": "warn" if discrepancies else "ok",
        "live_ready": True,
        "local_positions": local,
        "remote_balances": balances,
        "open_orders": open_orders,
        "discrepancies": discrepancies,
        "account": _account_summary(wallet),
        "instruments": {symbol: asdict(info) for symbol, info in instruments.items() if symbol in settings.symbols},
    }


def _coins_for_symbols(symbols: tuple[str, ...], instruments: dict[str, Instrument]) -> list[str]:
    coins = {"USDT"}
    for symbol in symbols:
        info = instruments.get(symbol)
        if info and info.base_coin:
            coins.add(info.base_coin.upper())
        elif symbol.endswith("USDT"):
            coins.add(symbol[:-4].upper())
    return sorted(coins)


def _balances(wallet: dict[str, Any]) -> list[dict[str, Any]]:
    rows = wallet.get("list")
    if not isinstance(rows, list) or not rows:
        return []
    coins = rows[0].get("coin")
    if not isinstance(coins, list):
        return []
    output = []
    for row in coins:
        if not isinstance(row, dict):
            continue
        output.append(
            {
                "coin": str(row.get("coin", "")),
                "equity": _float(row.get("equity")),
                "wallet_balance": _float(row.get("walletBalance")),
                "locked": _float(row.get("locked")),
                "usd_value": _float(row.get("usdValue")),
            }
        )
    return output


def _account_summary(wallet: dict[str, Any]) -> dict[str, Any]:
    rows = wallet.get("list")
    if not isinstance(rows, list) or not rows:
        return {}
    row = rows[0]
    return {
        "account_type": row.get("accountType"),
        "total_equity": _float(row.get("totalEquity")),
        "total_wallet_balance": _float(row.get("totalWalletBalance")),
        "total_available_balance": _float(row.get("totalAvailableBalance")),
    }


def _discrepancies(
    positions: list[Any],
    balances: list[dict[str, Any]],
    instruments: dict[str, Instrument],
) -> list[dict[str, Any]]:
    by_coin = {str(row.get("coin", "")).upper(): row for row in balances}
    local_by_coin: dict[str, float] = {}
    for position in positions:
        info = instruments.get(position.symbol)
        coin = info.base_coin.upper() if info and info.base_coin else position.symbol.removesuffix("USDT")
        local_by_coin[coin] = local_by_coin.get(coin, 0.0) + float(position.qty)
    issues = []
    for coin, local_qty in local_by_coin.items():
        remote_qty = _float((by_coin.get(coin) or {}).get("equity"))
        tolerance = max(1e-8, local_qty * 0.002)
        if remote_qty + tolerance < local_qty:
            issues.append(
                {
                    "severity": "error",
                    "code": "remote_balance_below_local_position",
                    "coin": coin,
                    "local_qty": round(local_qty, 10),
                    "remote_qty": round(remote_qty, 10),
                }
            )
    for coin, row in by_coin.items():
        if coin == "USDT":
            continue
        remote_qty = _float(row.get("equity"))
        local_qty = local_by_coin.get(coin, 0.0)
        if remote_qty > max(1e-8, local_qty * 1.05) and local_qty <= 0:
            issues.append(
                {
                    "severity": "warn",
                    "code": "remote_asset_without_local_position",
                    "coin": coin,
                    "remote_qty": round(remote_qty, 10),
                }
            )
    return issues


def _float(value: Any) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0
