import pytest
from unittest.mock import patch, MagicMock
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))


def test_fetch_latest_data_calls_subprocess():
    """fetch_latest_data가 심볼별로 fetch_history.py를 호출하는지 확인."""
    from scripts.weekly_report import fetch_latest_data

    with patch("subprocess.run") as mock_run:
        mock_run.return_value = MagicMock(returncode=0)
        fetch_latest_data(["XRPUSDT", "TRXUSDT"], days=35)

    assert mock_run.call_count == 2
    args_0 = mock_run.call_args_list[0][0][0]
    assert "--symbol" in args_0
    assert "XRPUSDT" in args_0
    assert "--days" in args_0
    assert "35" in args_0


def test_run_backtest_returns_summary():
    """run_backtest가 WF 백테스트를 실행하고 결과를 반환하는지 확인."""
    from scripts.weekly_report import run_backtest

    mock_result = {
        "summary": {
            "total_trades": 27, "total_pnl": 217.0, "return_pct": 21.7,
            "win_rate": 66.7, "profit_factor": 1.57, "max_drawdown_pct": 12.0,
            "sharpe_ratio": 33.3, "avg_win": 20.0, "avg_loss": -10.0,
            "total_fees": 5.0, "close_reasons": {},
        },
        "folds": [], "trades": [],
    }

    with patch("scripts.weekly_report.WalkForwardBacktester") as MockWF:
        MockWF.return_value.run.return_value = mock_result
        result = run_backtest(
            symbols=["XRPUSDT"], train_months=3, test_months=1,
            params={"atr_sl_mult": 2.0, "atr_tp_mult": 2.0,
                    "signal_threshold": 3, "adx_threshold": 25,
                    "volume_multiplier": 2.5},
        )

    assert result["summary"]["profit_factor"] == 1.57
    assert result["summary"]["total_trades"] == 27


def test_fetch_live_trades_from_api():
    """대시보드 API에서 청산 트레이드를 가져오는지 확인."""
    from scripts.weekly_report import fetch_live_trades

    mock_response = MagicMock()
    mock_response.json.return_value = {
        "trades": [
            {"symbol": "XRPUSDT", "direction": "LONG", "net_pnl": 19.568,
             "commission": 0.216, "status": "CLOSED"},
        ],
        "total": 1,
    }
    mock_response.raise_for_status = MagicMock()

    with patch("scripts.weekly_report.httpx.get", return_value=mock_response):
        trades = fetch_live_trades("http://test:8000")

    assert len(trades) == 1
    assert trades[0]["symbol"] == "XRPUSDT"
    assert trades[0]["net_pnl"] == pytest.approx(19.568)


def test_fetch_live_trades_api_failure():
    """API 실패 시 빈 리스트 반환."""
    from scripts.weekly_report import fetch_live_trades

    with patch("scripts.weekly_report.httpx.get", side_effect=Exception("connection refused")):
        trades = fetch_live_trades("http://unreachable:8000")

    assert trades == []


def test_fetch_live_stats_from_api():
    """대시보드 API에서 전체 통계를 가져오는지 확인."""
    from scripts.weekly_report import fetch_live_stats

    mock_response = MagicMock()
    mock_response.json.return_value = {
        "total_trades": 15, "wins": 9, "losses": 6,
        "total_pnl": 42.5, "total_fees": 3.2,
    }
    mock_response.raise_for_status = MagicMock()

    with patch("scripts.weekly_report.httpx.get", return_value=mock_response):
        stats = fetch_live_stats("http://test:8000")

    assert stats["total_trades"] == 15
    assert stats["wins"] == 9


import json
from datetime import date, timedelta


def test_load_trend_reads_previous_reports(tmp_path):
    """이전 주간 리포트를 읽어 PF/승률/MDD 추이를 반환."""
    from scripts.weekly_report import load_trend

    for i, (pf, wr, mdd) in enumerate([
        (1.31, 48.0, 9.0), (1.24, 45.0, 11.0),
        (1.20, 44.0, 12.0), (1.18, 43.0, 14.0),
    ]):
        d = date(2026, 3, 7) - timedelta(weeks=3 - i)
        report = {
            "date": d.isoformat(),
            "backtest": {"summary": {
                "profit_factor": pf, "win_rate": wr, "max_drawdown_pct": mdd,
                "total_trades": 20,
            }},
        }
        (tmp_path / f"report_{d.isoformat()}.json").write_text(json.dumps(report))

    trend = load_trend(str(tmp_path), weeks=4)
    assert len(trend["pf"]) == 4
    assert trend["pf"] == [1.31, 1.24, 1.20, 1.18]
    assert trend["pf_declining_3w"] is True


def test_load_trend_empty_dir(tmp_path):
    """리포트가 없으면 빈 추이 반환."""
    from scripts.weekly_report import load_trend

    trend = load_trend(str(tmp_path), weeks=4)
    assert trend["pf"] == []
    assert trend["pf_declining_3w"] is False


def test_check_ml_trigger_all_met():
    """3개 조건 모두 충족 시 recommend=True."""
    from scripts.weekly_report import check_ml_trigger

    result = check_ml_trigger(
        cumulative_trades=200, current_pf=0.85, pf_declining_3w=True,
    )
    assert result["recommend"] is True
    assert result["met_count"] == 3


def test_check_ml_trigger_none_met():
    """조건 미충족 시 recommend=False."""
    from scripts.weekly_report import check_ml_trigger

    result = check_ml_trigger(
        cumulative_trades=50, current_pf=1.5, pf_declining_3w=False,
    )
    assert result["recommend"] is False
    assert result["met_count"] == 0


def test_run_degradation_sweep_returns_top_n():
    """스윕을 실행하고 PF 상위 N개 대안을 반환."""
    from scripts.weekly_report import run_degradation_sweep
    from unittest.mock import patch

    fake_summaries = [
        {"profit_factor": 1.15, "total_trades": 30, "total_pnl": 50, "return_pct": 5,
         "win_rate": 55, "avg_win": 10, "avg_loss": -8, "max_drawdown_pct": 10,
         "sharpe_ratio": 2.0, "total_fees": 1, "close_reasons": {}},
        {"profit_factor": 1.08, "total_trades": 25, "total_pnl": 30, "return_pct": 3,
         "win_rate": 50, "avg_win": 8, "avg_loss": -7, "max_drawdown_pct": 12,
         "sharpe_ratio": 1.5, "total_fees": 1, "close_reasons": {}},
        {"profit_factor": 0.95, "total_trades": 20, "total_pnl": -10, "return_pct": -1,
         "win_rate": 40, "avg_win": 6, "avg_loss": -9, "max_drawdown_pct": 15,
         "sharpe_ratio": 0.5, "total_fees": 1, "close_reasons": {}},
    ]
    fake_combos = [
        {"atr_sl_mult": 1.5}, {"atr_sl_mult": 1.0}, {"atr_sl_mult": 2.0},
    ]

    with patch("scripts.weekly_report.run_single_backtest") as mock_bt:
        mock_bt.side_effect = fake_summaries
        with patch("scripts.weekly_report.generate_combinations", return_value=fake_combos):
            alternatives = run_degradation_sweep(
                symbols=["XRPUSDT"], train_months=3, test_months=1, top_n=3,
            )

    assert len(alternatives) <= 3
    assert alternatives[0]["summary"]["profit_factor"] >= alternatives[1]["summary"]["profit_factor"]


def test_format_report_normal():
    """정상 상태(PF >= 1.0) 리포트 포맷."""
    from scripts.weekly_report import format_report

    report_data = {
        "date": "2026-03-07",
        "backtest": {
            "summary": {
                "profit_factor": 1.24, "win_rate": 45.0,
                "max_drawdown_pct": 12.0, "total_trades": 88,
            },
            "per_symbol": {
                "XRPUSDT": {"profit_factor": 1.57, "total_trades": 27, "win_rate": 66.7},
                "TRXUSDT": {"profit_factor": 1.29, "total_trades": 25, "win_rate": 52.0},
                "DOGEUSDT": {"profit_factor": 1.09, "total_trades": 36, "win_rate": 44.4},
            },
        },
        "live_trades": {"count": 8, "net_pnl": 12.34, "win_rate": 62.5},
        "trend": {"pf": [1.31, 1.24], "win_rate": [48.0, 45.0], "mdd": [9.0, 12.0], "pf_declining_3w": False},
        "ml_trigger": {"recommend": False, "met_count": 0, "conditions": {
            "cumulative_trades_enough": False, "pf_below_1": False, "pf_declining_3w": False,
        }, "cumulative_trades": 47, "threshold": 150},
        "sweep": None,
    }

    text = format_report(report_data)
    assert "\uc8fc\uac04 \uc804\ub7b5 \ub9ac\ud3ec\ud2b8" in text
    assert "1.24" in text
    assert "XRPUSDT" in text or "XRP" in text


def test_format_report_degraded():
    """PF < 1.0일 때 스윕 결과 + ML 권장이 포함되는지 확인."""
    from scripts.weekly_report import format_report

    report_data = {
        "date": "2026-06-07",
        "backtest": {
            "summary": {"profit_factor": 0.87, "win_rate": 38.0, "max_drawdown_pct": 22.0, "total_trades": 90},
            "per_symbol": {},
        },
        "live_trades": {"count": 0, "net_pnl": 0, "win_rate": 0},
        "trend": {"pf": [1.1, 1.0, 0.87], "win_rate": [], "mdd": [], "pf_declining_3w": True},
        "ml_trigger": {"recommend": True, "met_count": 3, "conditions": {
            "cumulative_trades_enough": True, "pf_below_1": True, "pf_declining_3w": True,
        }, "cumulative_trades": 182, "threshold": 150},
        "sweep": [
            {"params": {"atr_sl_mult": 2.0, "atr_tp_mult": 2.5, "adx_threshold": 30, "volume_multiplier": 2.5, "signal_threshold": 3},
             "summary": {"profit_factor": 1.15, "total_trades": 30}},
        ],
    }

    text = format_report(report_data)
    assert "0.87" in text
    assert "ML" in text
    assert "1.15" in text


def test_send_report_uses_notifier():
    """Discord 웹훅으로 리포트를 전송."""
    from scripts.weekly_report import send_report
    from unittest.mock import patch

    with patch("scripts.weekly_report.DiscordNotifier") as MockNotifier:
        instance = MockNotifier.return_value
        send_report("test report content", webhook_url="https://example.com/webhook")
        instance._send.assert_called_once_with("test report content")


def test_generate_report_orchestration(tmp_path):
    """generate_report가 모든 단계를 조합하여 리포트 dict를 반환."""
    from scripts.weekly_report import generate_report
    from unittest.mock import patch

    # 합산 지표는 개별 트레이드에서 직접 계산되므로 mock에 트레이드 포함
    mock_trades = [
        {"net_pnl": 20.0, "entry_fee": 1.0, "exit_fee": 1.0, "exit_time": "2025-06-10 12:00:00"},
        {"net_pnl": 15.0, "entry_fee": 1.0, "exit_fee": 1.0, "exit_time": "2025-06-11 12:00:00"},
        {"net_pnl": -10.0, "entry_fee": 1.0, "exit_fee": 1.0, "exit_time": "2025-06-12 12:00:00"},
    ]
    mock_bt_result = {
        "summary": {
            "profit_factor": 1.24, "win_rate": 45.0,
            "max_drawdown_pct": 12.0, "total_trades": 3,
            "total_pnl": 25.0, "return_pct": 2.5,
            "avg_win": 17.5, "avg_loss": -10.0,
            "sharpe_ratio": 33.0, "total_fees": 6.0,
            "close_reasons": {},
        },
        "folds": [], "trades": mock_trades,
    }

    with patch("scripts.weekly_report.run_backtest", return_value=mock_bt_result):
        with patch("scripts.weekly_report.fetch_live_stats", return_value={"total_trades": 0, "wins": 0, "total_pnl": 0}):
            with patch("scripts.weekly_report.fetch_live_trades", return_value=[]):
                with patch("scripts.weekly_report.load_trend", return_value={
                    "pf": [1.31], "win_rate": [48.0], "mdd": [9.0], "pf_declining_3w": False,
                }):
                    report = generate_report(
                        symbols=["XRPUSDT"],
                        report_dir=str(tmp_path),
                        report_date=date(2026, 3, 7),
                    )

    assert report["date"] == "2026-03-07"
    # PF는 개별 트레이드에서 직접 계산: GP=35, GL=10 → 3.5
    assert report["backtest"]["summary"]["profit_factor"] == 3.5
    assert report["backtest"]["summary"]["total_trades"] == 3
    assert report["sweep"] is None  # PF >= 1.0이면 스윕 안 함


def test_save_report_creates_json(tmp_path):
    """리포트를 JSON으로 저장."""
    from scripts.weekly_report import save_report

    report = {"date": "2026-03-07", "test": True}
    save_report(report, str(tmp_path))

    saved = list(tmp_path.glob("report_*.json"))
    assert len(saved) == 1
    loaded = json.loads(saved[0].read_text())
    assert loaded["date"] == "2026-03-07"


def test_generate_quantstats_report_creates_html(tmp_path):
    """트레이드 데이터로 quantstats HTML 리포트를 생성."""
    from scripts.weekly_report import generate_quantstats_report

    trades = [
        {"exit_time": "2026-03-01 12:00:00", "net_pnl": 5.0},
        {"exit_time": "2026-03-02 15:00:00", "net_pnl": -2.0},
        {"exit_time": "2026-03-03 09:00:00", "net_pnl": 8.0},
        {"exit_time": "2026-03-04 18:00:00", "net_pnl": -1.5},
        {"exit_time": "2026-03-05 10:00:00", "net_pnl": 3.0},
    ]
    output = str(tmp_path / "test_report.html")
    result = generate_quantstats_report(trades, output)

    assert result is not None
    assert Path(result).exists()
    content = Path(result).read_text()
    assert "CoinTrader" in content


def test_generate_quantstats_report_empty_trades(tmp_path):
    """트레이드가 없으면 None 반환."""
    from scripts.weekly_report import generate_quantstats_report

    output = str(tmp_path / "empty.html")
    result = generate_quantstats_report([], output)
    assert result is None
