"""
Phase C2a integration tests: Orchestrator + Reconciler wiring.

3 tests against in-memory orchestrator with a stub reconciler.

  1. orchestrator runs watchdog every N iterations (cadence honored)
  2. orchestrator without reconciler doesn't crash, doesn't call watchdog
  3. orchestrator continues running when watchdog raises (failure isolation)

Run:
    cd ~/apex_v16
    python tests/test_orchestrator_phase_c2a.py
"""

from __future__ import annotations

import asyncio
import sys
from pathlib import Path

import pandas as pd

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.config import RuntimeConfig, RunMode, AccountKind
from orchestrator import Orchestrator
from persistence.state_store import SessionState


def _ok(label: str) -> None:
    print(f"  ok  {label}")


# ============================================================
# FAKES
# ============================================================

class FakeAI:
    async def ask(self, prompt, temperature=0.2, max_tokens=None):
        from brain.ai_client import AIResponse
        return AIResponse(text=None, error_kind="unknown")


class JsonlSink:
    def __init__(self):
        self.events = []
    def write(self, event, **fields):
        self.events.append({"event": event, **fields})


class FakeLogger:
    def __init__(self):
        import logging
        self.brain_log = JsonlSink()
        self.session_log = JsonlSink()
        self.error_log = JsonlSink()
        self.system = logging.getLogger("test.c2a")
    def log_session_event(self, event, **fields):
        self.session_log.write(event, **fields)
    def log_error(self, where, error, **extra):
        self.error_log.write("error", where=where, error=error, **extra)
    def log_trade_opened(self, **fields):
        self.brain_log.write("trade_opened", **fields)
    def log_trade_closed(self, **fields):
        self.brain_log.write("trade_closed", **fields)


class FakeProvider:
    async def get_bars(self, symbol, timeframe, n):
        return pd.DataFrame(columns=["open", "high", "low", "close", "volume"])


class _MemoryStore:
    def save(self, state): pass


class FakeReconciler:
    """Counts watchdog/startup calls. Optionally raises in watchdog."""
    def __init__(self, raise_in_watchdog: bool = False):
        self.startup_calls = 0
        self.watchdog_calls = 0
        self._raise = raise_in_watchdog

    async def reconcile_startup(self):
        self.startup_calls += 1
        return None

    async def watchdog_naked_positions(self):
        self.watchdog_calls += 1
        if self._raise:
            raise RuntimeError("watchdog boom")
        return None


# ============================================================
# FIXTURES
# ============================================================

from datetime import datetime as _dt, timezone as _tz
FIXED_DAY_UTC: _dt = _dt(2026, 4, 28, 14, 0, 0, tzinfo=_tz.utc)


def make_orch(*, reconciler=None, max_iterations=4, every=2) -> Orchestrator:
    cfg = RuntimeConfig(mode=RunMode.PAPER, account=AccountKind.INELIGIBLE)
    cfg.asset_filter = ["MES"]
    cfg.scan_loop_phase_offset_seconds = 0.0
    cfg.manage_loop_interval_seconds = 0
    cfg.maintenance_loop_interval_seconds = 0
    cfg.reconcile_interval_iterations = every
    return Orchestrator(
        config=cfg, ai_client=FakeAI(),
        market_data_provider=FakeProvider(),
        state=SessionState(), store=_MemoryStore(), logger=FakeLogger(),
        brain_dispatch={}, broker=None,
        reconciler=reconciler,
        now_utc_provider=lambda: FIXED_DAY_UTC,
        max_iterations=max_iterations,
    )


# ============================================================
# TESTS
# ============================================================

def test_watchdog_cadence_every_n_iterations():
    """V16 3-loop: watchdog fires every N maintenance_loop iters."""
    rec = FakeReconciler()
    # max_iterations=4 + interval=0 → maintenance runs exactly 4 iters
    # → watchdog (every=2) fires on iters 2 and 4 = 2 calls.
    orch = make_orch(reconciler=rec, max_iterations=4, every=2)
    asyncio.run(orch.run())
    assert rec.watchdog_calls == 2, \
        f"expected 2 watchdog calls (iters 2 and 4), got {rec.watchdog_calls}"
    _ok("watchdog: V16 cadence — every N maintenance ticks")


def test_no_reconciler_doesnt_call_watchdog_or_crash():
    """reconciler=None -> orchestrator runs cleanly across all 3 loops."""
    orch = make_orch(reconciler=None, max_iterations=3, every=2)
    rc = asyncio.run(orch.run())
    assert rc == 0, f"orchestrator must return 0, got {rc}"
    _ok("no reconciler: orchestrator runs cleanly, no crash")


def test_watchdog_failure_doesnt_break_loop():
    """watchdog raising -> warning logged, orchestrator continues."""
    rec = FakeReconciler(raise_in_watchdog=True)
    orch = make_orch(reconciler=rec, max_iterations=4, every=2)
    rc = asyncio.run(orch.run())
    assert rc == 0, "orchestrator must keep running after watchdog raises"
    assert rec.watchdog_calls == 2, \
        f"watchdog called both ticks despite raising, got {rec.watchdog_calls}"
    _ok("watchdog: exception isolated, loop continues to completion")


# ============================================================
# RUN
# ============================================================

if __name__ == "__main__":
    print("test_orchestrator_phase_c2a.py")
    test_watchdog_cadence_every_n_iterations()
    test_no_reconciler_doesnt_call_watchdog_or_crash()
    test_watchdog_failure_doesnt_break_loop()
    print("ALL 3 TESTS PASSED")
