from __future__ import annotations

import math
from collections import Counter
from dataclasses import dataclass
from typing import Any, Type

import akquant as aq
import pandas as pd
from akquant import Bar, Strategy


def build_demo_data(n: int = 320) -> pd.DataFrame:
    """Build deterministic synthetic bars for stream-monitor demonstrations."""
    ts = pd.date_range("2023-01-01", periods=n, freq="D")
    base = 100.0
    close = [
        base + 0.05 * i + 3.0 * math.sin(i / 8.0) + 1.2 * math.sin(i / 3.0)
        for i in range(n)
    ]
    df = pd.DataFrame(
        {
            "date": ts,
            "open": [c * 0.998 for c in close],
            "high": [c * 1.004 for c in close],
            "low": [c * 0.996 for c in close],
            "close": close,
            "volume": [1000.0 + float(i % 50) * 20.0 for i in range(n)],
            "symbol": "STREAM_MONITOR",
        }
    )
    return df


class _MovingAverageStrategy(Strategy):
    def __init__(self, short_window: int, long_window: int) -> None:
        super().__init__()
        self.short_window = short_window
        self.long_window = long_window
        self.warmup_period = long_window

    def on_bar(self, bar: Bar) -> None:
        closes = self.get_history(
            count=self.long_window, symbol=bar.symbol, field="close"
        )
        if len(closes) < self.long_window:
            return
        short_ma = float(sum(closes[-self.short_window :])) / float(self.short_window)
        long_ma = float(sum(closes[-self.long_window :])) / float(self.long_window)
        pos = self.get_position(bar.symbol)
        if short_ma > long_ma and pos == 0:
            self.order_target_percent(symbol=bar.symbol, target_percent=0.9)
        elif short_ma < long_ma and pos > 0:
            self.close_position(symbol=bar.symbol)


def strategy_factory(short_window: int, long_window: int) -> Type[Strategy]:
    """Create a strategy class bound to one parameter configuration."""

    class StrategyImpl(_MovingAverageStrategy):
        def __init__(self) -> None:
            super().__init__(short_window=short_window, long_window=long_window)

    return StrategyImpl


@dataclass
class StreamMonitor:
    """Mutable stream state captured by callback."""

    event_counts: Counter[str]
    seq_last: int = 0
    progress_seen: int = 0
    finished_payload: dict[str, Any] | None = None


def run_one_config(short_window: int, long_window: int) -> Any:
    """Run one parameter configuration with realtime stream monitoring output."""
    monitor = StreamMonitor(event_counts=Counter())

    def on_event(event: aq.BacktestStreamEvent) -> None:
        event_type = str(event.get("event_type", "unknown"))
        monitor.event_counts[event_type] += 1
        raw_seq = event.get("seq", monitor.seq_last)
        try:
            monitor.seq_last = int(raw_seq)
        except Exception:
            pass
        if event_type == "started":
            print(
                f"[started] run_id={event.get('run_id')} "
                f"cfg={short_window}/{long_window}"
            )
        elif event_type == "progress":
            monitor.progress_seen += 1
            if monitor.progress_seen % 6 == 0:
                print(
                    f"[progress] cfg={short_window}/{long_window} "
                    f"seq={monitor.seq_last} progress_events={monitor.progress_seen}"
                )
        elif (
            event_type in {"order", "trade"}
            and monitor.event_counts[event_type] % 8 == 0
        ):
            print(
                f"[monitor] cfg={short_window}/{long_window} "
                f"{event_type}_events={monitor.event_counts[event_type]} "
                f"seq={monitor.seq_last}"
            )
        elif event_type == "finished":
            payload = event.get("payload", {})
            if isinstance(payload, dict):
                monitor.finished_payload = payload
            else:
                monitor.finished_payload = {"payload": payload}
            print(
                f"[finished] cfg={short_window}/{long_window} "
                f"status={monitor.finished_payload.get('status')}"
            )

    result = aq.run_backtest(
        data=build_demo_data(),
        strategy=strategy_factory(short_window, long_window),
        symbol="STREAM_MONITOR",
        show_progress=False,
        initial_cash=500000.0,
        commission_rate=0.0,
        stamp_tax_rate=0.0,
        transfer_fee_rate=0.0,
        min_commission=0.0,
        execution_mode=aq.ExecutionMode.CurrentClose,
        lot_size=1,
        on_event=on_event,
        stream_progress_interval=5,
        stream_equity_interval=10,
        stream_batch_size=16,
        stream_max_buffer=128,
        stream_error_mode="continue",
    )

    ordered = sorted(monitor.event_counts.items(), key=lambda item: item[0])
    print(f"stream_event_counts_cfg_{short_window}_{long_window}={ordered}")
    print(
        f"stream_finished_callback_error_count_cfg_{short_window}_{long_window}="
        f"{(monitor.finished_payload or {}).get('callback_error_count', '0')}"
    )
    print(
        f"stream_result_total_return_cfg_{short_window}_{long_window}="
        f"{result.metrics.total_return:.6f}"
    )
    return result


def main() -> None:
    """Run multiple configs and print end marker for smoke checks."""
    cfgs = [(5, 20), (8, 30)]
    for short_window, long_window in cfgs:
        run_one_config(short_window=short_window, long_window=long_window)
    print("done_streaming_monitoring_console")


if __name__ == "__main__":
    main()
