from __future__ import annotations

from datetime import timedelta
from typing import Any

from rich.console import Group
from rich.panel import Panel
from typer import Context, Option, Typer

from .client import ApiResponse
from .output import run_command
from .render import group_parts, make_kv_table, make_rows_table, make_warning_text
from .state import TerminalState

app = Typer(name="uforia", help="Read-only terminal client for the Uforia analytics platform.")
system_app = Typer(help="System health and service status.")
overview_app = Typer(help="Overview market context and price summaries.")
signals_app = Typer(help="Signal families and individual signal workspaces.")
options_app = Typer(help="Options dashboard and signal data.")
perps_app = Typer(help="Perps dashboard and signal data.")
composite_app = Typer(help="Composite index data, decomposition, and transitions.")
datasets_app = Typer(help="Dataset inventory and row access.")
runs_app = Typer(help="Builder runs and run summary.")
engines_app = Typer(help="Engine catalog and status.")
execution_app = Typer(help="Read-only execution state.")

app.add_typer(system_app, name="system")
app.add_typer(overview_app, name="overview")
app.add_typer(signals_app, name="signals")
app.add_typer(options_app, name="options")
app.add_typer(perps_app, name="perps")
app.add_typer(composite_app, name="composite")
app.add_typer(datasets_app, name="datasets")
app.add_typer(runs_app, name="runs")
app.add_typer(engines_app, name="engines")
app.add_typer(execution_app, name="execution")


def get_state(ctx: Context) -> TerminalState:
    return ctx.obj


def _row_limit(state: TerminalState, full: int = 12, compact: int = 6) -> int:
    return compact if state.config.compact else full


def _render_api_response(state: TerminalState, title: str, response: ApiResponse[Any], sections: list[Any]) -> None:
    state.console.print(
        group_parts(
            Panel(f"{title}\nAPI generated at {response.meta.generated_at.isoformat()}", title="Uforia terminal"),
            *sections,
            make_warning_text(response.warnings),
        )
    )


@system_app.command("show")
def system_show(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)
    filters: dict[str, Any] = {}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_system_health()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        _render_api_response(
            current_state,
            "System health",
            response,
            [make_kv_table("Health", response.data.model_dump(mode="json"))],
        )

    run_command(state, "system.show", filters, fetch, render, watch=watch)


@overview_app.command("show")
def overview_show(
    ctx: Context,
    asset: str = Option("BTC", "--asset"),
    venue_scope: str = Option("binance", "--venue-scope"),
    timeframe: str = Option("15m", "--timeframe"),
    range_days: int = Option(1, "--range-days"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {
        "asset": asset,
        "venue_scope": venue_scope,
        "timeframe": timeframe,
        "range_days": range_days,
    }

    def fetch() -> dict[str, Any]:
        health = state.client.get_system_health()
        prices = state.client.get_prices()
        latest_price = next((row for row in prices.data if row.asset == asset), None)
        series = state.client.get_price_series(
            asset=asset,
            venue_scope=venue_scope,
            timeframe=timeframe,
            max_points=max(24, range_days * 96),
        )
        composite = state.client.get_composite_signal_latest("market_composite")
        return {
            "health": health,
            "prices": prices,
            "latest_price": latest_price,
            "series": series,
            "composite": composite,
        }

    def render(current_state: TerminalState, payload: dict[str, Any]) -> None:
        price_rows = [row.model_dump(mode="json") for row in payload["prices"].data]
        candle_rows = [
            point.model_dump(mode="json") for point in payload["series"].data.points[-12:]
        ]
        sections = [
            make_kv_table("System", payload["health"].data.model_dump(mode="json")),
            make_kv_table(
                f"{asset} latest candle",
                payload["series"].data.latest_values,
            ),
            make_kv_table(
                "Market composite",
                payload["composite"].data.model_dump(mode="json"),
            ),
            make_rows_table("Current prices", price_rows),
            make_rows_table("Recent candles", candle_rows, limit=_row_limit(current_state)),
        ]
        _render_api_response(current_state, "Overview", payload["health"], sections)

    run_command(state, "overview.show", filters, fetch, render, watch=watch)


@signals_app.command("list")
def signals_list(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_signals()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(
            current_state,
            "Signals",
            response,
            [make_rows_table("Signal families", rows)],
        )

    run_command(
        state,
        "signals.list",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@signals_app.command("show")
def signals_show(
    ctx: Context,
    signal_id: str,
    source: str | None = Option(None, "--source"),
    quality: str | None = Option(None, "--quality"),
    range_days: int = Option(1, "--range-days"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"signal_id": signal_id, "source": source, "quality": quality, "range_days": range_days}

    def fetch() -> dict[str, Any]:
        latest = state.client.get_signal_latest(signal_id, source)
        end = latest.data.latest_updated_at
        start = (end - timedelta(days=range_days)).isoformat() if end else None
        series = state.client.get_signal_series(
            signal_id,
            source=source,
            start=start,
            end=end.isoformat() if end else None,
            quality=quality,
            max_points=max(24, range_days * 96),
        )
        return {"latest": latest, "series": series}

    def render(current_state: TerminalState, payload: dict[str, Any]) -> None:
        latest = payload["latest"]
        series = payload["series"]
        sections = [
            make_kv_table("Latest summary", latest.data.model_dump(mode="json")),
            make_kv_table("Latest metrics", series.data.latest_values),
            make_rows_table(
                "Recent series rows",
                [point.model_dump(mode="json") for point in series.data.points[-12:]],
                limit=_row_limit(current_state),
            ),
        ]
        _render_api_response(current_state, f"Signal {signal_id}", latest, sections)

    run_command(state, "signals.show", filters, fetch, render, watch=watch)


@options_app.command("dashboards")
def options_dashboards(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_options_dashboards()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(
            current_state,
            "Options dashboards",
            response,
            [make_rows_table("Dashboards", rows)],
        )

    run_command(
        state,
        "options.dashboards",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@options_app.command("show")
def options_show(
    ctx: Context,
    dashboard_id: str,
    underlying: str | None = Option(None, "--underlying"),
    quality: str | None = Option(None, "--quality"),
    range_days: int = Option(1, "--range-days"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {
        "dashboard_id": dashboard_id,
        "underlying": underlying,
        "quality": quality,
        "range_days": range_days,
    }

    def fetch() -> dict[str, Any]:
        latest = state.client.get_options_dashboard_latest(dashboard_id, underlying)
        end = latest.data.latest_updated_at
        start = (end - timedelta(days=range_days)).isoformat() if end else None
        series = state.client.get_options_dashboard_series(
            dashboard_id,
            underlying=underlying,
            start=start,
            end=end.isoformat() if end else None,
            quality=quality,
            max_points=max(24, range_days * 96),
        )
        return {"latest": latest, "series": series}

    def render(current_state: TerminalState, payload: dict[str, Any]) -> None:
        latest = payload["latest"]
        series = payload["series"]
        sections = [
            make_kv_table("Latest summary", latest.data.model_dump(mode="json")),
            make_kv_table("Latest metrics", series.data.latest_values),
            make_rows_table(
                "Recent rows",
                [point.model_dump(mode="json") for point in series.data.points[-12:]],
                limit=_row_limit(current_state),
            ),
            make_kv_table("Context", series.data.context),
        ]
        _render_api_response(current_state, f"Options {dashboard_id}", latest, sections)

    run_command(state, "options.show", filters, fetch, render, watch=watch)


@options_app.command("signals")
def options_signals(
    ctx: Context,
    underlying: str | None = Option(None, "--underlying"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"underlying": underlying}

    def fetch() -> dict[str, Any]:
        signals = state.client.get_options_signals()
        if underlying is None:
            return {"signals": signals}
        latest_rows = []
        for signal in signals.data:
            latest_rows.append(state.client.get_options_signal_latest(signal.id, underlying).data)
        return {"signals": ApiResponse(latest_rows, signals.meta, signals.warnings)}

    def render(current_state: TerminalState, payload: dict[str, Any]) -> None:
        response = payload["signals"]
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Options signals", response, [make_rows_table("Signals", rows)])

    run_command(
        state,
        "options.signals",
        filters,
        fetch,
        render,
        ndjson_items=lambda payload: payload["signals"].data,
        watch=watch,
    )


@perps_app.command("dashboards")
def perps_dashboards(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_perps_dashboards()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(
            current_state,
            "Perps dashboards",
            response,
            [make_rows_table("Dashboards", rows)],
        )

    run_command(
        state,
        "perps.dashboards",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@perps_app.command("show")
def perps_show(
    ctx: Context,
    dashboard_id: str,
    asset: str = Option("BTC", "--asset"),
    venue_scope: str = Option("all", "--venue-scope"),
    quality: str | None = Option(None, "--quality"),
    range_days: int = Option(1, "--range-days"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {
        "dashboard_id": dashboard_id,
        "asset": asset,
        "venue_scope": venue_scope,
        "quality": quality,
        "range_days": range_days,
    }

    def fetch() -> dict[str, Any]:
        latest = state.client.get_perps_dashboard_latest(dashboard_id, asset, venue_scope)
        end = latest.data.latest_updated_at
        start = (end - timedelta(days=range_days)).isoformat() if end else None
        series = state.client.get_perps_dashboard_series(
            dashboard_id,
            asset=asset,
            venue_scope=venue_scope,
            start=start,
            end=end.isoformat() if end else None,
            quality=quality,
            max_points=max(24, range_days * 96),
        )
        return {"latest": latest, "series": series}

    def render(current_state: TerminalState, payload: dict[str, Any]) -> None:
        latest = payload["latest"]
        series = payload["series"]
        sections = [
            make_kv_table("Latest summary", latest.data.model_dump(mode="json")),
            make_kv_table("Latest metrics", series.data.latest_values),
            make_rows_table(
                "Recent rows",
                [point.model_dump(mode="json") for point in series.data.points[-12:]],
                limit=_row_limit(current_state),
            ),
            make_kv_table("Context", series.data.context),
        ]
        _render_api_response(current_state, f"Perps {dashboard_id}", latest, sections)

    run_command(state, "perps.show", filters, fetch, render, watch=watch)


@perps_app.command("signals")
def perps_signals(
    ctx: Context,
    asset: str = Option("BTC", "--asset"),
    venue_scope: str = Option("all", "--venue-scope"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"asset": asset, "venue_scope": venue_scope}

    def fetch() -> ApiResponse[Any]:
        signals = state.client.get_perps_signals()
        latest_rows = [
            state.client.get_perps_signal_latest(signal.id, asset, venue_scope).data
            for signal in signals.data
        ]
        return ApiResponse(latest_rows, signals.meta, signals.warnings)

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Perps signals", response, [make_rows_table("Signals", rows)])

    run_command(
        state,
        "perps.signals",
        filters,
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@composite_app.command("list")
def composite_list(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_composite_signals()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Composite signals", response, [make_rows_table("Signals", rows)])

    run_command(
        state,
        "composite.list",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@composite_app.command("show")
def composite_show(
    ctx: Context,
    signal_id: str,
    quality: str | None = Option(None, "--quality"),
    range_days: int = Option(1, "--range-days"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"signal_id": signal_id, "quality": quality, "range_days": range_days}

    def fetch() -> dict[str, Any]:
        latest = state.client.get_composite_signal_latest(signal_id)
        end = latest.data.latest_updated_at
        start = (end - timedelta(days=range_days)).isoformat() if end else None
        series = state.client.get_composite_signal_series(
            signal_id,
            start=start,
            end=end.isoformat() if end else None,
            quality=quality,
            max_points=max(24, range_days * 96),
        )
        decomposition = state.client.get_composite_decomposition(
            signal_id,
            start=start,
            end=end.isoformat() if end else None,
            quality=quality,
            max_points=25,
        )
        transitions = state.client.get_composite_transitions(
            signal_id,
            start=start,
            end=end.isoformat() if end else None,
            quality=quality,
            max_points=25,
        )
        return {
            "latest": latest,
            "series": series,
            "decomposition": decomposition,
            "transitions": transitions,
        }

    def render(current_state: TerminalState, payload: dict[str, Any]) -> None:
        latest = payload["latest"]
        sections = [
            make_kv_table("Latest summary", latest.data.model_dump(mode="json")),
            make_kv_table("Latest metrics", payload["series"].data.latest_values),
            make_rows_table(
                "Recent series rows",
                [point.model_dump(mode="json") for point in payload["series"].data.points[-12:]],
                limit=_row_limit(current_state),
            ),
            make_rows_table(
                "Latest contributors",
                [row.model_dump(mode="json") for row in payload["decomposition"].data.latest_contributors],
                limit=_row_limit(current_state),
            ),
            make_rows_table(
                "Recent transitions",
                [row.model_dump(mode="json") for row in payload["transitions"].data.records[-12:]],
                limit=_row_limit(current_state),
            ),
        ]
        _render_api_response(current_state, f"Composite {signal_id}", latest, sections)

    run_command(state, "composite.show", filters, fetch, render, watch=watch)


@datasets_app.command("list")
def datasets_list(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_datasets()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Datasets", response, [make_rows_table("Datasets", rows)])

    run_command(
        state,
        "datasets.list",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@datasets_app.command("show")
def datasets_show(ctx: Context, dataset_id: str, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)
    filters = {"dataset_id": dataset_id}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_dataset(dataset_id)

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        data = response.data.model_dump(mode="json")
        sections = [
            make_kv_table(
                "Dataset",
                {key: value for key, value in data.items() if key != "sample_partitions"},
            ),
            make_rows_table("Sample partitions", data["sample_partitions"] or []),
        ]
        _render_api_response(current_state, f"Dataset {dataset_id}", response, sections)

    run_command(state, "datasets.show", filters, fetch, render, watch=watch)


@datasets_app.command("rows")
def datasets_rows(
    ctx: Context,
    dataset_id: str,
    limit: int = Option(20, "--limit"),
    offset: int = Option(0, "--offset"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"dataset_id": dataset_id, "limit": limit, "offset": offset}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_dataset_rows(dataset_id, {"limit": limit, "offset": offset})

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        _render_api_response(
            current_state,
            f"Dataset rows {dataset_id}",
            response,
            [make_rows_table("Rows", response.data.rows)],
        )

    run_command(
        state,
        "datasets.rows",
        filters,
        fetch,
        render,
        ndjson_items=lambda response: response.data.rows,
        watch=watch,
    )


@runs_app.command("list")
def runs_list(
    ctx: Context,
    limit: int = Option(20, "--limit"),
    dataset: str | None = Option(None, "--dataset"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"limit": limit, "dataset": dataset}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_runs({"limit": limit, "dataset": dataset})

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Runs", response, [make_rows_table("Recent runs", rows)])

    run_command(
        state,
        "runs.list",
        filters,
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@runs_app.command("summary")
def runs_summary(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_runs_summary()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        _render_api_response(current_state, "Runs summary", response, [make_kv_table("Summary", response.data.model_dump(mode="json"))])

    run_command(state, "runs.summary", {}, fetch, render, watch=watch)


@engines_app.command("list")
def engines_list(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_engines()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Engines", response, [make_rows_table("Engines", rows)])

    run_command(
        state,
        "engines.list",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("venues")
def execution_venues(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_venues()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        sections = [make_kv_table(item.venue, item.model_dump(mode="json")) for item in response.data]
        _render_api_response(current_state, "Execution venues", response, sections)

    run_command(
        state,
        "execution.venues",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("markets")
def execution_markets(
    ctx: Context,
    venue: str | None = Option(None, "--venue"),
    asset: str | None = Option(None, "--asset"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"venue": venue, "asset": asset}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_markets(venue=venue, asset=asset)

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Execution markets", response, [make_rows_table("Markets", rows)])

    run_command(
        state,
        "execution.markets",
        filters,
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("accounts")
def execution_accounts(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_accounts()

    def render(current_state: TerminalState, response: ApiResponse[Any]) -> None:
        rows = [item.model_dump(mode="json") for item in response.data]
        _render_api_response(current_state, "Execution accounts", response, [make_rows_table("Accounts", rows)])

    run_command(
        state,
        "execution.accounts",
        {},
        fetch,
        render,
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


def _render_execution_records(
    state: TerminalState,
    title: str,
    response: ApiResponse[Any],
) -> None:
    rows = [item.model_dump(mode="json") for item in response.data]
    _render_api_response(state, title, response, [make_rows_table(title, rows)])


@execution_app.command("orders")
def execution_orders(
    ctx: Context,
    venue: str | None = Option(None, "--venue"),
    account: str | None = Option(None, "--account"),
    asset: str | None = Option(None, "--asset"),
    limit: int = Option(20, "--limit"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"venue": venue, "account": account, "asset": asset, "limit": limit}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_orders(filters)

    run_command(
        state,
        "execution.orders",
        filters,
        fetch,
        lambda current_state, response: _render_execution_records(current_state, "Execution orders", response),
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("fills")
def execution_fills(
    ctx: Context,
    venue: str | None = Option(None, "--venue"),
    account: str | None = Option(None, "--account"),
    asset: str | None = Option(None, "--asset"),
    limit: int = Option(20, "--limit"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"venue": venue, "account": account, "asset": asset, "limit": limit}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_fills(filters)

    run_command(
        state,
        "execution.fills",
        filters,
        fetch,
        lambda current_state, response: _render_execution_records(current_state, "Execution fills", response),
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("positions")
def execution_positions(
    ctx: Context,
    venue: str | None = Option(None, "--venue"),
    account: str | None = Option(None, "--account"),
    asset: str | None = Option(None, "--asset"),
    limit: int = Option(20, "--limit"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"venue": venue, "account": account, "asset": asset, "limit": limit}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_positions(filters)

    run_command(
        state,
        "execution.positions",
        filters,
        fetch,
        lambda current_state, response: _render_execution_records(current_state, "Execution positions", response),
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("balances")
def execution_balances(
    ctx: Context,
    venue: str | None = Option(None, "--venue"),
    account: str | None = Option(None, "--account"),
    asset: str | None = Option(None, "--asset"),
    limit: int = Option(20, "--limit"),
    watch: float | None = Option(None, "--watch"),
) -> None:
    state = get_state(ctx)
    filters = {"venue": venue, "account": account, "asset": asset, "limit": limit}

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_balances(filters)

    run_command(
        state,
        "execution.balances",
        filters,
        fetch,
        lambda current_state, response: _render_execution_records(current_state, "Execution balances", response),
        ndjson_items=lambda response: response.data,
        watch=watch,
    )


@execution_app.command("health")
def execution_health(ctx: Context, watch: float | None = Option(None, "--watch")) -> None:
    state = get_state(ctx)

    def fetch() -> ApiResponse[Any]:
        return state.client.get_execution_health()

    run_command(
        state,
        "execution.health",
        {},
        fetch,
        lambda current_state, response: _render_execution_records(current_state, "Execution health", response),
        ndjson_items=lambda response: response.data,
        watch=watch,
    )
