"""
ADR-091 Task T1 — AI 自學規則雙寫 alert_rule_catalog 測試
=============================================================
測試範圍：
  - test_insert_success_yaml_and_db            DB 插入成功路徑
  - test_db_failure_does_not_rollback_yaml     DB 失敗時僅 warning，不拋例外
  - test_idempotent_on_conflict                同 rule_name ON CONFLICT DO NOTHING 不報錯
  - test_feature_flag_disabled_skips_db_insert ENABLE_AI_RULE_CATALOG_WRITE=False 跳過 DB
  - test_confidence_defaults_to_0_5_review_status_draft
                                               confidence=0.50, review_status='draft'

禁止 Mock 測試鐵律：
  _insert_catalog_ai_generated() 純 Python + DB 邏輯。
  DB 依賴用 AsyncMock patch（get_db_context），只測業務邏輯分支，
  不依賴真實 PostgreSQL（unit 層）。
  _parse_for_to_seconds() 純 Python，直接呼叫真實函式。

建立：2026-04-28 (台北時區) Claude Sonnet 4.6 (ADR-091 T1)
"""
from __future__ import annotations

import os
from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, patch

import pytest

# conftest 在 import 前設定 MOCK_MODE=true
# 補設 DATABASE_URL，讓 Settings validate 不因必填欄位失敗
os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test")

from src.core.config import settings as _cfg_settings
from src.services.alert_rule_engine import (
    _insert_catalog_ai_generated,
    _parse_for_to_seconds,
)


# =============================================================================
# Helper
# =============================================================================

def _make_db_ctx(execute_side_effect=None):
    """回傳 (async_ctx_factory, mock_db)。
    simulate get_db_context() 的 async context manager。
    """
    mock_db = AsyncMock()
    if execute_side_effect is not None:
        mock_db.execute = AsyncMock(side_effect=execute_side_effect)
    else:
        mock_db.execute = AsyncMock(return_value=None)

    @asynccontextmanager
    async def _ctx():
        yield mock_db

    return _ctx, mock_db


_SAMPLE_RULE = {
    "id": "high_cpu_load",
    "match": {"alertname": ["HostHighCpuLoad"]},
    "response": {
        "suggested_action": "SCALE_UP",
        "kubectl_command": "kubectl scale deployment/api --replicas=3",
    },
    "labels": {"severity": "warning"},
    "annotations": {"summary": "CPU 超載"},
}


# =============================================================================
# _parse_for_to_seconds — 純 Python，無 mock 需要
# =============================================================================

class TestParseForToSeconds:
    def test_seconds(self):
        assert _parse_for_to_seconds("30s") == 30

    def test_minutes(self):
        assert _parse_for_to_seconds("5m") == 300

    def test_hours(self):
        assert _parse_for_to_seconds("2h") == 7200

    def test_days(self):
        assert _parse_for_to_seconds("1d") == 86400

    def test_no_unit_defaults_seconds(self):
        assert _parse_for_to_seconds("0") == 0

    def test_zero_string(self):
        assert _parse_for_to_seconds("0s") == 0

    def test_empty_string_returns_none(self):
        assert _parse_for_to_seconds("") is None

    def test_invalid_returns_none(self):
        assert _parse_for_to_seconds("abc") is None


# =============================================================================
# _insert_catalog_ai_generated
# =============================================================================

class TestInsertSuccessYamlAndDb:
    """DB 插入成功路徑"""

    async def test_insert_success_yaml_and_db(self):
        ctx, mock_db = _make_db_ctx()

        with (
            patch("src.db.base.get_db_context", ctx),
            patch.object(_cfg_settings, "ENABLE_AI_RULE_CATALOG_WRITE", True),
        ):
            await _insert_catalog_ai_generated(
                rule_dict=_SAMPLE_RULE,
                llm_source="ollama",
                rule_id="high_cpu_load",
                alertname_safe="HostHighCpuLoad",
            )

        mock_db.execute.assert_awaited_once()
        call_args = mock_db.execute.call_args
        sql_text = str(call_args[0][0])
        assert "ON CONFLICT" in sql_text
        params = call_args[0][1]
        assert params["rule_name"] == "HostHighCpuLoad"
        assert params["created_by_agent"] == "ollama"


class TestDbFailureDoesNotRollbackYaml:
    """DB 失敗時僅 warning，不拋例外（YAML 路徑不受影響）"""

    async def test_db_failure_does_not_raise(self):
        ctx, _ = _make_db_ctx(execute_side_effect=Exception("DB connection refused"))

        with (
            patch("src.db.base.get_db_context", ctx),
            patch.object(_cfg_settings, "ENABLE_AI_RULE_CATALOG_WRITE", True),
        ):
            # 不應拋例外
            await _insert_catalog_ai_generated(
                rule_dict=_SAMPLE_RULE,
                llm_source="gemini",
                rule_id="high_cpu_load",
                alertname_safe="HostHighCpuLoad",
            )
        # 到這裡代表沒有 raise — 測試通過


class TestIdempotentOnConflict:
    """同 rule_name 第二次觸發 ON CONFLICT DO NOTHING，不報錯"""

    async def test_second_call_no_error(self):
        # execute 回傳 None 模擬 DO NOTHING（無 row 回傳，無例外）
        ctx, mock_db = _make_db_ctx()

        with (
            patch("src.db.base.get_db_context", ctx),
            patch.object(_cfg_settings, "ENABLE_AI_RULE_CATALOG_WRITE", True),
        ):
            await _insert_catalog_ai_generated(
                rule_dict=_SAMPLE_RULE,
                llm_source="ollama",
                rule_id="high_cpu_load",
                alertname_safe="HostHighCpuLoad",
            )
            await _insert_catalog_ai_generated(
                rule_dict=_SAMPLE_RULE,
                llm_source="ollama",
                rule_id="high_cpu_load",
                alertname_safe="HostHighCpuLoad",
            )

        assert mock_db.execute.await_count == 2


class TestFeatureFlagDisabledSkipsDbInsert:
    """ENABLE_AI_RULE_CATALOG_WRITE=False → 跳過 DB insert"""

    async def test_flag_false_no_db_call(self):
        ctx, mock_db = _make_db_ctx()

        with (
            patch("src.db.base.get_db_context", ctx),
            patch.object(_cfg_settings, "ENABLE_AI_RULE_CATALOG_WRITE", False),
        ):
            await _insert_catalog_ai_generated(
                rule_dict=_SAMPLE_RULE,
                llm_source="ollama",
                rule_id="high_cpu_load",
                alertname_safe="HostHighCpuLoad",
            )

        mock_db.execute.assert_not_awaited()


class TestConfidenceAndReviewStatus:
    """SQL 中 confidence=0.50、review_status='draft'（符合 DB CHECK 約束）"""

    async def test_confidence_and_review_status_in_sql(self):
        ctx, mock_db = _make_db_ctx()

        with (
            patch("src.db.base.get_db_context", ctx),
            patch.object(_cfg_settings, "ENABLE_AI_RULE_CATALOG_WRITE", True),
        ):
            await _insert_catalog_ai_generated(
                rule_dict=_SAMPLE_RULE,
                llm_source="claude",
                rule_id="test_rule",
                alertname_safe="TestAlert",
            )

        call_args = mock_db.execute.call_args
        sql_text = str(call_args[0][0])
        # SQL 中應含 0.50 confidence 和 'draft'
        assert "0.50" in sql_text
        assert "'draft'" in sql_text
        # params 中不含 confidence/review_status（已硬編 SQL 內）
        params = call_args[0][1]
        assert "confidence" not in params
        assert "review_status" not in params
