"""
AWOOOI API - Gitea Webhook Service
====================================
ADR-059: GitHub → Gitea Webhook 遷移

整合流程:
1. Gitea Webhook (PR/Push) → AWOOOI API
2. HMAC-SHA256 簽章驗證 (X-Gitea-Signature)
3. 解析 PR diff / Push commits
4. 呼叫 OpenClaw 進行 AI 代碼審查
5. 儲存審查結果到 Redis
6. 發送 Telegram 通知
7. (可選) 建立 Approval 等待人工確認

支援事件:
- pull_request: PR 代碼審查
- push: 主分支推送審查

版本: v1.0
最後修改: 2026-04-05 (台北時區)
修改者: Claude Code (ADR-059 GitHub → Gitea 遷移)
"""

import json
import uuid
from typing import Protocol

import structlog
from pydantic import BaseModel, Field

from src.core.config import settings
from src.core.redis_client import get_redis
from src.utils.timezone import now_taipei_iso

logger = structlog.get_logger(__name__)


# Redis TTL: 7 天
GITEA_REVIEW_TTL_SECONDS = 7 * 24 * 60 * 60


# =============================================================================
# Result Models (Service 層數據契約)
# =============================================================================

class CodeReviewResult(BaseModel):
    """AI 代碼審查結果"""
    summary: str = Field(..., description="審查摘要")
    issues: list[dict] = Field(default=[], description="發現的問題列表")
    suggestions: list[dict] = Field(default=[], description="改進建議")
    security_concerns: list[str] = Field(default=[], description="安全疑慮")
    quality_score: float = Field(..., ge=0, le=100, description="代碼品質分數 0-100")
    analyzed_by: str = Field(..., description="分析模型 (ollama/claude)")
    confidence: float = Field(..., ge=0, le=1, description="分析信心度 0-1")



# =============================================================================
# Repository Interface & Implementation
# =============================================================================

class IGiteaReviewRepository(Protocol):
    """Gitea Review Repository Interface"""

    async def save_review(self, review_id: str, review_data: dict) -> bool:
        """儲存審查結果"""
        ...

    async def get_review(self, review_id: str) -> dict | None:
        """取得審查結果"""
        ...


class GiteaReviewRedisRepository:
    """Redis 實作的 Gitea Review Repository"""

    KEY_PREFIX = "gitea_review:"

    async def save_review(self, review_id: str, review_data: dict) -> bool:
        """
        儲存審查結果到 Redis

        Args:
            review_id: 審查 ID
            review_data: 審查結果資料

        Returns:
            bool: 儲存是否成功
        """
        try:
            redis_client = get_redis()
            key = f"{self.KEY_PREFIX}{review_id}"
            await redis_client.set(
                key,
                json.dumps(review_data, ensure_ascii=False),
                ex=GITEA_REVIEW_TTL_SECONDS,
            )
            logger.debug("gitea_review_saved", review_id=review_id)
            return True
        except Exception as e:
            logger.error("gitea_review_save_failed", review_id=review_id, error=str(e))
            return False

    async def get_review(self, review_id: str) -> dict | None:
        """
        取得審查結果

        Args:
            review_id: 審查 ID

        Returns:
            審查結果資料，或 None
        """
        try:
            redis_client = get_redis()
            key = f"{self.KEY_PREFIX}{review_id}"
            result = await redis_client.get(key)
            if result:
                return json.loads(result)
            return None
        except Exception as e:
            logger.error("gitea_review_get_failed", review_id=review_id, error=str(e))
            return None


# =============================================================================
# Service
# =============================================================================

class GiteaWebhookService:
    """
    Gitea Webhook 服務

    封裝審查結果的儲存、查詢以及全部業務協調流程:
    - PR 代碼審查 (review_pull_request)
    - Push 代碼審查 (review_push)
    - OpenClaw 呼叫封裝
    - Telegram 通知
    - Approval 建立
    """

    def __init__(self, repository: IGiteaReviewRepository | None = None):
        self._repository = repository or GiteaReviewRedisRepository()

    # ------------------------------------------------------------------
    # Redis CRUD
    # ------------------------------------------------------------------

    async def save_review_result(
        self,
        review_id: str,
        review_data: dict,
        ttl: int | None = None,
    ) -> bool:
        """儲存審查結果 (支援自訂 TTL)"""
        if ttl is not None and ttl != GITEA_REVIEW_TTL_SECONDS:
            # 直接寫 Redis 以使用自訂 TTL
            try:
                redis_client = get_redis()
                key = f"{self._repository.KEY_PREFIX}{review_id}"  # type: ignore[attr-defined]
                await redis_client.set(
                    key,
                    json.dumps(review_data, ensure_ascii=False),
                    ex=ttl,
                )
                logger.debug("gitea_review_saved_custom_ttl", review_id=review_id, ttl=ttl)
                return True
            except Exception as e:
                logger.error("gitea_review_save_failed", review_id=review_id, error=str(e))
                return False
        return await self._repository.save_review(review_id, review_data)

    async def get_review_result(self, review_id: str) -> dict | None:
        """取得審查結果"""
        return await self._repository.get_review(review_id)

    # ------------------------------------------------------------------
    # Internal helpers: OpenClaw
    # ------------------------------------------------------------------

    async def _fetch_pr_diff(self, diff_url: str) -> str:
        """取得 PR diff 內容 (直接 HTTP GET Gitea diff URL)"""
        try:
            import httpx
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.get(diff_url)
                if response.status_code == 200:
                    content = response.text
                    if len(content) > 50000:
                        content = content[:50000] + "\n... (truncated)"
                    return content
                logger.warning("gitea_diff_fetch_failed", url=diff_url, status=response.status_code)
                return ""
        except Exception as e:
            logger.warning("gitea_diff_fetch_error", url=diff_url, error=str(e))
            return ""

    async def _call_openclaw_code_review(
        self,
        repo_name: str,
        pr_title: str,
        pr_body: str,
        diff_content: str,
        changed_files: int,
        additions: int,
        deletions: int,
    ) -> CodeReviewResult | None:
        """
        呼叫 OpenClaw 進行 PR 代碼審查

        優先使用 Ollama (本地，零成本)
        Fallback: Claude (大型 PR)

        Phase 22 P0 修復: 使用 OpenClawHttpService (2026-03-31)
        """
        try:
            from src.services.local_code_review_service import get_local_code_review_service
            svc = get_local_code_review_service()
            pr_id = f"{repo_name}-{pr_title[:20]}"
            result = await svc.review_pr(
                pr_id=pr_id,
                repo=repo_name,
                title=pr_title,
                diff=diff_content,
            )
            if result and isinstance(result, dict):
                review_text = result.get("review_text", "")
                issues_count = result.get("issues_count", 0)
                return CodeReviewResult(
                    summary=review_text[:300] if review_text else "PR review completed",
                    issues=[{"text": line} for line in review_text.split("\n") if "⚠️" in line],
                    suggestions=[],
                    security_concerns=[],
                    quality_score=max(0.0, 100.0 - issues_count * 15),
                    analyzed_by=result.get("model", "ollama"),
                    confidence=0.7,
                )
            return None

        except Exception as e:
            logger.exception("openclaw_code_review_error", error=str(e))
            return None

    async def _call_openclaw_push_review(
        self,
        repo_name: str,
        ref: str,
        commits: list[dict],
        files_changed: dict,
    ) -> CodeReviewResult | None:
        """
        Phase 32 ADR-067: Ollama qwen2.5-coder:7b push 代碼審查
        2026-04-10 Claude Sonnet 4.6: OpenClaw 無 push-review 端點 → 改用 Ollama 本地推理
        """
        try:
            from src.services.local_code_review_service import get_local_code_review_service
            svc = get_local_code_review_service()

            branch = ref.split("/")[-1]
            commit_msgs = "\n".join(f'- {c["sha"]}: {c["message"]}' for c in commits[:5])
            added = files_changed.get("added", [])
            modified = files_changed.get("modified", [])
            removed = files_changed.get("removed", [])
            files_summary = (
                f"Added: {', '.join(added[:5]) or '無'}\n"
                f"Modified: {', '.join(modified[:5]) or '無'}\n"
                f"Removed: {', '.join(removed[:5]) or '無'}"
            )

            result = await svc.review_push(
                repo_name=repo_name,
                branch=branch,
                commit_summary=commit_msgs,
                files_summary=files_summary,
            )
            # local_code_review_service 回傳 dict，需轉成 CodeReviewResult
            if result and isinstance(result, dict):
                issues_count = result.get("issues_count", 0)
                review_text = result.get("review_text", "")
                return CodeReviewResult(
                    summary=review_text[:300] if review_text else "Push review completed",
                    issues=[{"text": line} for line in review_text.split("\n") if "⚠️" in line],
                    suggestions=[],
                    security_concerns=[],
                    quality_score=max(0.0, 100.0 - issues_count * 15),
                    analyzed_by=result.get("model", "ollama"),
                    confidence=0.7,
                )
            return result  # type: ignore[return-value]  # None case

        except Exception as e:
            logger.exception("openclaw_push_review_error", error=str(e))
            return None

    # ------------------------------------------------------------------
    # Internal helpers: persist
    # ------------------------------------------------------------------

    async def _save_review_with_analysis(
        self,
        review_id: str,
        event_type: str,
        repo: str,
        target: str,
        analysis: CodeReviewResult | None,
        metadata: dict,
    ) -> None:
        """
        組裝並儲存代碼審查結果到 Redis (透過 Service)

        Key: gitea_review:{review_id}
        TTL: 7 天
        """
        result = {
            "review_id": review_id,
            "event_type": event_type,
            "repo": repo,
            "target": target,
            "created_at": now_taipei_iso(),
            # 2026-04-12 ogt: _call_openclaw_push_review 回傳 dict，無 model_dump()
            "analysis": analysis.model_dump() if hasattr(analysis, "model_dump") else analysis,
            "metadata": metadata,
        }

        success = await self._repository.save_review(review_id, result)

        if success:
            logger.info("gitea_review_saved", review_id=review_id, ttl_days=7)
        else:
            logger.error("gitea_review_save_failed", review_id=review_id)

    # ------------------------------------------------------------------
    # Internal helpers: Telegram
    # ------------------------------------------------------------------

    async def _send_gitea_telegram_alert(
        self,
        review_id: str,
        event_type: str,
        repo: str,
        target: str,
        url: str,
        author: str,
        analysis: CodeReviewResult | None,
    ) -> None:
        """
        發送 Gitea 審查告警到 Telegram

        格式:
        ═══════════════════════════
        🔍 GITEA CODE REVIEW
        ═══════════════════════════
        📦 repo/name
        🔀 PR #123: Feature title
        👤 @author
        ───────────────────────────
        📊 品質分數: 85/100
        ⚠️ 發現 2 個問題
        🔐 1 個安全疑慮
        ───────────────────────────
        🧠 AI 摘要:
        「代碼品質良好，但建議...」
        ───────────────────────────
        [ 🔗 查看 PR ] [ 📋 詳情 ]
        """
        try:
            from src.services.telegram_gateway import get_telegram_gateway
            telegram = get_telegram_gateway()

            # 檢查是否有設定 Bot Token
            if not settings.OPENCLAW_TG_BOT_TOKEN:
                logger.debug("gitea_telegram_skipped", reason="Bot token not configured")
                return

            await telegram.initialize()

            # 構建訊息
            quality_emoji = (
                "🟢" if analysis and analysis.quality_score >= 80
                else "🟡" if analysis and analysis.quality_score >= 60
                else "🔴"
            )

            # 2026-04-15 ogt: 改為 ADR-075 TYPE-1 格式（禁止 ═══ raw 格式）
            message_lines = [
                f"{quality_emoji} <b>TYPE-1 | Gitea Code Review</b>",
                "──────────────────────",
                f"├─ 倉庫: <code>{repo}</code>",
                f"├─ 目標: <code>{target}</code>",
                f"├─ 作者: @{author}",
            ]

            if analysis:
                message_lines.append(f"├─ 品質分數: {analysis.quality_score:.0f}/100")
                if analysis.issues:
                    message_lines.append(f"├─ 問題: {len(analysis.issues)} 個")
                if analysis.security_concerns:
                    message_lines.append(f"├─ 安全疑慮: {len(analysis.security_concerns)} 個")
                message_lines.append(f"├─ AI 摘要: 「{analysis.summary[:150]}」")
            else:
                message_lines.append("├─ AI 分析失敗")

            message_lines.extend([
                f"└─ <a href=\"{url}\">Review #{review_id}</a>",
            ])

            message = "\n".join(message_lines)

            await telegram.send_alert_notification(message)

            logger.info("gitea_telegram_sent", review_id=review_id, repo=repo, event_type=event_type)

        except Exception as e:
            logger.exception("gitea_telegram_failed", error=str(e))

    # ------------------------------------------------------------------
    # Internal helpers: Approval
    # ------------------------------------------------------------------

    async def _create_gitea_approval(
        self,
        review_id: str,
        repo: str,
        target: str,
        url: str,
        analysis: CodeReviewResult,
    ) -> str:
        """
        為有安全疑慮的 PR 建立 Approval 記錄

        Returns:
            str: Approval ID
        """
        try:
            from src.models.approval import (
                ApprovalRequestCreate,
                BlastRadius,
                DataImpact,
                RiskLevel,
            )
            from src.services.approval_db import get_approval_service
            approval_service = get_approval_service()

            # 決定風險等級
            if len(analysis.security_concerns) > 2 or analysis.quality_score < 50:
                risk_level = RiskLevel.CRITICAL
            elif analysis.security_concerns or analysis.quality_score < 70:
                risk_level = RiskLevel.HIGH
            else:
                risk_level = RiskLevel.MEDIUM

            # P1-2 修正: 欄位對齊 ApprovalRequestBase (2026-03-29)
            root_cause = f"Code review found security concerns in {target}"
            suggestion = f"Review {len(analysis.security_concerns)} security concern(s): {', '.join(analysis.security_concerns[:3])}"
            approval_request = ApprovalRequestCreate(
                action=f"Code Review Security: {repo}",
                description=f"Root Cause: {root_cause}\nSuggestion: {suggestion}",
                risk_level=risk_level,
                blast_radius=BlastRadius(
                    affected_pods=1,
                    estimated_downtime="0",
                    related_services=[repo],
                    data_impact=DataImpact.READ_ONLY,
                ),
                dry_run_checks=[],
                requested_by="gitea-webhook",
                metadata={
                    "source": "gitea",
                    "alert_type": "code_review_security",
                    "target_resource": repo,
                    "namespace": "gitea",
                    "gitea_review_id": review_id,
                    "target": target,
                    "url": url,
                    "quality_score": analysis.quality_score,
                    "security_concerns": analysis.security_concerns,
                    "issues_count": len(analysis.issues),
                    "llm_provider": analysis.analyzed_by,
                    "llm_confidence": analysis.confidence,
                },
            )

            # 創建 Approval
            approval_id = str(uuid.uuid4())
            await approval_service.create_approval(
                approval_id=approval_id,
                request=approval_request,
            )

            logger.info(
                "gitea_approval_created",
                approval_id=approval_id,
                review_id=review_id,
                risk_level=risk_level.value,
            )

            return approval_id

        except Exception as e:
            logger.exception("gitea_approval_creation_failed", error=str(e))
            return f"temp-{uuid.uuid4().hex[:8]}"

    # ------------------------------------------------------------------
    # Public: Orchestration (Background Tasks)
    # ------------------------------------------------------------------

    async def review_pull_request(
        self,
        repo,          # GiteaRepository
        pr,            # GiteaPullRequest
        sender,        # GiteaUser
        review_id: str,
        action: str,
    ) -> None:
        """
        背景任務: PR 代碼審查

        1. 取得 PR diff
        2. 呼叫 OpenClaw 分析
        3. 儲存結果到 Redis
        4. 發送 Telegram 通知
        5. 建立 Approval (可選)
        """
        try:
            logger.info(
                "gitea_pr_review_started",
                review_id=review_id,
                repo=repo.full_name,
                pr_number=pr.number,
                sender=sender.login,
            )

            # 1. 取得 PR diff
            diff_content = await self._fetch_pr_diff(pr.diff_url)

            # 2. 呼叫 OpenClaw 進行代碼審查
            analysis = await self._call_openclaw_code_review(
                repo_name=repo.full_name,
                pr_title=pr.title,
                pr_body=pr.body or "",
                diff_content=diff_content,
                changed_files=pr.changed_files,
                additions=pr.additions,
                deletions=pr.deletions,
            )

            # 3. 儲存結果到 Redis
            await self._save_review_with_analysis(
                review_id=review_id,
                event_type="pull_request",
                repo=repo.full_name,
                target=f"PR #{pr.number}",
                analysis=analysis,
                metadata={
                    "pr_number": pr.number,
                    "pr_title": pr.title,
                    "pr_url": pr.html_url,
                    "author": pr.user.login,
                    "action": action,
                    "changed_files": pr.changed_files,
                    "additions": pr.additions,
                    "deletions": pr.deletions,
                },
            )

            # 4. 發送 Telegram 通知
            await self._send_gitea_telegram_alert(
                review_id=review_id,
                event_type="pull_request",
                repo=repo.full_name,
                target=f"PR #{pr.number}: {pr.title[:50]}",
                url=pr.html_url,
                author=pr.user.login,
                analysis=analysis,
            )

            # 5. 如果有安全疑慮，建立 Approval
            if analysis and analysis.security_concerns:
                await self._create_gitea_approval(
                    review_id=review_id,
                    repo=repo.full_name,
                    target=f"PR #{pr.number}",
                    url=pr.html_url,
                    analysis=analysis,
                )

            logger.info(
                "gitea_pr_review_completed",
                review_id=review_id,
                quality_score=analysis.quality_score if analysis else None,
                has_security_concerns=bool(analysis and analysis.security_concerns),
            )

        except Exception as e:
            logger.exception(
                "gitea_pr_review_failed",
                review_id=review_id,
                error=str(e),
            )

    async def review_push(
        self,
        repo,          # GiteaRepository
        commits: list, # list[GiteaCommit]
        sender,        # GiteaUser
        review_id: str,
        ref: str,
        before_sha: str | None,
        after_sha: str | None,
    ) -> None:
        """
        背景任務: Push 代碼審查

        1. 整理 commit 資訊
        2. 呼叫 OpenClaw 分析
        3. 儲存結果到 Redis
        4. 發送 Telegram 通知 (只有發現問題時才通知)
        """
        try:
            logger.info(
                "gitea_push_review_started",
                review_id=review_id,
                repo=repo.full_name,
                commit_count=len(commits),
            )

            # 1. 整理 commit 資訊
            commit_summary = []
            all_files: dict[str, list] = {"added": [], "modified": [], "removed": []}
            for commit in commits:
                commit_summary.append({
                    "sha": commit.id[:8],
                    "message": commit.message[:100],
                    "author": commit.author.get("name", "unknown"),
                })
                all_files["added"].extend(commit.added)
                all_files["modified"].extend(commit.modified)
                all_files["removed"].extend(commit.removed)

            # 2. 呼叫 OpenClaw 進行代碼審查 (Push 版)
            analysis = await self._call_openclaw_push_review(
                repo_name=repo.full_name,
                ref=ref,
                commits=commit_summary,
                files_changed=all_files,
            )

            # 3. 儲存結果到 Redis
            await self._save_review_with_analysis(
                review_id=review_id,
                event_type="push",
                repo=repo.full_name,
                target=f"push to {ref.split('/')[-1]}",
                analysis=analysis,
                metadata={
                    "ref": ref,
                    "before_sha": before_sha,
                    "after_sha": after_sha,
                    "commit_count": len(commits),
                    "pusher": sender.login,
                    "files": all_files,
                },
            )

            # 4. 發送 Telegram 通知 (只有發現問題時才通知)
            if analysis and (
                analysis.issues
                or analysis.security_concerns
                or analysis.quality_score < 70
            ):
                await self._send_gitea_telegram_alert(
                    review_id=review_id,
                    event_type="push",
                    repo=repo.full_name,
                    target=f"push to {ref.split('/')[-1]} ({len(commits)} commits)",
                    url=repo.html_url,
                    author=sender.login,
                    analysis=analysis,
                )

            logger.info(
                "gitea_push_review_completed",
                review_id=review_id,
                quality_score=analysis.quality_score if analysis else None,
            )

        except Exception as e:
            logger.exception(
                "gitea_push_review_failed",
                review_id=review_id,
                error=str(e),
            )



# =============================================================================
# Singleton
# =============================================================================

# 單例
_service: GiteaWebhookService | None = None


def get_gitea_webhook_service() -> GiteaWebhookService:
    """取得 GiteaWebhookService 單例"""
    global _service
    if _service is None:
        _service = GiteaWebhookService()
    return _service
