"""
NexQuant Backtesting Test Fixtures
Wiederverwendbare Test-Daten und Fixtures für alle Backtesting-Tests
"""
import pytest
import numpy as np
import pandas as pd
import tempfile
import os
from pathlib import Path
from datetime import datetime, timedelta

# Importiere die zu testenden Klassen
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from rdagent.components.backtesting.backtest_engine import BacktestMetrics, FactorBacktester
from rdagent.components.backtesting.results_db import ResultsDatabase
from rdagent.components.backtesting.risk_management import (
    CorrelationAnalyzer, PortfolioOptimizer, AdvancedRiskManager
)


# =============================================================================
# FIXTURES FÜR BACKTEST METRICS
# =============================================================================

@pytest.fixture
def sample_factor_data():
    """Normale Faktor-Daten für Standard-Tests"""
    np.random.seed(42)
    n = 252
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    factor_values = pd.Series(np.random.randn(n), index=dates, name='factor')
    forward_returns = pd.Series(np.random.randn(n) * 0.01 + 0.0001, index=dates, name='fwd_ret')
    return factor_values, forward_returns


@pytest.fixture
def sample_returns_data():
    """Returns-Daten für Sharpe und Drawdown Tests"""
    np.random.seed(42)
    n = 252
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    returns = pd.Series(np.random.randn(n) * 0.01 + 0.0005, index=dates)
    equity = (1 + returns).cumprod()
    return returns, equity


@pytest.fixture
def backtest_metrics():
    """BacktestMetrics Instanz mit Standard-Parametern"""
    return BacktestMetrics(risk_free_rate=0.02)


# =============================================================================
# FIXTURES FÜR EDGE CASES
# =============================================================================

@pytest.fixture
def empty_data():
    """Leere Daten für Edge-Case Tests"""
    return pd.Series([], dtype=float), pd.Series([], dtype=float)


@pytest.fixture
def nan_data():
    """Daten mit vielen NaN-Werten"""
    np.random.seed(42)
    n = 100
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    factor = pd.Series([np.nan] * 50 + list(np.random.randn(50)), index=dates)
    fwd_ret = pd.Series(list(np.random.randn(50)) + [np.nan] * 50, index=dates)
    return factor, fwd_ret


@pytest.fixture
def insufficient_data():
    """Zu wenig Daten (< 10 Punkte)"""
    np.random.seed(42)
    n = 5
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    factor = pd.Series(np.random.randn(n), index=dates)
    fwd_ret = pd.Series(np.random.randn(n), index=dates)
    return factor, fwd_ret


@pytest.fixture
def extreme_values_data():
    """Daten mit Extremwerten"""
    np.random.seed(42)
    n = 252
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    factor = pd.Series(np.random.randn(n), index=dates)
    factor.iloc[50] = 1000  # Extremwert
    factor.iloc[100] = -1000  # Extremwert negativ
    fwd_ret = pd.Series(np.random.randn(n) * 0.01, index=dates)
    return factor, fwd_ret


@pytest.fixture
def constant_data():
    """Konstante Daten (Std = 0)"""
    n = 252
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    factor = pd.Series([1.0] * n, index=dates)
    fwd_ret = pd.Series([0.001] * n, index=dates)
    return factor, fwd_ret


# =============================================================================
# FIXTURES FÜR DATABASE TESTS
# =============================================================================

@pytest.fixture
def temp_db_path():
    """Temporäre Datenbank für Tests"""
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = os.path.join(tmpdir, 'test_backtest.db')
        yield db_path


@pytest.fixture
def results_database(temp_db_path):
    """ResultsDatabase Instanz mit temporärer DB"""
    db = ResultsDatabase(db_path=temp_db_path)
    yield db
    db.close()


@pytest.fixture
def populated_database(results_database):
    """Datenbank mit Test-Daten befüllt"""
    db = results_database
    
    # Faktoren hinzufügen
    db.add_factor("Momentum", "price_based")
    db.add_factor("MeanReversion", "price_based")
    db.add_factor("Volatility", "risk_based")
    db.add_factor("Volume", "volume_based")
    db.add_factor("ML_Factor", "ml_based")
    
    # Backtest-Ergebnisse hinzufügen
    db.add_backtest("Momentum", {
        'ic': 0.08, 'sharpe_ratio': 1.5, 'annualized_return': 0.12,
        'max_drawdown': -0.08, 'win_rate': 0.55
    })
    db.add_backtest("MeanReversion", {
        'ic': 0.05, 'sharpe_ratio': 1.2, 'annualized_return': 0.08,
        'max_drawdown': -0.05, 'win_rate': 0.52
    })
    db.add_backtest("Volatility", {
        'ic': -0.03, 'sharpe_ratio': 0.8, 'annualized_return': 0.04,
        'max_drawdown': -0.03, 'win_rate': 0.48
    })
    db.add_backtest("ML_Factor", {
        'ic': 0.12, 'sharpe_ratio': 2.1, 'annualized_return': 0.18,
        'max_drawdown': -0.10, 'win_rate': 0.60
    })
    
    # Loop-Ergebnisse hinzufügen
    db.add_loop(1, 4, 6, 0.08, "completed")
    db.add_loop(2, 5, 5, 0.10, "completed")
    db.add_loop(3, 3, 7, 0.05, "completed")
    
    return db


# =============================================================================
# FIXTURES FÜR RISK MANAGEMENT TESTS
# =============================================================================

@pytest.fixture
def sample_returns_matrix():
    """Returns-Matrix für Korrelations-Analyse"""
    np.random.seed(42)
    n = 252
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    columns = ['Mom', 'MeanRev', 'Vol', 'Volu', 'ML']
    
    # Erzeuge korrelierte Returns
    data = np.random.randn(n, 5)
    data[:, 0] = data[:, 1] * 0.3 + data[:, 0] * 0.7  # Mom korreliert mit MeanRev
    data[:, 3] = data[:, 2] * 0.5 + data[:, 3] * 0.5  # Volu korreliert mit Vol
    
    return pd.DataFrame(data, columns=columns, index=dates)


@pytest.fixture
def correlation_analyzer():
    """CorrelationAnalyzer Instanz"""
    return CorrelationAnalyzer(lookback=60)


@pytest.fixture
def portfolio_optimizer():
    """PortfolioOptimizer Instanz"""
    return PortfolioOptimizer()


@pytest.fixture
def sample_expected_returns():
    """Erwartete Returns für Portfolio-Optimierung"""
    return pd.Series({
        'Mom': 0.10, 'MeanRev': 0.08, 'Vol': 0.06,
        'Volu': 0.07, 'ML': 0.12
    })


@pytest.fixture
def sample_covariance_matrix(sample_returns_matrix):
    """Kovarianz-Matrix aus Returns"""
    return sample_returns_matrix.cov() * 252


@pytest.fixture
def risk_manager():
    """AdvancedRiskManager Instanz"""
    return AdvancedRiskManager(max_pos=0.2, max_lev=5.0, max_dd=0.20)


@pytest.fixture
def sample_weights():
    """Test-Gewichtungen"""
    return np.array([0.25, 0.20, 0.15, 0.20, 0.20])


# =============================================================================
# FIXTURES FÜR BACKTESTER
# =============================================================================

@pytest.fixture
def factor_backtester():
    """FactorBacktester Instanz mit temporärem Output-Verzeichnis"""
    with tempfile.TemporaryDirectory() as tmpdir:
        backtester = FactorBacktester()
        backtester.results_path = Path(tmpdir)
        yield backtester


# =============================================================================
# ZUSÄTZLICHE HILFS-FIXTURES
# =============================================================================

@pytest.fixture
def realistic_market_data():
    """Realistischere Markt-Daten mit typischen Eigenschaften"""
    np.random.seed(42)
    n = 504  # 2 Jahre
    dates = pd.date_range(start='2023-01-01', periods=n, freq='B')
    
    # Faktor mit etwas Autokorrelation (wie echte Faktoren)
    factor = pd.Series(index=dates)
    factor.iloc[0] = 0
    for i in range(1, n):
        factor.iloc[i] = 0.3 * factor.iloc[i-1] + np.random.randn() * 0.7
    
    # Forward Returns mit leichtem positiven Drift
    fwd_ret = pd.Series(np.random.randn(n) * 0.015 + 0.0002, index=dates)
    
    # Füge einige Ausreißer hinzu (wie bei echten Marktdaten)
    fwd_ret.iloc[50] = -0.05  # Crash-Tag
    fwd_ret.iloc[150] = 0.04  # Rally-Tag
    
    return factor, fwd_ret


@pytest.fixture
def zero_variance_returns():
    """Returns mit Varianz = 0 (für Edge-Case Tests)"""
    n = 100
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    returns = pd.Series([0.001] * n, index=dates)
    equity = (1 + returns).cumprod()
    return returns, equity


@pytest.fixture
def negative_equity_data():
    """Equity-Daten mit Drawdowns"""
    np.random.seed(42)
    n = 252
    dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
    
    # Erzeuge Equity mit signifikantem Drawdown
    returns = pd.Series(np.random.randn(n) * 0.02, index=dates)
    returns.iloc[50:80] = -0.03  # Drawdown-Periode
    equity = (1 + returns).cumprod()
    
    return returns, equity
