"""
AWOOOI API - GitHub Webhook Handler
====================================
Phase 13.1: GitHub PR/Push/CI → OpenClaw AI 整合

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

支援事件:
- pull_request: PR 代碼審查 (#74-75)
- push: 主分支推送審查 (#74-75)
- workflow_run: CI 失敗診斷 (#76)

安全要求 (feedback_openclaw_security.md):
- HMAC 簽章驗證 (X-Hub-Signature-256)
- Webhook Secret 存放於 K8s Secret
- Rate limiting 防止 DoS
- 倉庫白名單驗證

🔴 HARD RULE: 時間顯示使用 Asia/Taipei (UTC+8)

版本: v2.1
最後修改: 2026-04-01 11:00 (台北時區)
修改者: Claude Code
變更: 協調函數移至 Service 層 (leWOOOgo ADR-024)
"""

import hashlib
import hmac
import json
import uuid
from typing import Literal

from fastapi import APIRouter, BackgroundTasks, Header, HTTPException, Request, status
from pydantic import BaseModel

from src.core.config import settings
from src.core.logging import get_logger
from src.services.github_webhook_service import get_github_webhook_service

logger = get_logger("awoooi.github_webhook")

router = APIRouter(prefix="/webhooks/github", tags=["GitHub Webhook"])

# =============================================================================
# Constants
# =============================================================================

# OpenClaw 配置 (使用 settings 中的 OPENCLAW_URL)
OPENCLAW_URL = settings.OPENCLAW_URL

# GitHub Review 結果 Redis TTL: 7 天 (秒)
GITHUB_REVIEW_TTL_SECONDS = 7 * 24 * 60 * 60


# =============================================================================
# Pydantic Models
# =============================================================================

class GitHubUser(BaseModel):
    """GitHub 使用者"""
    login: str
    id: int
    avatar_url: str | None = None


class GitHubRepository(BaseModel):
    """GitHub 倉庫"""
    id: int
    name: str
    full_name: str
    private: bool = False
    html_url: str


class GitHubPullRequest(BaseModel):
    """GitHub PR 資訊"""
    id: int
    number: int
    title: str
    body: str | None = None
    state: str  # open, closed
    html_url: str
    diff_url: str
    user: GitHubUser
    head: dict  # head branch info
    base: dict  # base branch info
    additions: int = 0
    deletions: int = 0
    changed_files: int = 0


class GitHubCommit(BaseModel):
    """GitHub Commit 資訊"""
    id: str  # SHA
    message: str
    timestamp: str
    url: str
    author: dict
    added: list[str] = []
    removed: list[str] = []
    modified: list[str] = []


class GitHubWorkflowRun(BaseModel):
    """GitHub Workflow Run 資訊 (Phase 13.1 #76)"""
    id: int
    name: str
    status: str  # queued, in_progress, completed
    conclusion: str | None = None  # success, failure, cancelled, skipped, timed_out
    html_url: str
    run_number: int
    run_attempt: int = 1
    head_sha: str
    head_branch: str | None = None
    event: str  # push, pull_request, schedule, workflow_dispatch
    created_at: str
    updated_at: str
    logs_url: str | None = None  # API URL for logs (requires auth)


class GitHubWorkflowJob(BaseModel):
    """GitHub Workflow Job 資訊"""
    id: int
    name: str
    status: str
    conclusion: str | None = None
    started_at: str | None = None
    completed_at: str | None = None
    steps: list[dict] = []


class GitHubWebhookPayload(BaseModel):
    """GitHub Webhook Payload (通用)"""
    action: str | None = None  # PR: opened, synchronize, etc.
    repository: GitHubRepository
    sender: GitHubUser
    # PR 事件
    pull_request: GitHubPullRequest | None = None
    # Push 事件
    ref: str | None = None  # refs/heads/main
    before: str | None = None  # previous commit SHA
    after: str | None = None  # current commit SHA
    commits: list[GitHubCommit] | None = None
    pusher: dict | None = None
    # Workflow Run 事件 (Phase 13.1 #76)
    workflow_run: GitHubWorkflowRun | None = None
    workflow_job: GitHubWorkflowJob | None = None


class GitHubWebhookResponse(BaseModel):
    """Webhook 回應"""
    status: Literal["accepted", "ignored", "error"]
    message: str
    event_type: str | None = None
    review_id: str | None = None


# =============================================================================
# HMAC Signature Verification (CISO 安全要求)
# =============================================================================

class GitHubSignatureError(Exception):
    """GitHub 簽章驗證失敗"""
    pass


async def verify_github_signature(
    request: Request,
    x_hub_signature_256: str | None,
) -> bool:
    """
    驗證 GitHub Webhook 請求的 HMAC-SHA256 簽章

    CISO 安全要求:
    - 所有 GitHub Webhook 必須攜帶 X-Hub-Signature-256 Header
    - 簽章格式: sha256=<hex_digest>
    - 使用 GITHUB_WEBHOOK_SECRET 進行驗證

    安全鐵律 (Fail-Closed):
    - 生產環境: Secret 未設定 → 直接拒絕
    - 開發環境: 可跳過驗證 (僅供本地測試)

    Args:
        request: FastAPI Request 物件
        x_hub_signature_256: X-Hub-Signature-256 Header 值

    Returns:
        bool: 驗證是否通過

    Raises:
        GitHubSignatureError: 簽章驗證失敗
    """
    # ==========================================================================
    # Fail-Closed 安全策略 (CISO 要求)
    # ==========================================================================
    if not settings.GITHUB_WEBHOOK_SECRET:
        # 生產環境: 強制拒絕 (Fail-Closed)
        if settings.ENVIRONMENT == "prod":
            logger.critical(
                "github_webhook_secret_missing_in_production",
                environment=settings.ENVIRONMENT,
                message="CRITICAL: GITHUB_WEBHOOK_SECRET missing in production!",
            )
            raise GitHubSignatureError(
                "Critical: GITHUB_WEBHOOK_SECRET missing in production environment"
            )

        # 開發環境: 允許跳過 (僅供本地測試)
        logger.warning(
            "github_signature_verification_skipped_dev_only",
            environment=settings.ENVIRONMENT,
            reason="GITHUB_WEBHOOK_SECRET not configured (dev mode only)",
        )
        return True

    # 必須提供簽章
    if not x_hub_signature_256:
        logger.warning("github_signature_missing")
        raise GitHubSignatureError("Missing X-Hub-Signature-256 header")

    # 解析簽章格式
    if not x_hub_signature_256.startswith("sha256="):
        raise GitHubSignatureError("Invalid signature format (expected sha256=...)")

    provided_signature = x_hub_signature_256[7:]  # 移除 "sha256=" 前綴

    # 讀取 Request Body
    body = await request.body()

    # 計算預期簽章
    expected_signature = hmac.new(
        settings.GITHUB_WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()

    # 常數時間比較 (防止計時攻擊)
    if not hmac.compare_digest(provided_signature, expected_signature):
        logger.warning(
            "github_signature_verification_failed",
            provided=provided_signature[:16] + "...",
            expected=expected_signature[:16] + "...",
        )
        raise GitHubSignatureError("Invalid signature")

    logger.info("github_signature_verification_success")
    return True


def verify_allowed_repo(full_name: str) -> bool:
    """
    驗證倉庫是否在白名單中

    Args:
        full_name: 完整倉庫名稱 (owner/repo)

    Returns:
        bool: 是否允許
    """
    allowed_repos = settings.get_github_allowed_repos()

    # 如果白名單為空，開發環境允許所有
    if not allowed_repos:
        if settings.ENVIRONMENT == "prod":
            logger.warning(
                "github_allowed_repos_empty_in_production",
                repo=full_name,
                message="No allowed repos configured in production",
            )
            return False
        # 開發環境: 白名單空 = 允許所有
        logger.debug(
            "github_repo_allowed_dev_mode",
            repo=full_name,
            reason="Empty whitelist in dev mode",
        )
        return True

    # 檢查是否在白名單中
    is_allowed = full_name in allowed_repos
    if not is_allowed:
        logger.warning(
            "github_repo_not_in_whitelist",
            repo=full_name,
            allowed_repos=allowed_repos,
        )
    return is_allowed


# =============================================================================
# Main Webhook Handler
# =============================================================================

@router.post(
    "",
    response_model=GitHubWebhookResponse,
    status_code=status.HTTP_202_ACCEPTED,
    summary="GitHub Webhook 接收端點",
    description="接收 GitHub PR/Push 事件並觸發 AI 代碼審查",
)
async def handle_github_webhook(
    request: Request,
    background_tasks: BackgroundTasks,
    x_github_event: str | None = Header(None, alias="X-GitHub-Event"),
    x_github_delivery: str | None = Header(None, alias="X-GitHub-Delivery"),
    x_hub_signature_256: str | None = Header(None, alias="X-Hub-Signature-256"),
):
    """
    GitHub Webhook Handler

    支援事件:
    - pull_request (opened, synchronize, reopened)
    - push (to default branch)

    處理流程:
    1. 驗證簽章
    2. 驗證倉庫白名單
    3. 解析事件類型
    4. 背景執行 AI 審查 (委派給 GitHubWebhookService)
    """
    try:
        # 1. 驗證 HMAC 簽章
        try:
            await verify_github_signature(request, x_hub_signature_256)
        except GitHubSignatureError as e:
            logger.warning("github_webhook_signature_failed", error=str(e))
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail=str(e),
            ) from e

        # 2. 解析 Payload
        body = await request.body()
        payload_dict = json.loads(body)
        payload = GitHubWebhookPayload(**payload_dict)

        # 3. 驗證倉庫白名單
        if not verify_allowed_repo(payload.repository.full_name):
            return GitHubWebhookResponse(
                status="ignored",
                message=f"Repository {payload.repository.full_name} not in whitelist",
                event_type=x_github_event,
            )

        # 4. 根據事件類型處理
        logger.info(
            "github_webhook_received",
            github_event=x_github_event,
            delivery_id=x_github_delivery,
            repo=payload.repository.full_name,
            sender=payload.sender.login,
        )

        # Pull Request 事件
        if x_github_event == "pull_request":
            return await handle_pull_request(
                payload=payload,
                background_tasks=background_tasks,
                delivery_id=x_github_delivery,
            )

        # Push 事件
        elif x_github_event == "push":
            return await handle_push(
                payload=payload,
                background_tasks=background_tasks,
                delivery_id=x_github_delivery,
            )

        # Workflow Run 事件 (Phase 13.1 #76 CI 失敗診斷)
        elif x_github_event == "workflow_run":
            return await handle_workflow_run(
                payload=payload,
                background_tasks=background_tasks,
                delivery_id=x_github_delivery,
            )

        # Ping 事件 (GitHub 測試連線)
        elif x_github_event == "ping":
            return GitHubWebhookResponse(
                status="accepted",
                message="Pong! Webhook configured successfully.",
                event_type="ping",
            )

        # 其他事件 (忽略)
        else:
            return GitHubWebhookResponse(
                status="ignored",
                message=f"Event type '{x_github_event}' not supported",
                event_type=x_github_event,
            )

    except HTTPException:
        raise
    except Exception as e:
        logger.exception("github_webhook_processing_failed", error=str(e))
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Internal error processing webhook",
        ) from e


# =============================================================================
# Event Handlers (HTTP 層: 解析、驗證、回應 — 業務邏輯在 Service 層)
# =============================================================================

async def handle_pull_request(
    payload: GitHubWebhookPayload,
    background_tasks: BackgroundTasks,
    delivery_id: str | None,  # noqa: ARG001 — reserved for idempotency (future use)
) -> GitHubWebhookResponse:
    """
    處理 Pull Request 事件

    支援 action:
    - opened: 新建 PR
    - synchronize: 推送新 commit 到 PR
    - reopened: 重新開啟 PR
    """
    pr = payload.pull_request
    if not pr:
        return GitHubWebhookResponse(
            status="error",
            message="Missing pull_request data",
            event_type="pull_request",
        )

    # 只處理需要審查的 action
    supported_actions = {"opened", "synchronize", "reopened"}
    if payload.action not in supported_actions:
        return GitHubWebhookResponse(
            status="ignored",
            message=f"PR action '{payload.action}' not supported",
            event_type="pull_request",
        )

    # 生成審查 ID
    review_id = f"gh-pr-{payload.repository.id}-{pr.number}-{uuid.uuid4().hex[:8]}"

    # 背景執行審查 (委派給 Service)
    service = get_github_webhook_service()
    background_tasks.add_task(
        service.review_pull_request,
        repo=payload.repository,
        pr=pr,
        sender=payload.sender,
        review_id=review_id,
        action=payload.action,
    )

    logger.info(
        "github_pr_review_scheduled",
        review_id=review_id,
        repo=payload.repository.full_name,
        pr_number=pr.number,
        pr_title=pr.title[:50],
        action=payload.action,
    )

    return GitHubWebhookResponse(
        status="accepted",
        message=f"PR #{pr.number} review scheduled",
        event_type="pull_request",
        review_id=review_id,
    )


async def handle_push(
    payload: GitHubWebhookPayload,
    background_tasks: BackgroundTasks,
    delivery_id: str | None,  # noqa: ARG001 — reserved for idempotency (future use)
) -> GitHubWebhookResponse:
    """
    處理 Push 事件

    只處理 default branch (main/master) 的 push
    """
    # 檢查是否推送到主分支
    ref = payload.ref or ""
    # 通常是 refs/heads/main 或 refs/heads/master
    if not (ref.endswith("/main") or ref.endswith("/master")):
        return GitHubWebhookResponse(
            status="ignored",
            message=f"Push to non-default branch: {ref}",
            event_type="push",
        )

    commits = payload.commits or []
    if not commits:
        return GitHubWebhookResponse(
            status="ignored",
            message="No commits in push",
            event_type="push",
        )

    # 生成審查 ID
    review_id = f"gh-push-{payload.repository.id}-{payload.after[:8]}-{uuid.uuid4().hex[:8]}"

    # 背景執行審查 (委派給 Service)
    service = get_github_webhook_service()
    background_tasks.add_task(
        service.review_push,
        repo=payload.repository,
        commits=commits,
        sender=payload.sender,
        review_id=review_id,
        ref=ref,
        before_sha=payload.before,
        after_sha=payload.after,
    )

    logger.info(
        "github_push_review_scheduled",
        review_id=review_id,
        repo=payload.repository.full_name,
        ref=ref,
        commit_count=len(commits),
        after_sha=payload.after[:8] if payload.after else None,
    )

    return GitHubWebhookResponse(
        status="accepted",
        message=f"Push with {len(commits)} commit(s) review scheduled",
        event_type="push",
        review_id=review_id,
    )


async def handle_workflow_run(
    payload: GitHubWebhookPayload,
    background_tasks: BackgroundTasks,
    delivery_id: str | None,  # noqa: ARG001 — reserved for idempotency (future use)
) -> GitHubWebhookResponse:
    """
    處理 Workflow Run 事件 (Phase 13.1 #76 CI 失敗診斷)

    只處理 completed + failure 的 workflow run
    """
    workflow_run = payload.workflow_run
    if not workflow_run:
        return GitHubWebhookResponse(
            status="ignored",
            message="No workflow_run in payload",
            event_type="workflow_run",
        )

    # 只處理 completed 狀態
    if workflow_run.status != "completed":
        return GitHubWebhookResponse(
            status="ignored",
            message=f"Workflow status '{workflow_run.status}' not completed",
            event_type="workflow_run",
        )

    # 只處理失敗的 workflow
    if workflow_run.conclusion not in ("failure", "timed_out"):
        return GitHubWebhookResponse(
            status="ignored",
            message=f"Workflow conclusion '{workflow_run.conclusion}' is not failure",
            event_type="workflow_run",
        )

    # 生成診斷 ID
    diagnosis_id = f"gh-ci-{payload.repository.id}-{workflow_run.id}-{uuid.uuid4().hex[:8]}"

    # 背景執行 CI 失敗診斷 (委派給 Service)
    service = get_github_webhook_service()
    background_tasks.add_task(
        service.diagnose_ci_failure,
        repo=payload.repository,
        workflow_run=workflow_run,
        sender=payload.sender,
        diagnosis_id=diagnosis_id,
    )

    logger.info(
        "github_ci_failure_diagnosis_scheduled",
        diagnosis_id=diagnosis_id,
        repo=payload.repository.full_name,
        workflow_name=workflow_run.name,
        workflow_id=workflow_run.id,
        conclusion=workflow_run.conclusion,
        head_sha=workflow_run.head_sha[:8],
    )

    return GitHubWebhookResponse(
        status="accepted",
        message=f"CI failure diagnosis scheduled for '{workflow_run.name}'",
        event_type="workflow_run",
        review_id=diagnosis_id,
    )


# =============================================================================
# Query Endpoints
# =============================================================================

@router.get(
    "/reviews/{review_id}",
    summary="取得審查結果",
    description="根據 review_id 取得 GitHub 代碼審查結果",
)
async def get_review_result(review_id: str):
    """
    取得 GitHub 審查結果 (透過 Service)

    Args:
        review_id: 審查 ID

    Returns:
        dict: 審查結果
    """
    service = get_github_webhook_service()
    result = await service.get_review_result(review_id)

    if not result:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Review {review_id} not found or expired",
        )

    return result
