"""Gate:资金账户(spot account_book) + 交易账户(futures account_book),USDT."""
from __future__ import annotations

from typing import Any, Callable, Optional

from lib.account_ledger.account_ledger_normalize import (
    ACCOUNT_FUNDING,
    ACCOUNT_TRADING,
    from_ccxt_ledger_entry,
    kind_from_raw,
    make_ref_id,
    normalize_row,
)


def _sec(ms: int) -> int:
    return max(0, int(int(ms) // 1000))


def _paginate_spot_book(exchange, *, start_ms: int, end_ms: int, max_pages: int = 10) -> list[dict]:
    out: list[dict] = []
    # Gate spot account_book: from/to 为秒
    cursor = _sec(start_ms)
    end = _sec(end_ms)
    for _ in range(max_pages):
        try:
            batch = exchange.privateSpotGetAccountBook(
                {
                    "currency": "USDT",
                    "from": cursor,
                    "to": end,
                    "limit": 100,
                }
            )
        except Exception:
            break
        if not batch:
            break
        if isinstance(batch, dict):
            batch = batch.get("data") or batch.get("result") or []
        if not isinstance(batch, list) or not batch:
            break
        out.extend(batch)
        if len(batch) < 100:
            break
        last_t = batch[-1].get("time") or batch[-1].get("create_time")
        try:
            last_i = int(float(last_t))
        except Exception:
            break
        # spot 返回秒
        if last_i > 1e12:
            last_i = last_i // 1000
        if last_i >= end:
            break
        cursor = last_i + 1
    return out


def _paginate_swap_book(exchange, *, start_ms: int, end_ms: int, max_pages: int = 10) -> list[dict]:
    out: list[dict] = []
    cursor = _sec(start_ms)
    end = _sec(end_ms)
    for _ in range(max_pages):
        try:
            batch = exchange.privateFuturesGetSettleAccountBook(
                {
                    "settle": "usdt",
                    "from": cursor,
                    "to": end,
                    "limit": 100,
                }
            )
        except Exception:
            break
        if not batch:
            break
        if isinstance(batch, dict):
            batch = batch.get("data") or batch.get("result") or []
        if not isinstance(batch, list) or not batch:
            break
        out.extend(batch)
        if len(batch) < 100:
            break
        last_t = batch[-1].get("time")
        try:
            last_i = int(float(last_t))
        except Exception:
            break
        if last_i > 1e12:
            last_i = last_i // 1000
        if last_i >= end:
            break
        cursor = last_i + 1
    return out


def _spot_row(raw: dict) -> Optional[dict[str, Any]]:
    if not isinstance(raw, dict):
        return None
    amt = raw.get("change")
    ts = raw.get("time") or raw.get("create_time")
    # 秒 → 毫秒
    try:
        t = float(ts)
        if t < 1e12:
            t = t * 1000.0
        ts = t
    except Exception:
        pass
    raw_type = str(raw.get("type") or raw.get("change_type") or "")
    bal = raw.get("balance")
    ref = str(raw.get("id") or raw.get("txid") or "")
    return normalize_row(
        account=ACCOUNT_FUNDING,
        ccy="USDT",
        amount=amt,
        ts_ms=ts,
        ref_id=ref or make_ref_id("funding", raw_type, ts, amt),
        raw_type=raw_type,
        balance_after=bal,
        note=str(raw.get("text") or ""),
        kind=kind_from_raw(raw_type, float(amt) if amt is not None else None),
    )


def _swap_row(raw: dict) -> Optional[dict[str, Any]]:
    if not isinstance(raw, dict):
        return None
    # futures account_book: change, balance, type, text, time, contract...
    amt = raw.get("change")
    ts = raw.get("time")
    try:
        t = float(ts)
        if t < 1e12:
            t = t * 1000.0
        ts = t
    except Exception:
        pass
    raw_type = str(raw.get("type") or "")
    bal = raw.get("balance")
    ref = str(raw.get("id") or "")
    return normalize_row(
        account=ACCOUNT_TRADING,
        ccy="USDT",
        amount=amt,
        ts_ms=ts,
        ref_id=ref or make_ref_id("trading", raw_type, ts, amt, raw.get("contract")),
        raw_type=raw_type,
        balance_after=bal,
        symbol=str(raw.get("contract") or ""),
        note=str(raw.get("text") or ""),
        kind=kind_from_raw(raw_type, float(amt) if amt is not None else None),
    )


def fetch_gate_account_ledger(
    exchange,
    *,
    start_ms: int,
    end_ms: int,
    ensure_markets: Optional[Callable[[], None]] = None,
) -> tuple[list[dict[str, Any]], list[str]]:
    errors: list[str] = []
    rows: list[dict[str, Any]] = []
    if ensure_markets:
        try:
            ensure_markets()
        except Exception as e:
            errors.append(f"markets:{e}")

    try:
        for e in _paginate_spot_book(exchange, start_ms=start_ms, end_ms=end_ms):
            n = _spot_row(e)
            if n:
                rows.append(n)
    except Exception as e:
        errors.append(f"funding:{e}")
        # 回退 ccxt fetch_ledger
        try:
            batch = exchange.fetch_ledger(
                "USDT", int(start_ms), 100, {"type": "spot", "until": int(end_ms)}
            ) or []
            for e in batch:
                n = from_ccxt_ledger_entry(e, account=ACCOUNT_FUNDING)
                if n:
                    rows.append(n)
        except Exception as e2:
            errors.append(f"funding_fallback:{e2}")

    try:
        for e in _paginate_swap_book(exchange, start_ms=start_ms, end_ms=end_ms):
            n = _swap_row(e)
            if n:
                rows.append(n)
    except Exception as e:
        errors.append(f"trading:{e}")
        try:
            batch = exchange.fetch_ledger(
                "USDT",
                int(start_ms),
                100,
                {"type": "swap", "settle": "usdt", "until": int(end_ms)},
            ) or []
            for e in batch:
                n = from_ccxt_ledger_entry(e, account=ACCOUNT_TRADING)
                if n:
                    rows.append(n)
        except Exception as e2:
            errors.append(f"trading_fallback:{e2}")

    return rows, errors
