from __future__ import annotations

from datetime import datetime, timezone

import orjson
from typer.testing import CliRunner

from uforia.terminal.app import app
from uforia.terminal.client import UforiaApiClient


def _envelope(data: object) -> dict[str, object]:
    return {
        "data": data,
        "meta": {"generated_at": "2026-03-17T12:00:00+00:00", "query": {}},
        "warnings": [],
    }


def _payloads() -> dict[str, dict[str, object]]:
    return {
        "/api/v1/system/health": _envelope(
            {
                "status": "ok",
                "api_version": "v1",
                "data_root": "db",
                "websocket_enabled": False,
                "dataset_count": 12,
                "signal_count": 21,
                "latest_signal_update": "2026-03-17T12:00:00+00:00",
                "latest_run_update": "2026-03-17T12:00:00+00:00",
                "db_enabled": True,
                "db_status": "ok",
            }
        ),
        "/api/v1/prices": _envelope(
            [
                {
                    "asset": "BTC",
                    "mid_price": 70100.0,
                    "best_bid": 70099.0,
                    "best_ask": 70101.0,
                    "spread_bps": 0.29,
                    "venue": "binance",
                    "ts": "2026-03-17T12:00:00+00:00",
                }
            ]
        ),
        "/api/v1/prices/series": _envelope(
            {
                "asset": "BTC",
                "venue_scope": "binance",
                "timeframe": "15m",
                "points": [
                    {
                        "ts": "2026-03-17T11:45:00+00:00",
                        "asset": "BTC",
                        "venue_scope": "binance",
                        "timeframe": "15m",
                        "open": 70000.0,
                        "high": 70200.0,
                        "low": 69900.0,
                        "close": 70100.0,
                        "volume": 123.0,
                        "trade_count": 10,
                    }
                ],
                "latest_values": {"open": 70000.0, "high": 70200.0, "low": 69900.0, "close": 70100.0},
                "context": {"latest_price_update": "2026-03-17T12:00:00+00:00"},
            }
        ),
        "/api/v1/composite/signals/market_composite/latest": _envelope(
            {
                "id": "market_composite",
                "name": "Market Composite",
                "description": "Top-down market composite",
                "scope": "market",
                "asset": "BTC",
                "latest_updated_at": "2026-03-17T12:00:00+00:00",
                "latest_quality": "ok",
                "latest_values": {"sentiment": 22.1, "directional": -11.2},
            }
        ),
        "/api/v1/signals": _envelope(
            [
                {
                    "id": "btc_vvix",
                    "name": "BTC VVIX",
                    "symbol": "BTC",
                    "family": "vvix",
                    "description": "BTC vol-of-vol",
                    "available_sources": ["dvol"],
                    "latest_source": "dvol",
                    "latest_updated_at": "2026-03-17T12:00:00+00:00",
                    "latest_quality": "ok",
                    "latest_values": {"dvol": 55.0},
                }
            ]
        ),
        "/api/v1/signals/btc_vvix/latest": _envelope(
            {
                "id": "btc_vvix",
                "name": "BTC VVIX",
                "symbol": "BTC",
                "family": "vvix",
                "description": "BTC vol-of-vol",
                "available_sources": ["dvol"],
                "latest_source": "dvol",
                "latest_updated_at": "2026-03-17T12:00:00+00:00",
                "latest_quality": "ok",
                "latest_values": {"dvol": 55.0, "rvvix_14d": 101.0},
            }
        ),
        "/api/v1/signals/btc_vvix/series": _envelope(
            {
                "signal_id": "btc_vvix",
                "symbol": "BTC",
                "source": "dvol",
                "points": [
                    {
                        "ts": "2026-03-17T11:00:00+00:00",
                        "source": "dvol",
                        "underlying": "BTC",
                        "dvol": 55.0,
                        "vol_index_value": 55.0,
                        "rvvix_14d": 101.0,
                        "rvvix_30d": 98.0,
                        "qvvix_30d": 75.0,
                        "gap": False,
                        "quality_flag": "ok",
                    }
                ],
                "latest_values": {"dvol": 55.0, "rvvix_14d": 101.0},
                "context": {"points": 1},
            }
        ),
        "/api/v1/options/dashboards": _envelope(
            [
                {
                    "id": "vol_surface",
                    "name": "Vol Surface",
                    "family": "options",
                    "description": "Surface dashboard",
                    "supported_underlyings": ["BTC", "ETH"],
                    "latest_updated_at": "2026-03-17T12:00:00+00:00",
                    "latest_quality": "ok",
                    "latest_values": {"mean_mark_iv": 0.64},
                }
            ]
        ),
        "/api/v1/options/vol_surface/latest": _envelope(
            {
                "id": "vol_surface",
                "name": "Vol Surface",
                "family": "options",
                "description": "Surface dashboard",
                "supported_underlyings": ["BTC", "ETH"],
                "latest_updated_at": "2026-03-17T12:00:00+00:00",
                "latest_quality": "ok",
                "latest_values": {"mean_mark_iv": 0.64},
            }
        ),
        "/api/v1/options/vol_surface/series": _envelope(
            {
                "dashboard_id": "vol_surface",
                "underlying": "BTC",
                "points": [
                    {
                        "ts": "2026-03-17T11:00:00+00:00",
                        "underlying": "BTC",
                        "pair_id": "btc-1",
                        "tenor": "7d",
                        "tenor_bucket": "1w",
                        "moneyness_bucket": "atm",
                        "expiry_bucket": "near",
                        "strike_bucket": "atm",
                        "values": {"mean_mark_iv": 0.64},
                        "quality_flag": "ok",
                    }
                ],
                "latest_values": {"mean_mark_iv": 0.64},
                "context": {"source": "surface_snapshot"},
            }
        ),
        "/api/v1/perps/dashboards": _envelope(
            [
                {
                    "id": "funding_basis_carry",
                    "name": "Funding-Basis Carry",
                    "family": "perps",
                    "description": "Carry dashboard",
                    "supported_assets": ["BTC", "ETH"],
                    "supported_venue_scopes": ["all", "binance"],
                    "latest_updated_at": "2026-03-17T12:00:00+00:00",
                    "latest_quality": "ok",
                    "latest_values": {"carry_annualized_pct": 2.2},
                }
            ]
        ),
        "/api/v1/perps/funding_basis_carry/latest": _envelope(
            {
                "id": "funding_basis_carry",
                "name": "Funding-Basis Carry",
                "family": "perps",
                "description": "Carry dashboard",
                "supported_assets": ["BTC", "ETH"],
                "supported_venue_scopes": ["all", "binance"],
                "latest_updated_at": "2026-03-17T12:00:00+00:00",
                "latest_quality": "ok",
                "latest_values": {"carry_annualized_pct": 2.2},
            }
        ),
        "/api/v1/perps/funding_basis_carry/series": _envelope(
            {
                "dashboard_id": "funding_basis_carry",
                "asset": "BTC",
                "venue_scope": "all",
                "points": [
                    {
                        "ts": "2026-03-17T11:00:00+00:00",
                        "asset": "BTC",
                        "venue_scope": "all",
                        "values": {"carry_annualized_pct": 2.2},
                        "quality_flag": "ok",
                    }
                ],
                "latest_values": {"carry_annualized_pct": 2.2},
                "context": {"lookback": "7d"},
            }
        ),
        "/api/v1/composite/signals": _envelope(
            [
                {
                    "id": "market_composite",
                    "name": "Market Composite",
                    "description": "Top-down market composite",
                    "scope": "market",
                    "asset": "BTC",
                    "latest_updated_at": "2026-03-17T12:00:00+00:00",
                    "latest_quality": "ok",
                    "latest_values": {"sentiment": 22.1},
                }
            ]
        ),
        "/api/v1/composite/signals/market_composite/series": _envelope(
            {
                "signal_id": "market_composite",
                "scope": "market",
                "asset": "BTC",
                "points": [
                    {
                        "ts": "2026-03-17T11:00:00+00:00",
                        "scope": "market",
                        "asset": "BTC",
                        "values": {"sentiment": 22.1, "directional": -11.2},
                        "quality_flag": "ok",
                    }
                ],
                "latest_values": {"sentiment": 22.1, "directional": -11.2},
                "context": {"components": 3},
            }
        ),
        "/api/v1/composite/decomposition/market_composite": _envelope(
            {
                "signal_id": "market_composite",
                "scope": "market",
                "asset": "BTC",
                "records": [],
                "latest_contributors": [
                    {
                        "ts": "2026-03-17T11:00:00+00:00",
                        "scope": "market",
                        "asset": "BTC",
                        "pillar": "sentiment",
                        "family": "signals",
                        "component": "btc_vvix",
                        "raw_value": 55.0,
                        "normalized_value": 0.2,
                        "component_weight": 0.3,
                        "pillar_score": 0.2,
                        "pillar_weight": 0.4,
                        "signed_contribution": 0.08,
                        "quality_flag": "ok",
                    }
                ],
                "context": {"components": 1},
            }
        ),
        "/api/v1/composite/transitions/market_composite": _envelope(
            {
                "signal_id": "market_composite",
                "scope": "market",
                "asset": "BTC",
                "records": [
                    {
                        "ts": "2026-03-17T11:00:00+00:00",
                        "scope": "market",
                        "asset": "BTC",
                        "index_type": "market",
                        "current_regime": "neutral",
                        "prior_regime": "risk_on",
                        "stay_probability": 0.6,
                        "upshift_probability": 0.2,
                        "downshift_probability": 0.2,
                        "median_regime_duration_hours": 12.0,
                        "quality_flag": "ok",
                    }
                ],
                "context": {"window": "24h"},
            }
        ),
        "/api/v1/datasets": _envelope(
            [
                {
                    "id": "signal_series",
                    "name": "Signal Series",
                    "rows": 42,
                    "latest_ts": "2026-03-17T12:00:00+00:00",
                    "latest_logical_ts": "2026-03-17T12:00:00+00:00",
                    "partition_columns": ["dataset"],
                    "primary_key": ["ts", "id"],
                    "schema_columns": ["ts", "id", "value"],
                }
            ]
        ),
        "/api/v1/datasets/signal_series": _envelope(
            {
                "id": "signal_series",
                "name": "Signal Series",
                "rows": 42,
                "latest_ts": "2026-03-17T12:00:00+00:00",
                "latest_logical_ts": "2026-03-17T12:00:00+00:00",
                "partition_columns": ["dataset"],
                "primary_key": ["ts", "id"],
                "schema_columns": ["ts", "id", "value"],
                "sample_partitions": [{"dataset": "signal_series"}],
            }
        ),
        "/api/v1/datasets/signal_series/rows": _envelope(
            {"dataset_id": "signal_series", "rows": [{"ts": "2026-03-17T12:00:00+00:00", "id": "btc_vvix", "value": 55.0}], "limit": 20}
        ),
        "/api/v1/runs": _envelope(
            [
                {
                    "run_id": "run-1",
                    "logical_ts": "2026-03-17T12:00:00+00:00",
                    "run_ts": "2026-03-17T12:01:00+00:00",
                    "dataset": "signal_series",
                    "venue": "derive",
                    "underlying": "BTC",
                    "status": "success",
                    "rows_written_raw": 10,
                    "rows_written_normalized": 10,
                    "error_message": None,
                    "duration_ms": 1234,
                }
            ]
        ),
        "/api/v1/runs/summary": _envelope(
            {
                "total_runs": 5,
                "success_count": 5,
                "failed_count": 0,
                "gap_count": 0,
                "skipped_count": 0,
                "latest_logical_ts": "2026-03-17T12:00:00+00:00",
                "latest_run_lag_hours": 0.0,
            }
        ),
        "/api/v1/engines": _envelope([{"id": "web", "name": "web", "status": "ok", "detail": "healthy"}]),
        "/api/v1/execution/venues": _envelope(
            [
                {
                    "venue": "hyperliquid",
                    "supported_instrument_types": ["perp"],
                    "supported_order_types": ["limit", "market"],
                    "supports_post_only": True,
                    "supports_reduce_only": True,
                    "supports_amend": True,
                    "supports_mass_cancel": True,
                    "supports_private_ws": True,
                    "supports_quote_routing": False,
                }
            ]
        ),
    }


def test_terminal_help_lists_public_command_groups() -> None:
    runner = CliRunner()
    result = runner.invoke(app, ["--help"])

    assert result.exit_code == 0, result.stdout
    for command in ("system", "overview", "signals", "options", "perps", "composite", "datasets", "runs", "engines", "execution"):
        assert command in result.stdout
    assert "microstructure" not in result.stdout


def test_terminal_system_show_json(monkeypatch) -> None:
    payloads = _payloads()

    def fake_request(self: UforiaApiClient, path: str, params: dict[str, object] | None = None) -> dict[str, object]:
        assert params in (None, {})
        return payloads[path]

    monkeypatch.setattr(UforiaApiClient, "_request", fake_request)
    runner = CliRunner()
    result = runner.invoke(app, ["--json", "system", "show"])

    assert result.exit_code == 0, result.stdout
    payload = orjson.loads(result.stdout)
    assert payload["command"] == "system.show"
    assert payload["data"]["status"] == "ok"
    assert payload["data"]["db_status"] == "ok"


def test_terminal_signals_list_ndjson(monkeypatch) -> None:
    payloads = _payloads()

    def fake_request(self: UforiaApiClient, path: str, params: dict[str, object] | None = None) -> dict[str, object]:
        return payloads[path]

    monkeypatch.setattr(UforiaApiClient, "_request", fake_request)
    runner = CliRunner()
    result = runner.invoke(app, ["--ndjson", "signals", "list"])

    assert result.exit_code == 0, result.stdout
    lines = [orjson.loads(line) for line in result.stdout.splitlines() if line.strip()]
    assert len(lines) == 1
    assert lines[0]["command"] == "signals.list"
    assert lines[0]["item"]["id"] == "btc_vvix"


def test_terminal_overview_show_renders_summary(monkeypatch) -> None:
    payloads = _payloads()

    def fake_request(self: UforiaApiClient, path: str, params: dict[str, object] | None = None) -> dict[str, object]:
        return payloads[path]

    monkeypatch.setattr(UforiaApiClient, "_request", fake_request)
    runner = CliRunner()
    result = runner.invoke(app, ["overview", "show", "--asset", "BTC"])

    assert result.exit_code == 0, result.stdout
    assert "Overview" in result.stdout
    assert "Current prices" in result.stdout
    assert "Market composite" in result.stdout


def test_terminal_options_show_json(monkeypatch) -> None:
    payloads = _payloads()

    def fake_request(self: UforiaApiClient, path: str, params: dict[str, object] | None = None) -> dict[str, object]:
        return payloads[path]

    monkeypatch.setattr(UforiaApiClient, "_request", fake_request)
    runner = CliRunner()
    result = runner.invoke(app, ["--json", "options", "show", "vol_surface", "--underlying", "BTC"])

    assert result.exit_code == 0, result.stdout
    payload = orjson.loads(result.stdout)
    assert payload["command"] == "options.show"
    assert payload["data"]["latest"]["data"]["id"] == "vol_surface"


def test_terminal_execution_venues_rich(monkeypatch) -> None:
    payloads = _payloads()

    def fake_request(self: UforiaApiClient, path: str, params: dict[str, object] | None = None) -> dict[str, object]:
        return payloads[path]

    monkeypatch.setattr(UforiaApiClient, "_request", fake_request)
    runner = CliRunner()
    result = runner.invoke(app, ["execution", "venues"], terminal_width=200)

    assert result.exit_code == 0, result.stdout
    assert "Execution venues" in result.stdout
    assert "hyperliquid" in result.stdout
