"""Integration tests for the Phase 2 decision funnel workspace."""

from datetime import date
from types import SimpleNamespace
from unittest.mock import patch

import pytest
from django.contrib.auth.models import User
from django.test import Client

from apps.audit.infrastructure.models import AttributionReport, ExperienceSummary, LossAnalysis
from apps.backtest.infrastructure.models import BacktestResultModel

pytestmark = pytest.mark.timeout(90)


def _response_text(response) -> str:
    return response.content.decode("utf-8")


def _assert_html_partial_contract(response, *fragments: str) -> str:
    assert response.status_code == 200
    assert response["Content-Type"].startswith("text/html")

    content = _response_text(response)
    for fragment in fragments:
        assert fragment in content
    return content


def _action_recommendation_stub(*recommended_sectors):
    """Build a minimal action recommendation compatible with Step 2 and Step 3."""
    return SimpleNamespace(
        reasoning="测试用资产配置建议",
        regime_contribution=0.6,
        pulse_contribution=0.4,
        position_limit_pct=0.8,
        recommended_sectors=list(recommended_sectors),
        asset_weights={"equity": 0.6, "bond": 0.2, "commodity": 0.1, "cash": 0.1},
        risk_budget_pct=0.7,
    )


@pytest.fixture
def authenticated_client(db):
    """Return a logged-in Django test client."""
    user = User.objects.create_user(
        username="decision-funnel-user",
        email="decision-funnel@example.com",
        password="testpass123",
    )
    client = Client()
    client.force_login(user)
    return client


@pytest.fixture
def sample_backtest(db):
    """Create a completed backtest for audit replay."""
    return BacktestResultModel.objects.create(
        name="Decision Funnel Backtest",
        status="completed",
        start_date=date(2026, 1, 1),
        end_date=date(2026, 3, 1),
        initial_capital=100000.0,
        final_capital=108000.0,
        total_return=0.08,
        annualized_return=0.16,
        max_drawdown=-0.03,
        sharpe_ratio=1.2,
        equity_curve=[["2026-01-01", 100000.0], ["2026-03-01", 108000.0]],
        regime_history=[{"date": "2026-01-01", "regime": "RECOVERY", "confidence": 0.8}],
        trades=[],
    )


@pytest.fixture
def sample_attribution_report(sample_backtest):
    """Create an existing attribution report and related artifacts."""
    report = AttributionReport.objects.create(
        backtest=sample_backtest,
        period_start=sample_backtest.start_date,
        period_end=sample_backtest.end_date,
        attribution_method="brinson",
        regime_timing_pnl=0.03,
        asset_selection_pnl=0.04,
        interaction_pnl=0.01,
        total_pnl=0.08,
        regime_accuracy=0.75,
        regime_predicted="RECOVERY",
        regime_actual="RECOVERY",
    )
    LossAnalysis.objects.create(
        report=report,
        loss_source="ASSET_SELECTION_ERROR",
        impact=-0.01,
        impact_percentage=12.5,
        description="选股权重分配过于集中。",
        improvement_suggestion="降低单一资产集中度。",
    )
    ExperienceSummary.objects.create(
        report=report,
        lesson="顺周期资产需要配合更严格的仓位控制。",
        recommendation="下次优先检查波动率和仓位上限。",
        priority="HIGH",
    )
    return report


@pytest.mark.django_db
def test_decision_step3_partial_uses_rotation_outputs(authenticated_client):
    """Step 3 partial should render real rotation/action orchestration output."""
    with (
        patch(
            "core.application.decision_context.GetActionRecommendationUseCase.execute",
            return_value=SimpleNamespace(
                **_action_recommendation_stub("科技", "消费").__dict__,
                context_observed_at=date(2026, 5, 9),
                context_source="action_log_cached",
            ),
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_rotation_recommendation",
            return_value={
                "target_allocation": {
                    "CSI300": 0.42,
                    "GOLD": 0.18,
                },
                "data_source": "stored_signal",
                "is_stale": False,
                "signal_date": "2026-05-09",
            },
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_asset_master",
            return_value=[
                {"code": "CSI300", "name": "沪深300", "category": "equity"},
                {"code": "GOLD", "name": "黄金", "category": "equity"},
            ],
        ),
    ):
        response = authenticated_client.get("/api/decision/context/step3/")

    content = _assert_html_partial_contract(
        response,
        "阶段 3: 板块选择与风格映射",
        "当前决策账户",
        "推荐偏好依据",
        "Regime + 动量板块",
        "风格轮动信号 (Rotation)",
        "数据日期",
        "有效至",
        "口径: 每晚预计算",
    )
    assert "科技" in content
    assert "消费" in content
    assert "沪深300" in content
    assert "BUY" in content


@pytest.mark.django_db
def test_decision_step3_partial_shows_rotation_fallback_warning(authenticated_client):
    """Step 3 partial should surface stale rotation fallback hints to the user."""
    with (
        patch(
            "core.application.decision_context.GetActionRecommendationUseCase.execute",
            return_value=SimpleNamespace(
                **_action_recommendation_stub("红利", "大盘").__dict__,
                context_observed_at=date(2026, 3, 30),
                context_source="action_log_cached",
            ),
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_rotation_recommendation",
            return_value={
                "target_allocation": {
                    "515180": 0.55,
                    "510300": 0.45,
                },
                "data_source": "stored_signal_fallback",
                "is_stale": True,
                "warning_message": "实时轮动重算失败，当前展示最近一次已落库信号，结果可能滞后。",
                "signal_date": "2026-03-30",
            },
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_asset_master",
            return_value=[
                {"code": "515180", "name": "红利ETF", "category": "equity"},
                {"code": "510300", "name": "沪深300ETF", "category": "equity"},
            ],
        ),
    ):
        response = authenticated_client.get("/api/decision/context/step3/")

    content = _assert_html_partial_contract(
        response,
        "阶段 3: 板块选择与风格映射",
        "轮动结果状态",
        "Regime + 动量板块",
        "风格轮动信号 (Rotation)",
        "数据日期",
        "有效至",
    )
    assert "实时轮动重算失败" in content
    assert "已回退" in content
    assert "2026-03-30" in content


@pytest.mark.django_db
def test_decision_step1_and_step2_partials_show_freshness_metadata(authenticated_client):
    """Step 1 and Step 2 partials should show validity windows for nightly snapshots."""
    with (
        patch(
            "core.application.decision_context.get_regime_repository",
            return_value=SimpleNamespace(
                get_latest_snapshot=lambda before_date=None: SimpleNamespace(
                    dominant_regime="Recovery",
                    observed_at=date(2026, 5, 9),
                    confidence=0.91,
                )
            ),
        ),
        patch(
            "core.application.decision_context.GetLatestPulseUseCase.execute",
            return_value=SimpleNamespace(
                observed_at=date(2026, 5, 9),
                composite_score=0.42,
                regime_strength="strong",
                is_reliable=True,
            ),
        ),
        patch(
            "core.application.decision_context.GetActionRecommendationUseCase.execute",
            return_value=SimpleNamespace(
                **_action_recommendation_stub("科技").__dict__,
                context_observed_at=date(2026, 5, 9),
                context_source="action_log_cached",
            ),
        ),
    ):
        step1_response = authenticated_client.get("/api/decision/context/step1/")
        step2_response = authenticated_client.get("/api/decision/context/step2/")

    step1_content = _assert_html_partial_contract(
        step1_response,
        "阶段 1: 宏观准入判断",
        "只读判断",
        "不在这里修改账户配置",
        "数据日期",
        "有效至",
        "口径: 每晚预计算",
        "Regime 快照",
        "Pulse 快照",
    )
    assert "2026-05-09" in step1_content

    step2_content = _assert_html_partial_contract(
        step2_response,
        "阶段 2: 资产方向建议",
        "建议风险敞口上限",
        "不可在此处编辑",
        "不是单笔交易限额",
        "数据日期",
        "有效至",
        "口径: 每晚预计算",
        "配置建议快照",
    )
    assert "2026-05-09" in step2_content
    assert "70%" in step2_content
    assert ">0.70<" not in step2_content


@pytest.mark.django_db
def test_decision_audit_api_returns_existing_report(
    authenticated_client,
    sample_attribution_report,
):
    """Standalone audit API should return the current attribution payload."""
    response = authenticated_client.get(
        f"/api/decision/audit/?backtest_id={sample_attribution_report.backtest_id}"
    )

    assert response.status_code == 200
    assert response["Content-Type"].startswith("application/json")
    payload = response.json()
    assert payload["success"] is True
    assert payload["data"]["report_id"] == sample_attribution_report.id
    assert payload["data"]["backtest_id"] == sample_attribution_report.backtest_id
    assert payload["data"]["attribution_method"] == "brinson"
    assert payload["data"]["portfolio_return"] == 8.0
    assert payload["data"]["allocation_effect"] == 3.0
    assert payload["data"]["selection_effect"] == 4.0
    assert payload["data"]["interaction_effect"] == 1.0
    assert payload["data"]["loss_source"] == "资产选择错误"


@pytest.mark.django_db
def test_decision_funnel_context_and_step6_partial(
    authenticated_client,
    sample_attribution_report,
):
    """Context API may expose audit data, but Step 6 partial must stay execution-only."""
    with (
        patch(
            "core.application.decision_context.GetActionRecommendationUseCase.execute",
            return_value=_action_recommendation_stub("科技"),
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_rotation_recommendation",
            return_value={"target_allocation": {"CSI300": 0.4}},
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_asset_master",
            return_value=[{"code": "CSI300", "name": "沪深300", "category": "equity"}],
        ),
    ):
        context_response = authenticated_client.get(
            f"/api/decision/funnel/context/?backtest_id={sample_attribution_report.backtest_id}"
        )
        partial_response = authenticated_client.get(
            f"/api/decision/context/step6/?backtest_id={sample_attribution_report.backtest_id}"
        )

    assert context_response.status_code == 200
    assert context_response["Content-Type"].startswith("application/json")
    context_payload = context_response.json()
    assert context_payload["success"] is True
    assert context_payload["data"]["step3_sectors"]["rotation_signals"][0]["sector"] == "沪深300"
    assert (
        context_payload["data"]["step6_audit"]["lesson_learned"]
        == "顺周期资产需要配合更严格的仓位控制。"
    )

    partial_html = _assert_html_partial_contract(
        partial_response,
        "阶段 6: 审批执行",
        "计划审批与执行落地",
        "当前审批请求",
        "执行接入规划",
        "execution-plan-panel",
        "自动交易系统",
    )
    assert "阶段 6: 审批执行" in partial_html
    assert "Brinson 模型归因报告" not in partial_html


@pytest.mark.django_db
def test_decision_context_api_exposes_rotation_metadata(authenticated_client):
    """Decision funnel context API should expose rotation freshness metadata."""
    with (
        patch(
            "core.application.decision_context.GetActionRecommendationUseCase.execute",
            return_value=_action_recommendation_stub("科技"),
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_rotation_recommendation",
            return_value={
                "target_allocation": {"CSI300": 0.4},
                "data_source": "stored_signal",
                "is_stale": False,
                "warning_message": None,
                "signal_date": "2026-03-31",
            },
        ),
        patch(
            "core.application.decision_context.RotationIntegrationService.get_asset_master",
            return_value=[{"code": "CSI300", "name": "沪深300", "category": "equity"}],
        ),
    ):
        response = authenticated_client.get("/api/decision/funnel/context/")

    assert response.status_code == 200
    payload = response.json()
    step3 = payload["data"]["step3_sectors"]
    assert step3["rotation_data_source"] == "stored_signal"
    assert step3["rotation_is_stale"] is False
    assert step3["rotation_warning_message"] is None
    assert step3["rotation_signal_date"] == "2026-03-31"
