"""Tests for sidecar allocation reduction (ALLOC-01, ALLOC-02, ALLOC-03).

Phase 24: Hot-path allocation reduction (#313). Tests verify:
- ALLOC-01: Single OpenDeviationBarCache instance in _create_engine
- ALLOC-02: _write_bars_batch_to_clickhouse accepts pl.DataFrame directly
- ALLOC-03: ouroboros_mode threaded from symbol_modes, no per-flush registry lookup
"""
from __future__ import annotations

from pathlib import Path

import pytest

# Read source files directly from the repo (not installed package) so
# worktree changes are visible without maturin develop.
_SIDECAR_DIR = Path(__file__).resolve().parent.parent / "python" / "opendeviationbar" / "sidecar"
_INIT_SRC = (_SIDECAR_DIR / "__init__.py").read_text()
_WRITE_SRC = (_SIDECAR_DIR / "clickhouse_write.py").read_text()
_RECOVERY_SRC = (_SIDECAR_DIR / "recovery.py").read_text()


# =============================================================================
# ALLOC-01: Single cache instance in _create_engine
# =============================================================================


class TestSingleCacheInstance:
    """ALLOC-01: _create_engine returns 5-tuple with ch_cache as 5th element."""

    def test_create_engine_returns_5_tuple(self):
        """_create_engine return statement includes ch_cache as 5th element."""
        assert "return engine, loaded, min_tid_guard, symbol_modes, ch_cache" in _INIT_SRC

    def test_no_context_manager_cache_in_create_engine(self):
        """No 'with OpenDeviationBarCache() as cache:' in __init__.py."""
        assert "with OpenDeviationBarCache() as cache:" not in _INIT_SRC

    def test_single_cache_instantiation_in_create_engine(self):
        """Exactly ONE 'ch_cache = OpenDeviationBarCache()' in __init__.py."""
        count = _INIT_SRC.count("ch_cache = OpenDeviationBarCache()")
        assert count == 1, f"Expected 1 cache creation, found {count}"

    def test_recovery_destructures_5_tuple(self):
        """recovery.py destructures 5-tuple from _create_engine."""
        assert "new_ch_cache" in _RECOVERY_SRC

    def test_recovery_returns_5_tuple_on_success(self):
        """recovery.py returns new_ch_cache on success."""
        assert "new_symbol_modes, new_ch_cache" in _RECOVERY_SRC

    def test_recovery_returns_none_cache_on_failure(self):
        """recovery.py returns None for ch_cache on failure."""
        assert ", None" in _RECOVERY_SRC  # return ..., None in failure path


# =============================================================================
# ALLOC-02: Dual-type write function
# =============================================================================


class TestDualTypeWrite:
    """ALLOC-02: _write_bars_batch_to_clickhouse accepts pl.DataFrame | list[dict]."""

    def test_write_accepts_dataframe(self):
        """_write_bars_batch_to_clickhouse has isinstance check for list."""
        assert "isinstance(bars, list)" in _WRITE_SRC

    def test_write_has_ouroboros_mode_param(self):
        """_write_bars_batch_to_clickhouse has ouroboros_mode parameter."""
        assert "ouroboros_mode: str" in _WRITE_SRC

    def test_write_no_get_symbol_entries_call(self):
        """_write_bars_batch_to_clickhouse does NOT import/call get_symbol_entries."""
        # Extract the function body after the docstring
        fn_start = _WRITE_SRC.index("def _write_bars_batch_to_clickhouse(")
        fn_end = _WRITE_SRC.index("\ndef _write_batch_with_retry(")
        fn_body = _WRITE_SRC[fn_start:fn_end]
        # Strip out triple-quoted docstrings to check only executable code
        import re
        code_only = re.sub(r'""".*?"""', '', fn_body, flags=re.DOTALL)
        assert "get_symbol_entries" not in code_only

    def test_retry_has_ouroboros_mode_param(self):
        """_write_batch_with_retry has ouroboros_mode parameter."""
        fn_start = _WRITE_SRC.index("def _write_batch_with_retry(")
        fn_body = _WRITE_SRC[fn_start:]
        assert "ouroboros_mode: str" in fn_body

    def test_retry_passes_ouroboros_mode_through(self):
        """_write_batch_with_retry passes ouroboros_mode to _write_bars_batch_to_clickhouse."""
        fn_start = _WRITE_SRC.index("def _write_batch_with_retry(")
        fn_body = _WRITE_SRC[fn_start:]
        assert "ouroboros_mode=ouroboros_mode" in fn_body

    def test_write_parameter_named_bars(self):
        """Parameter renamed from bar_dicts to bars."""
        assert "bars: pl.DataFrame | list[dict]" in _WRITE_SRC


# =============================================================================
# ALLOC-03: ouroboros_mode threaded from symbol_modes
# =============================================================================


class TestOuroborosModeThreading:
    """ALLOC-03: Per-flush ouroboros_mode from pre-computed symbol_modes."""

    def test_flush_all_batches_uses_symbol_modes(self):
        """_flush_all_batches resolves mode from symbol_modes dict."""
        assert "symbol_modes.get(symbol" in _INIT_SRC

    def test_flush_passes_ouroboros_mode_to_write(self):
        """_flush_all_batches passes ouroboros_mode to _write_batch_with_retry."""
        # Find _flush_all_batches function
        fn_start = _INIT_SRC.index("def _flush_all_batches()")
        fn_end = _INIT_SRC.index("\n    def _drain_gap_events()")
        fn_body = _INIT_SRC[fn_start:fn_end]
        assert "ouroboros_mode=mode" in fn_body

    def test_run_sidecar_destructures_5_tuple(self):
        """run_sidecar destructures 5-tuple from _create_engine."""
        assert "symbol_modes, ch_cache = _create_engine(" in _INIT_SRC
