"""Open-source test suite V2 — fixed assertions."""

from __future__ import annotations

import json
import sys
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

import numpy as np
import pandas as pd
import pytest

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


class TestNexQuantCLI:
    def test_cli_commands_available(self):
        import subprocess
        r = subprocess.run([sys.executable, "nexquant.py", "--help"], capture_output=True, text=True, timeout=10)
        assert r.returncode == 0
        for cmd in ["evaluate", "top", "best", "portfolio", "build-strategies", "generate-strategies", "health"]:
            assert cmd in r.stdout.lower(), f"Missing command: {cmd}"


class TestBacktestEdgeCases:
    def test_all_zero_signal(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_signal
        n = 500
        close = pd.Series(1.10 + np.arange(n) * 0.0001, index=pd.date_range("2024-01-01", periods=n, freq="1min"))
        result = backtest_signal(close, pd.Series(0.0, index=close.index))
        assert result["n_trades"] == 0

    def test_sortino_present(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_signal
        n = 2000
        dates = pd.date_range("2024-01-01", periods=n, freq="1min")
        close = pd.Series(1.10 * np.exp(np.cumsum(np.random.default_rng(42).normal(0, 0.0002, n))), index=dates)
        signal = pd.Series(np.where(np.random.default_rng(43).normal(0, 1, n) > 0, 1.0, -1.0), index=dates)
        result = backtest_signal(close, signal)
        assert "sortino" in result
        assert result["sortino"] is not None

    def test_calmar_present(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_signal
        n = 2000
        dates = pd.date_range("2024-01-01", periods=n, freq="1min")
        close = pd.Series(1.10 * np.exp(np.cumsum(np.random.default_rng(42).normal(0, 0.0002, n))), index=dates)
        signal = pd.Series(np.where(np.random.default_rng(44).normal(0, 1, n) > 0, 1.0, -1.0), index=dates)
        result = backtest_signal(close, signal)
        assert "calmar" in result

    def test_all_required_keys_present(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_signal
        n = 2000
        dates = pd.date_range("2024-01-01", periods=n, freq="1min")
        close = pd.Series(1.10 * np.exp(np.cumsum(np.random.default_rng(42).normal(0, 0.0002, n))), index=dates)
        signal = pd.Series(np.where(np.random.default_rng(43).normal(0, 1, n) > 0, 1.0, -1.0), index=dates)
        result = backtest_signal(close, signal)
        required = ["sharpe", "max_drawdown", "win_rate", "total_return", "n_trades",
                    "annual_return_pct", "monthly_return_pct", "sortino", "calmar"]
        for key in required:
            assert key in result, f"Missing: {key}"


class TestCoreUtils:
    def test_multiprocessing_wrapper(self):
        from rdagent.core.utils import multiprocessing_wrapper
        def fn(x):
            return x * 2
        results = multiprocessing_wrapper([(fn, (5,))], n=1)
        assert results[0] == 10

    def test_import_class_valid(self):
        from rdagent.core.utils import import_class
        cls = import_class("rdagent.core.exception.WorkflowError")
        assert cls is not None

    def test_singleton(self):
        from rdagent.core.utils import SingletonBaseClass
        class A(SingletonBaseClass):
            pass
        assert A() is A()


class TestBacktestFromFwdReturns:
    def test_all_nan(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_from_forward_returns
        idx = pd.MultiIndex.from_arrays([pd.date_range("2024-01-01", periods=500, freq="1min"),
                                          ["EURUSD"] * 500], names=["datetime", "instrument"])
        close = pd.Series(1.10 + np.arange(500) * 0.0001, index=idx)
        fwd = close.groupby(level="instrument").shift(-96) / close - 1
        result = backtest_from_forward_returns(pd.Series([np.nan] * 500, index=idx), fwd, close)
        assert result["status"] == "failed"

    def test_ic_bounds(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_from_forward_returns
        idx = pd.MultiIndex.from_arrays([pd.date_range("2024-01-01", periods=500, freq="1min"),
                                          ["EURUSD"] * 500], names=["datetime", "instrument"])
        close = pd.Series(1.10 + np.arange(500) * 0.0001, index=idx)
        fwd = close.groupby(level="instrument").shift(-96) / close - 1
        factor = pd.Series(np.random.default_rng(42).normal(0, 1, 500), index=idx)
        result = backtest_from_forward_returns(factor, fwd, close)
        if result["status"] == "success" and "ic" in result:
            assert -1.0 <= result["ic"] <= 1.0


class TestProtectionEdgeCases:
    def test_empty_manager(self):
        from rdagent.components.backtesting.protections import ProtectionManager
        pm = ProtectionManager()
        r = pm.check_all(returns=[0.01], timestamps=[], current_equity=100000, peak_equity=100000)
        assert not r.should_block

    def test_with_defaults(self):
        from rdagent.components.backtesting.protections import ProtectionManager
        pm = ProtectionManager()
        pm.create_default_protections()
        r = pm.check_all(returns=[0.01], timestamps=[pd.Timestamp.now()], current_equity=100000, peak_equity=101000)
        assert not r.should_block

    def test_get_stats(self):
        from rdagent.components.backtesting.protections import ProtectionManager
        pm = ProtectionManager()
        pm.create_default_protections()
        stats = pm.get_stats()
        assert isinstance(stats, dict)

    def test_protection_result_active(self):
        from rdagent.components.backtesting.protections import ProtectionResult
        from datetime import datetime, timedelta
        pr = ProtectionResult(should_block=True, reason="test", until=datetime.now() + timedelta(hours=1))
        assert pr.is_active


class TestEnvImports:
    def test_all_importable(self):
        from rdagent.utils.env import Env, QTDockerEnv, QlibCondaConf, QlibCondaEnv, KGDockerEnv
        assert all([Env, QTDockerEnv, QlibCondaConf, QlibCondaEnv, KGDockerEnv])


class TestLogInfra:
    def test_all_importable(self):
        from rdagent.log.conf import LOG_SETTINGS
        from rdagent.log.logger import RDAgentLog
        from rdagent.log.daily_log import session
        from rdagent.log.timer import RD_Agent_TIMER_wrapper
        assert LOG_SETTINGS is not None
        assert RDAgentLog is not None
        assert callable(session)
        assert RD_Agent_TIMER_wrapper is not None


class TestCoreExperiment:
    def test_task_and_experiment(self):
        from rdagent.core.experiment import Task, Experiment, FBWorkspace
        t = Task(name="t", description="d")
        exp = Experiment(sub_tasks=[t])
        assert len(exp.sub_tasks) == 1
        ws = FBWorkspace()
        assert ws.workspace_path is not None


class TestPromptLoader:
    def test_loads_strategy_generation(self):
        from rdagent.components.prompt_loader import load_prompt
        result = load_prompt("strategy_generation")
        assert isinstance(result, dict)

    def test_missing_raises(self):
        from rdagent.components.prompt_loader import load_prompt
        with pytest.raises(FileNotFoundError):
            load_prompt("xyz_nonexistent")


class TestApplyRiskMgmtMask:
    def test_output_same_length(self):
        from rdagent.components.backtesting.vbt_backtest import _apply_risk_mask
        dates = pd.date_range("2024-01-01", periods=100, freq="1min")
        close = pd.Series(1.10, index=dates)
        signal = pd.Series(np.where(np.arange(100) % 2 == 0, 1.0, -1.0), index=dates)
        masked, metrics = _apply_risk_mask(signal, close, leverage=1.0, txn_cost_bps=2.14)
        assert len(masked) == len(signal)
        assert isinstance(metrics, dict)

    def test_flat_signal(self):
        from rdagent.components.backtesting.vbt_backtest import _apply_risk_mask
        dates = pd.date_range("2024-01-01", periods=200, freq="1min")
        close = pd.Series(1.10, index=dates)
        signal = pd.Series(0.0, index=dates)
        masked, metrics = _apply_risk_mask(signal, close, leverage=1.0, txn_cost_bps=2.14)
        assert isinstance(metrics, dict)


class TestBacktestSignalMetrics:
    def test_flat_signal_zero_trades(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_signal
        dates = pd.date_range("2024-01-01", periods=500, freq="1min")
        close = pd.Series(1.10, index=dates)
        result = backtest_signal(close, pd.Series(0.0, index=dates))
        assert result["n_trades"] == 0

    def test_nan_signal_handled(self):
        from rdagent.components.backtesting.vbt_backtest import backtest_signal
        n = 200
        dates = pd.date_range("2024-01-01", periods=n, freq="1min")
        close = pd.Series(1.10 + np.random.default_rng(42).normal(0, 0.001, n).cumsum(), index=dates)
        signal = pd.Series(np.where(np.random.default_rng(42).normal(0, 1, n) > 0, 1.0, np.nan), index=dates)
        result = backtest_signal(close, signal)
        assert result["status"] in ("success", "failed")
