"""
tests/test_openclaw_daily_template.py
─────────────────────────────────────────────────────────────────
Operation Ollama-First v5.0 / Phase 3 / A8 — 日報模板路由測試

驗證面：
  T1. flag=false → 走相容 legacy 函式，但函式內仍是 Ollama-first
  T2. flag=true（預設）→ 走 _generate_daily_report_hermes_template
  T3. _compute_daily_kpi 各 KPI 函數可獨立 mock 測（DB 失敗回安全預設）
  T4. _render_daily_template_v2 缺欄位優雅降級（_SafeUndefined 不 raise）
  T5. _SafeUndefined 對 'X.Y.Z' 巢狀存取不爆

紀律：
  - 不打真實 DB / Gemini API
  - 不寫 ai_insights
  - 不發 Telegram
"""

import os
import logging
from datetime import date, datetime
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest


# ═══════════════════════════════════════════════════════════════════════════
# Fixtures
# ═══════════════════════════════════════════════════════════════════════════

@pytest.fixture(autouse=True)
def _reset_flag(monkeypatch):
    """每個 test 前清環境變數，避免互相污染"""
    monkeypatch.delenv('OPENCLAW_DAILY_HERMES_TEMPLATE', raising=False)
    yield


# ═══════════════════════════════════════════════════════════════════════════
# T1+T2 — Routing
# ═══════════════════════════════════════════════════════════════════════════

class TestRouting:

    def test_flag_false_routes_to_legacy(self, monkeypatch):
        """flag=false → 相容 legacy 函式被呼叫；Gemini 仍只可在函式內當備援"""
        monkeypatch.setenv('OPENCLAW_DAILY_HERMES_TEMPLATE', 'false')
        import importlib
        import services.openclaw_strategist_service as svc
        importlib.reload(svc)

        legacy_called = {'v': False}
        hermes_called = {'v': False}

        def mock_legacy():
            legacy_called['v'] = True
            return {'status': 'ok', 'mode': 'legacy'}

        def mock_hermes():
            hermes_called['v'] = True
            return {'status': 'ok', 'mode': 'hermes_template'}

        monkeypatch.setattr(svc, '_legacy_full_gemini_daily_report', mock_legacy)
        monkeypatch.setattr(svc, '_generate_daily_report_hermes_template', mock_hermes)

        svc.generate_daily_report()

        assert legacy_called['v'] is True, "flag=false 必須走 legacy 路徑"
        assert hermes_called['v'] is False, "flag=false 不可走 hermes 模板"

    def test_flag_true_routes_to_hermes_template(self, monkeypatch):
        """flag=true → _generate_daily_report_hermes_template 被呼叫"""
        monkeypatch.setenv('OPENCLAW_DAILY_HERMES_TEMPLATE', 'true')
        import importlib
        import services.openclaw_strategist_service as svc
        importlib.reload(svc)

        legacy_called = {'v': False}
        hermes_called = {'v': False}

        monkeypatch.setattr(svc, '_legacy_full_gemini_daily_report',
                            lambda: legacy_called.update(v=True) or {'status': 'ok'})
        monkeypatch.setattr(svc, '_generate_daily_report_hermes_template',
                            lambda: hermes_called.update(v=True) or {'status': 'ok'})

        svc.generate_daily_report()

        assert hermes_called['v'] is True, "flag=true 必須走 hermes 模板路徑"
        assert legacy_called['v'] is False, "flag=true 不可走 legacy"

    def test_flag_default_is_true(self, monkeypatch):
        """無 env 設定時 → 預設 true（Hermes/Ollama-first）"""
        # 不 set env
        import importlib
        import services.openclaw_strategist_service as svc
        importlib.reload(svc)

        assert svc._daily_hermes_template_enabled() is True  # Operation Ollama-First v5.0：預設翻 ON


# ═══════════════════════════════════════════════════════════════════════════
# T3 — KPI 計算（DB 失敗安全降級）
# ═══════════════════════════════════════════════════════════════════════════

class TestKPIComputation:

    def test_compute_daily_kpi_invalid_date_raises(self):
        import services.openclaw_strategist_service as svc
        with pytest.raises(TypeError):
            svc._compute_daily_kpi("not-a-date")

    def test_revenue_kpi_returns_safe_default_on_db_error(self, monkeypatch):
        """DB 異常時 _query_revenue_kpi 回零（不拋 exception）"""
        import services.openclaw_strategist_service as svc

        class _BrokenSession:
            def execute(self, *a, **kw):
                raise RuntimeError('DB connection lost')
            def close(self):
                pass

        monkeypatch.setattr(svc, 'get_session', lambda: _BrokenSession())

        result = svc._query_revenue_kpi(date(2026, 5, 3))

        assert result['today'] == 0.0
        assert result['dod_pct'] == 0.0
        assert result['wow_pct'] == 0.0

    def test_fetch_top_threats_logs_malformed_metadata(self, monkeypatch, caplog):
        import services.openclaw_strategist_service as svc

        class _Rows:
            def fetchall(self):
                return [
                    ("SKU-1", "price gap", 0.91, "{bad-json", datetime(2026, 5, 13)),
                ]

        class _Session:
            def execute(self, *a, **kw):
                return _Rows()
            def close(self):
                pass

        monkeypatch.setattr(svc, "get_session", lambda: _Session())
        caplog.set_level(logging.DEBUG, logger="services.openclaw_strategist_service")

        threats = svc._fetch_top_threats(1)

        assert threats[0]["sku"] == "SKU-1"
        assert threats[0]["gap_pct"] == 0
        assert "metadata_json decode failed" in caplog.text

    def test_competitor_summary_tracks_rescore_review_bucket(self):
        source = (Path(__file__).resolve().parents[1] / "services/openclaw_strategist_service.py").read_text(encoding="utf-8")

        assert "rescore_accepted_count" in source
        assert "fetch_competitor_coverage" in source
        assert "summarize_review_decision_envelopes" in source
        assert "review_decision_text" in source
        assert "重算待人工覆核" in source


# ═══════════════════════════════════════════════════════════════════════════
# T4+T5 — Template 渲染與缺欄位優雅降級
# ═══════════════════════════════════════════════════════════════════════════

class TestTemplateRendering:

    def test_render_with_full_context_succeeds(self):
        import services.openclaw_strategist_service as svc

        context = {
            'today': '2026年05月02日',
            'weekday': '週五',
            'revenue': {
                'today': 1234567.0,
                'yesterday': 1100000.0,
                'avg_7d': 1050000.0,
                'dod_pct': 12.2,
                'wow_pct': 17.6,
            },
            'orders': {
                'today_rows': 234,
                'today_sku': 187,
                'avg_value_today': 5276.0,
            },
            'top_skus': [
                {'name': 'SKU-A', 'qty': 50, 'revenue': 100000},
                {'name': 'SKU-B', 'qty': 32, 'revenue': 80000},
            ],
            'price_gaps': [
                {'sku_name': '商品X', 'momo_price': 1200, 'comp_price': 980,
                 'gap_pct': 22.4, 'competitor': 'PChome'},
            ],
            'inventory_alerts': [],
            'priority_actions': ['對 SKU-A 啟動 EA 流程', '觀察 PChome 補貼'],
            'gemini_insight': '今日營收強勁成長，建議加碼家電促銷檔期。',
        }

        rendered = svc._render_daily_template_v2(context)

        assert '2026年05月02日' in rendered
        assert '週五' in rendered
        assert 'NT$1,234,567' in rendered
        assert 'SKU-A' in rendered
        assert '商品X' in rendered
        assert 'PChome' in rendered
        assert '今日營收強勁成長' in rendered

    def test_render_with_missing_fields_does_not_raise(self):
        """_SafeUndefined 對缺欄位回 — 不拋 UndefinedError"""
        import services.openclaw_strategist_service as svc

        context = {
            'today': '2026年05月02日',
            'weekday': '週五',
            'revenue': {'today': 0.0, 'dod_pct': 0.0, 'wow_pct': 0.0},
            'orders': {},  # 空 dict
            'top_skus': [],
            'price_gaps': [],
            'inventory_alerts': [],
            'priority_actions': [],
            'gemini_insight': '',
        }

        # 不 raise 即過
        rendered = svc._render_daily_template_v2(context)

        assert isinstance(rendered, str)
        assert len(rendered) > 0
        # 缺欄位該降級為 — 或預設值
        assert '今日無熱銷資料' in rendered or '✅' in rendered

    def test_safe_undefined_nested_access(self):
        """_SafeUndefined 對 'X.Y.Z' 巢狀存取不爆"""
        import services.openclaw_strategist_service as svc

        # 完全無 'revenue' 也不該 raise
        context = {
            'today': '2026年05月02日',
            'weekday': '週五',
            # 故意省略 revenue / orders / top_skus 等
        }
        rendered = svc._render_daily_template_v2(context)
        assert isinstance(rendered, str)
        assert '2026年05月02日' in rendered
