# source: https://raw.githubusercontent.com/4tie/fortiesr/d5cc40760e3cbaff381058a0ea8733d2be469578/backend/tests/auto_quant/conftest_integration.py
"""Integration test fixtures for AutoQuant pipeline tests.

This file provides fixtures for integration-level testing that combines
real pipeline orchestration with mocked subprocess calls.
"""

from __future__ import annotations

import asyncio
import json
from pathlib import Path
from typing import Any, AsyncGenerator
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from backend.api.routers.auto_quant import router
from backend.services.auto_quant.pipeline import (
    _cancel_flags,
    _queues,
    _states,
)

from .fixtures.mock_subprocess import (
    MockAsyncProcess,
    create_backtest_output,
    create_backtest_result,
)
from .fixtures.websocket import validate_websocket_message


# ═══════════════════════════════════════════════════════════════════════════════
# Mocking fixtures
# ═══════════════════════════════════════════════════════════════════════════════


@pytest.fixture
def mock_freqtrade_subprocess(monkeypatch):
    """
    Patch asyncio.create_subprocess_exec to return mock processes
    with realistic backtest output instead of actually running freqtrade.
    """

    async def mock_exec(
        *args,
        stdout=None,
        stderr=None,
        env=None,
        **kwargs,
    ) -> MockAsyncProcess:
        """Mock subprocess that returns cached backtest results."""

        # Determine what kind of output based on command
        if "backtest" in str(args):
            # Standard backtest output
            output = create_backtest_result(
                profit=0.05, max_dd=0.10, trades=42, win_rate=0.55
            )
        elif "hyperopt" in str(args):
            # Hyperopt output with epochs
            output = create_backtest_output(include_hyperopt=True, epochs=10)
        else:
            # Default backtest
            output = create_backtest_result(profit=0.02)

        return MockAsyncProcess(stdout_data=output, return_code=0)

    monkeypatch.setattr("asyncio.create_subprocess_exec", mock_exec)
    return mock_exec


@pytest.fixture
def app_with_service(tmp_path, mock_freqtrade_subprocess):
    """
    Build a FastAPI app with auto_quant router and mocked services.

    This is the primary fixture for integration tests. It provides:
    - A real FastAPI app with the auto_quant router
    - Mocked freqtrade subprocess calls
    - Temporary directory for test files
    - Settings and services mocks

    Yields:
        tuple[TestClient, Path, MagicMock]: (test_client, temp_dir, settings)
    """
    app = FastAPI()
    app.include_router(router)

    # Create real temporary directories
    user_data_dir = tmp_path / "user_data"
    strategies_dir = user_data_dir / "strategies"
    strategies_dir.mkdir(parents=True, exist_ok=True)
    user_data_dir.mkdir(parents=True, exist_ok=True)

    # Create a minimal test strategy
    test_strategy = (
        strategies_dir / "Github_4tie_fortiesr__conftest_integration__20260709_213639.py"
    )
    test_strategy.write_text(
        """
from freqtrade.strategy import IStrategy

class Github_4tie_fortiesr__conftest_integration__20260709_213639(IStrategy):
    stoploss = -0.10
    timeframe = '5m'

    def populate_indicators(self, dataframe, metadata):
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        dataframe.loc[:, 'enter_long'] = 0
        return dataframe

    def populate_exit_trend(self, dataframe, metadata):
        dataframe.loc[:, 'exit_long'] = 0
        return dataframe
""",
        encoding="utf-8",
    )

    # Create mock settings
    settings = MagicMock()
    settings.default_config_file_path = str(tmp_path / "config.json")
    settings.strategies_directory_path = str(strategies_dir)
    settings.freqtrade_executable_path = "freqtrade"
    settings.user_data_directory_path = str(user_data_dir)

    # Create default config file
    config = {
        "stake_currency": "USDT",
        "dry_run": True,
        "exchange": {"name": "binance"},
    }
    (tmp_path / "config.json").write_text(json.dumps(config), encoding="utf-8")

    # Create mock services
    services = MagicMock()
    services.settings_store.load.return_value = settings
    app.state.services = services

    with TestClient(app, raise_server_exceptions=False) as client:
        yield client, tmp_path, settings


# ═══════════════════════════════════════════════════════════════════════════════
# Parameterized fixtures
# ═══════════════════════════════════════════════════════════════════════════════


@pytest.fixture(params=[5, 10, 50])
def hyperopt_epochs(request):
    """Parametrized fixture providing different epoch counts for hyperopt tests."""
    return request.param


@pytest.fixture(params=[True, False])
def wfo_enabled(request):
    """Parametrized fixture for WFO enabled/disabled."""
    return request.param


@pytest.fixture(params=[True, False])
def ensemble_enabled(request):
    """Parametrized fixture for ensemble enabled/disabled."""
    return request.param


@pytest.fixture(
    params=[
        ("5m", 10, False, False),
        ("1h", 50, True, False),
        ("4h", 100, False, True),
    ]
)
def pipeline_config(request):
    """
    Parametrized fixture providing different pipeline configurations.

    Yields:
        tuple[str, int, bool, bool]: (timeframe, hyperopt_epochs, wfo_enabled, ensemble_enabled)
    """
    return request.param


# ═══════════════════════════════════════════════════════════════════════════════
# WebSocket testing fixtures
# ═══════════════════════════════════════════════════════════════════════════════


@pytest.fixture
async def websocket_messages(
    app_with_service,
) -> AsyncGenerator[list[dict[str, Any]], None]:
    """
    Fixture that collects WebSocket messages during a pipeline run.

    This fixture manages connection to the WebSocket endpoint and validates
    all received messages against the expected schema.
    """
    messages: list[dict[str, Any]] = []

    async def collect_messages(
        client: TestClient, run_id: str, timeout: int = 30
    ) -> list[dict[str, Any]]:
        """Collect all WebSocket messages from a pipeline run."""
        messages_local = []
        end_time = asyncio.get_event_loop().time() + timeout

        # Note: WebSocket testing in FastAPI TestClient requires special handling
        # In actual tests, use a real WebSocket client or test the handler directly
        return messages_local

    yield messages


# ═══════════════════════════════════════════════════════════════════════════════
# Utility fixtures
# ═══════════════════════════════════════════════════════════════════════════════


@pytest.fixture
def pipeline_state_snapshot(tmp_path):
    """Create a sample PipelineState snapshot for testing."""
    from backend.services.auto_quant.pipeline_modules.state import PipelineState

    state = PipelineState(
        run_id="test-run-123",
        config={
            "strategy": "Github_4tie_fortiesr__conftest_integration__20260709_213639",
            "timeframe": "5m",
            "in_sample_range": "20230101-20240101",
            "out_sample_range": "20240101-20240601",
            "hyperopt_epochs": 10,
            "wfo_enabled": False,
            "ensemble_enabled": False,
        },
        output_dir=tmp_path / "output",
    )
    return state


@pytest.fixture(autouse=True)
def cleanup_integration_state():
    """Clean up global state after each integration test."""
    yield
    _states.clear()
    _queues.clear()
    _cancel_flags.clear()
