"""
Sentry Webhook Signature 驗證測試
===================================
P3.1-T2 by Claude 2026-04-27 — Tier-2 三服務感知強化

驗證：
1. SentryWebhookService.parse_sentry_issue() 正確解析 payload
2. SentryWebhookService.build_telegram_message() 組裝正確
3. verify_sentry_signature() 正確驗證 HMAC-SHA256
4. verify_sentry_signature() 在 prod 無 secret 時 raise
5. handle_sentry_error endpoint 有 signature 驗證（401 on bad sig）
6. handle_sentry_error endpoint 在有效簽章時通過驗證

注意：endpoint 測試使用 FastAPI TestClient + mock service
"""

from __future__ import annotations

import hashlib
import hmac

import pytest


# ─────────────────────────────────────────────────────────────────────────────
# Test: SentryWebhookService.parse_sentry_issue
# ─────────────────────────────────────────────────────────────────────────────

class TestParseSentryIssue:
    def setup_method(self):
        from src.services.sentry_webhook_service import SentryWebhookService
        self.svc = SentryWebhookService()

    def _make_payload(
        self,
        issue_id: str = "12345",
        title: str = "TypeError: null",
        culprit: str = "app/views.py:42",
        level: str = "error",
        project_slug: str = "awoooi",
        action: str = "triggered",
    ) -> dict:
        return {
            "action": action,
            "data": {
                "issue": {
                    "id": issue_id,
                    "title": title,
                    "culprit": culprit,
                    "level": level,
                    "firstSeen": "2026-04-27T00:00:00Z",
                    "count": 3,
                    "project": {"slug": project_slug},
                },
                "event": {
                    "message": "Something went wrong",
                    "platform": "python",
                    "tags": [["env", "prod"]],
                    "exception": {
                        "values": [{
                            "stacktrace": {
                                "frames": [
                                    {"filename": "app/views.py", "function": "get", "lineno": 42, "context_line": "x = obj.value"},
                                ]
                            }
                        }]
                    },
                },
            },
        }

    def test_parse_valid_payload(self):
        payload = self._make_payload()
        issue = self.svc.parse_sentry_issue(payload)
        assert issue is not None
        assert issue.issue_id == "12345"
        assert issue.title == "TypeError: null"
        assert issue.culprit == "app/views.py:42"
        assert issue.level == "error"
        assert issue.project == "awoooi"
        assert issue.count == 3

    def test_parse_missing_issue_id_returns_none(self):
        payload = self._make_payload()
        del payload["data"]["issue"]["id"]
        result = self.svc.parse_sentry_issue(payload)
        assert result is None

    def test_parse_stacktrace_extracted(self):
        payload = self._make_payload()
        issue = self.svc.parse_sentry_issue(payload)
        assert issue is not None
        assert len(issue.stacktrace) == 1
        assert issue.stacktrace[0]["filename"] == "app/views.py"

    def test_parse_empty_payload_returns_none(self):
        result = self.svc.parse_sentry_issue({})
        assert result is None

    def test_parse_exception_safety(self):
        """惡意 payload 不應 raise，應回傳 None"""
        result = self.svc.parse_sentry_issue({"data": None})
        assert result is None


# ─────────────────────────────────────────────────────────────────────────────
# Test: SentryWebhookService.build_telegram_message
# ─────────────────────────────────────────────────────────────────────────────

class TestBuildTelegramMessage:
    def setup_method(self):
        from src.services.sentry_webhook_service import (
            AIDecision,
            SentryIssueContext,
            SentryWebhookService,
        )
        self.svc = SentryWebhookService()
        self.issue = SentryIssueContext(
            issue_id="99",
            title="NullPointerException",
            culprit="com.example.Service:100",
            level="fatal",
            project="backend",
            first_seen="2026-04-27T00:00:00Z",
            count=1,
            message=None,
            platform="java",
            tags=[],
            stacktrace=[],
        )
        self.decision = AIDecision(
            root_cause="NullPointer in bean init",
            impact="Service down",
            fix_suggestion="Add null check",
            prevention="Unit test coverage",
            confidence=0.85,
            analyzed_by="ollama/qwen3",
        )

    def test_message_contains_project(self):
        msg = self.svc.build_telegram_message(self.issue, self.decision, "ap-001")
        assert "backend" in msg

    def test_message_contains_approval_id(self):
        msg = self.svc.build_telegram_message(self.issue, self.decision, "ap-001")
        assert "ap-001" in msg

    def test_message_contains_root_cause(self):
        msg = self.svc.build_telegram_message(self.issue, self.decision, "ap-001")
        assert "NullPointer" in msg

    def test_message_no_decision(self):
        """decision=None 時不應 crash"""
        msg = self.svc.build_telegram_message(self.issue, None, "ap-002")
        assert "ap-002" in msg

    def test_message_frequency_shown_when_count_gt_1(self):
        freq = {"count_1h": 3, "count_24h": 10, "count_7d": 25}
        msg = self.svc.build_telegram_message(self.issue, self.decision, "ap-003", anomaly_frequency=freq)
        assert "頻率" in msg
        assert "10" in msg  # count_24h


# ─────────────────────────────────────────────────────────────────────────────
# Test: verify_sentry_signature
# ─────────────────────────────────────────────────────────────────────────────

class TestVerifySentrySignature:
    def _make_sig(self, body: bytes, secret: str) -> str:
        return hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()

    def test_valid_signature_passes(self, monkeypatch):
        from src.services.sentry_webhook_service import verify_sentry_signature
        from src.core.config import settings
        monkeypatch.setattr(settings, "SENTRY_WEBHOOK_SECRET", "test-secret")
        monkeypatch.setattr(settings, "ENVIRONMENT", "dev")

        body = b'{"action": "triggered"}'
        sig = self._make_sig(body, "test-secret")
        assert verify_sentry_signature(body, sig) is True

    def test_invalid_signature_raises(self, monkeypatch):
        from src.services.sentry_webhook_service import (
            SentrySignatureError,
            verify_sentry_signature,
        )
        from src.core.config import settings
        monkeypatch.setattr(settings, "SENTRY_WEBHOOK_SECRET", "test-secret")
        monkeypatch.setattr(settings, "ENVIRONMENT", "dev")

        body = b'{"action": "triggered"}'
        with pytest.raises(SentrySignatureError):
            verify_sentry_signature(body, "wrong-signature")

    def test_missing_secret_in_prod_raises(self, monkeypatch):
        from src.services.sentry_webhook_service import (
            SentrySignatureError,
            verify_sentry_signature,
        )
        from src.core.config import settings
        monkeypatch.setattr(settings, "SENTRY_WEBHOOK_SECRET", "")
        monkeypatch.setattr(settings, "ENVIRONMENT", "prod")

        with pytest.raises(SentrySignatureError):
            verify_sentry_signature(b"body", "some-sig")

    def test_missing_secret_in_dev_passes(self, monkeypatch):
        from src.services.sentry_webhook_service import verify_sentry_signature
        from src.core.config import settings
        monkeypatch.setattr(settings, "SENTRY_WEBHOOK_SECRET", "")
        monkeypatch.setattr(settings, "ENVIRONMENT", "dev")

        # dev 環境無 secret → 允許通過（不驗簽）
        result = verify_sentry_signature(b"body", "")
        assert result is True

    def test_missing_sig_header_raises(self, monkeypatch):
        from src.services.sentry_webhook_service import (
            SentrySignatureError,
            verify_sentry_signature,
        )
        from src.core.config import settings
        monkeypatch.setattr(settings, "SENTRY_WEBHOOK_SECRET", "test-secret")
        monkeypatch.setattr(settings, "ENVIRONMENT", "dev")

        with pytest.raises(SentrySignatureError):
            verify_sentry_signature(b"body", "")  # 空 header


# ─────────────────────────────────────────────────────────────────────────────
# Test: endpoint signature 驗證接線確認
# ─────────────────────────────────────────────────────────────────────────────

class TestEndpointSignatureWiring:
    def test_endpoint_imports_verify_sentry_signature(self):
        """確認 sentry_webhook.py 已 import verify_sentry_signature"""
        import inspect
        from src.api.v1 import sentry_webhook
        source = inspect.getsource(sentry_webhook)
        assert "verify_sentry_signature" in source, \
            "sentry_webhook.py 應 import 並呼叫 verify_sentry_signature"

    def test_endpoint_imports_sentry_signature_error(self):
        """確認 sentry_webhook.py 已 import SentrySignatureError"""
        import inspect
        from src.api.v1 import sentry_webhook
        source = inspect.getsource(sentry_webhook)
        assert "SentrySignatureError" in source, \
            "sentry_webhook.py 應 import SentrySignatureError 用於 401 處理"

    def test_endpoint_calls_verify_before_json_parse(self):
        """確認 verify_sentry_signature 在 request.json() 之前呼叫"""
        import inspect
        from src.api.v1 import sentry_webhook
        source = inspect.getsource(sentry_webhook.handle_sentry_error)
        verify_pos = source.find("verify_sentry_signature")
        json_pos = source.find("request.json()")
        assert verify_pos != -1, "verify_sentry_signature 應在 handle_sentry_error 中"
        assert json_pos != -1, "request.json() 應在 handle_sentry_error 中"
        assert verify_pos < json_pos, "簽章驗證應在 JSON 解析之前"
