"""
AI Governance REST API — /governance 頁面後端
============================================
PR 1：3 個 GET endpoint，供前端 /governance 頁面使用。

Endpoints:
  GET /api/v1/ai/governance/events   — ai_governance_events 查詢（分頁 + 多維度過濾）
  GET /api/v1/ai/governance/queue    — remediation dispatch 隊列（graceful fallback）
  GET /api/v1/ai/governance/summary  — 30d SLO 違反時序 + compliance_rate

設計原則:
- Router 層只負責 HTTP 路由，業務邏輯/DB 查詢在 governance_query_service
- Pydantic V2 response models（src/models/governance.py）
- queue endpoint 在 dispatch 表尚未建立時回 table_pending=True，不拋 500

2026-05-02 ogt + Claude Sonnet 4.6 Asia/Taipei
"""

from __future__ import annotations

from datetime import datetime
from typing import Annotated

import structlog
from fastapi import APIRouter, HTTPException, Query

from src.models.governance import (
    GovernanceEventsResponse,
    GovernanceQueueResponse,
    GovernanceSummaryResponse,
    KnowledgeReviewDraftArchiveRequest,
    KnowledgeReviewDraftArchiveResponse,
    KnowledgeReviewDraftDedupeResponse,
    KnowledgeStaleCandidatesResponse,
    KnowledgeStaleOwnerReviewBatchQueueRequest,
    KnowledgeStaleOwnerReviewBatchQueueResponse,
    KnowledgeStaleOwnerReviewBurnDownResponse,
    KnowledgeStaleOwnerReviewCompleteRequest,
    KnowledgeStaleOwnerReviewCompleteResponse,
    KnowledgeStaleOwnerReviewCompletionBatchPreviewRequest,
    KnowledgeStaleOwnerReviewCompletionBatchPreviewResponse,
    KnowledgeStaleOwnerReviewCompletionQueueResponse,
    KnowledgeStaleOwnerReviewInboxResponse,
    KnowledgeStaleOwnerReviewRequest,
    KnowledgeStaleOwnerReviewResponse,
)
from src.services.governance_km_review_service import (
    KmReviewDraftArchiveError,
    archive_km_review_draft_duplicates,
)
from src.services.governance_km_stale_review_service import (
    KmStaleOwnerReviewError,
    batch_queue_km_stale_owner_reviews,
    complete_km_stale_owner_review,
    preview_km_stale_owner_review_completion_batch,
    query_km_stale_owner_review_burndown,
    query_km_stale_owner_review_completion_queue,
    query_km_stale_owner_review_inbox,
    queue_km_stale_owner_review,
)
from src.services.governance_query_service import (
    query_governance_events,
    query_governance_queue,
    query_governance_summary,
    query_km_review_draft_dedupe,
    query_km_stale_candidates,
)

logger = structlog.get_logger(__name__)

router = APIRouter()


# =============================================================================
# GET /api/v1/ai/governance/events
# =============================================================================

@router.get("/ai/governance/events", response_model=GovernanceEventsResponse)
async def get_governance_events(
    event_id: Annotated[list[str] | None, Query(alias="event_id")] = None,
    event_type: Annotated[list[str] | None, Query(alias="event_type")] = None,
    from_: Annotated[datetime | None, Query(alias="from")] = None,
    to: Annotated[datetime | None, Query(alias="to")] = None,
    status: Annotated[str | None, Query(pattern="^(resolved|unresolved)$")] = None,
    severity: Annotated[str | None, Query(pattern="^(critical|warning|info)$")] = None,
    page: Annotated[int, Query(ge=1)] = 1,
    size: Annotated[int, Query(ge=10, le=100)] = 20,
) -> GovernanceEventsResponse:
    """
    查詢 AI 治理事件列表（分頁）。

    - event_type: 多值過濾（可重複傳）
    - event_id: 多值精準過濾（可重複傳），供 Telegram 詳情 / 歷史與 Work Items 錨點回看
    - from / to: ISO 8601 時間範圍（URL 傳 from 參數）
    - status: resolved / unresolved
    - severity: critical / warning / info（由 event_type 映射決定）
    - page: ≥1，default 1
    - size: 10-100，default 20
    """
    logger.debug(
        "governance_events_request",
        event_ids=event_id,
        event_types=event_type,
        from_=from_,
        to=to,
        status=status,
        severity=severity,
        page=page,
        size=size,
    )
    return await query_governance_events(
        event_ids=event_id,
        event_types=event_type,
        from_dt=from_,
        to_dt=to,
        status=status,
        severity=severity,
        page=page,
        size=size,
    )


# =============================================================================
# GET /api/v1/ai/governance/queue
# =============================================================================

@router.get("/ai/governance/queue", response_model=GovernanceQueueResponse)
async def get_governance_queue(
    dispatch_status: Annotated[
        str,
        Query(pattern="^(all|pending|dispatched|executing|succeeded|failed|skipped|cancelled)$"),
    ] = "pending",
    event_type: Annotated[list[str] | None, Query(alias="event_type")] = None,
    page: Annotated[int, Query(ge=1)] = 1,
    size: Annotated[int, Query(ge=10, le=100)] = 20,
) -> GovernanceQueueResponse:
    """
    查詢 remediation dispatch 隊列。

    governance_remediation_dispatch 表由 Track D 建立，尚未完成時
    本 endpoint 回傳 { table_pending: true, items: [], total: 0 }，不拋 500。

    - dispatch_status: pending（default）/ dispatched / executing / succeeded / failed / skipped / cancelled / all
    - event_type: 多值過濾（可重複傳）
    - page / size: 分頁
    """
    logger.debug(
        "governance_queue_request",
        dispatch_status=dispatch_status,
        event_type=event_type,
        page=page,
        size=size,
    )
    return await query_governance_queue(
        dispatch_status=dispatch_status,
        event_types=event_type,
        page=page,
        size=size,
    )


# =============================================================================
# GET /api/v1/ai/governance/km-review-drafts/dedupe
# =============================================================================

@router.get(
    "/ai/governance/km-review-drafts/dedupe",
    response_model=KnowledgeReviewDraftDedupeResponse,
)
async def get_km_review_draft_dedupe(
    limit: Annotated[int, Query(ge=10, le=200)] = 100,
) -> KnowledgeReviewDraftDedupeResponse:
    """
    查詢 Hermes KM healthcheck review drafts 的去重 read model。

    這是 read-only owner review surface：只回傳 canonical / duplicate /
    owner_action，不自動 archive、不自動 approve/publish KM。
    """
    logger.debug("km_review_draft_dedupe_request", limit=limit)
    return await query_km_review_draft_dedupe(limit=limit)


# =============================================================================
# POST /api/v1/ai/governance/km-review-drafts/dedupe/{event_id}/archive-duplicates
# =============================================================================

@router.post(
    "/ai/governance/km-review-drafts/dedupe/{governance_event_id}/archive-duplicates",
    response_model=KnowledgeReviewDraftArchiveResponse,
)
async def post_km_review_draft_archive_duplicates(
    governance_event_id: str,
    request: KnowledgeReviewDraftArchiveRequest,
) -> KnowledgeReviewDraftArchiveResponse:
    """
    Owner 審核後封存 Hermes KM healthcheck duplicate review drafts。

    這不是 read endpoint：必須明確傳 owner_approved=true，且後端會重新比對
    最新 dedupe plan。封存為 KnowledgeEntry.status=archived，不刪除資料。
    """
    logger.info(
        "km_review_draft_archive_request",
        governance_event_id=governance_event_id,
        canonical_entry_id=request.canonical_entry_id,
        duplicate_count=len(request.duplicate_entry_ids),
        owner=request.owner,
        dry_run=request.dry_run,
        owner_approved=request.owner_approved,
    )
    try:
        return await archive_km_review_draft_duplicates(
            governance_event_id=governance_event_id,
            request=request,
        )
    except KmReviewDraftArchiveError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# GET /api/v1/ai/governance/km-stale-candidates
# =============================================================================

@router.get(
    "/ai/governance/km-stale-candidates",
    response_model=KnowledgeStaleCandidatesResponse,
)
async def get_km_stale_candidates(
    project_id: Annotated[str, Query(min_length=1, max_length=64)] = "awoooi",
    limit: Annotated[int, Query(ge=5, le=100)] = 20,
) -> KnowledgeStaleCandidatesResponse:
    """
    查詢 stale KM 的 read-only 優先處理清單。

    Hermes 可以用這個 read model 產生 KM 更新草稿；owner console 則能先看
    哪些條目有 Incident / Sentry / SigNoz / PlayBook 脈絡，避免只看到總數。
    """
    logger.debug(
        "km_stale_candidates_request",
        project_id=project_id,
        limit=limit,
    )
    return await query_km_stale_candidates(project_id=project_id, limit=limit)


# =============================================================================
# GET /api/v1/ai/governance/km-stale-owner-reviews
# =============================================================================

@router.get(
    "/ai/governance/km-stale-owner-reviews",
    response_model=KnowledgeStaleOwnerReviewInboxResponse,
)
async def get_km_stale_owner_reviews(
    project_id: Annotated[str, Query(min_length=1, max_length=64)] = "awoooi",
    dispatch_status: Annotated[
        str,
        Query(pattern="^(all|pending|dispatched|executing|succeeded|failed|skipped|cancelled)$"),
    ] = "pending",
    limit: Annotated[int, Query(ge=5, le=100)] = 20,
) -> KnowledgeStaleOwnerReviewInboxResponse:
    """
    查詢 stale KM owner-review 工作台。

    這是 read-only inbox：把 dispatch trail 與 KM priority context 合併，
    讓 operator 可以依 P0/P1、score、batch 來源與流程階段逐筆 completion。
    """
    logger.debug(
        "km_stale_owner_reviews_request",
        project_id=project_id,
        dispatch_status=dispatch_status,
        limit=limit,
    )
    try:
        return await query_km_stale_owner_review_inbox(
            project_id=project_id,
            dispatch_status=dispatch_status,
            limit=limit,
        )
    except KmStaleOwnerReviewError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# GET /api/v1/ai/governance/km-stale-owner-review-burndown
# =============================================================================

@router.get(
    "/ai/governance/km-stale-owner-review-burndown",
    response_model=KnowledgeStaleOwnerReviewBurnDownResponse,
)
async def get_km_stale_owner_review_burndown(
    project_id: Annotated[str, Query(min_length=1, max_length=64)] = "awoooi",
    limit: Annotated[int, Query(ge=1, le=100)] = 20,
) -> KnowledgeStaleOwnerReviewBurnDownResponse:
    """
    查詢 stale KM owner-review 完成與 stale ratio burn-down 狀態。

    這是 read-only dashboard：把 pending review、completion audit、recheck
    snapshot 與距離治理門檻的剩餘筆數放在同一個前端面板。
    """
    logger.debug(
        "km_stale_owner_review_burndown_request",
        project_id=project_id,
        limit=limit,
    )
    return await query_km_stale_owner_review_burndown(
        project_id=project_id,
        limit=limit,
    )


# =============================================================================
# GET /api/v1/ai/governance/km-stale-owner-review-completion-queue
# =============================================================================

@router.get(
    "/ai/governance/km-stale-owner-review-completion-queue",
    response_model=KnowledgeStaleOwnerReviewCompletionQueueResponse,
)
async def get_km_stale_owner_review_completion_queue(
    project_id: Annotated[str, Query(min_length=1, max_length=64)] = "awoooi",
    status_bucket: Annotated[
        str,
        Query(pattern="^(all|ready|blocked|completed|failed|pending)$"),
    ] = "all",
    priority_tier: Annotated[list[str] | None, Query(alias="priority_tier")] = None,
    recommended_completion_outcome: Annotated[
        str,
        Query(pattern="^(all|refresh_with_evidence|archive|supersede)$"),
    ] = "all",
    batch_governance_event_id: Annotated[str | None, Query(max_length=120)] = None,
    can_preview: bool | None = None,
    limit: Annotated[int, Query(ge=1, le=100)] = 20,
) -> KnowledgeStaleOwnerReviewCompletionQueueResponse:
    """
    查詢 stale KM owner-review completion 分流。

    這是 read-only queue：把 active / completed / failed dispatch 拆成
    ready、blocked、completed、failed，讓前端呈現下一步卡點；打開頁面不寫 KM。
    """
    logger.debug(
        "km_stale_owner_review_completion_queue_request",
        project_id=project_id,
        status_bucket=status_bucket,
        priority_tiers=priority_tier,
        recommended_completion_outcome=recommended_completion_outcome,
        batch_governance_event_id=batch_governance_event_id,
        can_preview=can_preview,
        limit=limit,
    )
    try:
        return await query_km_stale_owner_review_completion_queue(
            project_id=project_id,
            status_bucket=status_bucket,
            priority_tiers=priority_tier,
            recommended_completion_outcome=recommended_completion_outcome,
            batch_governance_event_id=batch_governance_event_id,
            can_preview=can_preview,
            limit=limit,
        )
    except KmStaleOwnerReviewError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# POST /api/v1/ai/governance/km-stale-owner-review-completion-queue/batch-preview
# =============================================================================

@router.post(
    "/ai/governance/km-stale-owner-review-completion-queue/batch-preview",
    response_model=KnowledgeStaleOwnerReviewCompletionBatchPreviewResponse,
)
async def post_km_stale_owner_review_completion_batch_preview(
    request: KnowledgeStaleOwnerReviewCompletionBatchPreviewRequest,
) -> KnowledgeStaleOwnerReviewCompletionBatchPreviewResponse:
    """
    Preview a bounded set of owner-review completion candidates.

    This endpoint is intentionally dry-run only: it does not write KM, does not
    enqueue a batch executor, and does not create governance audit rows. Each
    item must still be completed through the single-item dry-run + owner confirm
    endpoint.
    """
    logger.info(
        "km_stale_owner_review_completion_batch_preview_request",
        project_id=request.project_id,
        status_bucket=request.status_bucket,
        priority_tiers=request.priority_tiers,
        recommended_completion_outcome=request.recommended_completion_outcome,
        batch_governance_event_id=request.batch_governance_event_id,
        limit=request.limit,
        owner=request.owner,
    )
    try:
        return await preview_km_stale_owner_review_completion_batch(request=request)
    except KmStaleOwnerReviewError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# POST /api/v1/ai/governance/km-stale-candidates/batch-queue-review
# =============================================================================

@router.post(
    "/ai/governance/km-stale-candidates/batch-queue-review",
    response_model=KnowledgeStaleOwnerReviewBatchQueueResponse,
)
async def post_km_stale_candidate_batch_queue_review(
    request: KnowledgeStaleOwnerReviewBatchQueueRequest,
) -> KnowledgeStaleOwnerReviewBatchQueueResponse:
    """
    將 P0/P1 stale KM 批次排入 owner review。

    這個 endpoint 只建立 batch audit 與逐筆 owner-review dispatch，不改寫 KM。
    真正 refresh / archive / supersede 仍需單筆 dry-run fingerprint + owner approval。
    """
    logger.info(
        "km_stale_candidate_batch_queue_review_request",
        project_id=request.project_id,
        priority_tiers=request.priority_tiers,
        limit=request.limit,
        owner=request.owner,
        dry_run=request.dry_run,
    )
    try:
        return await batch_queue_km_stale_owner_reviews(request=request)
    except KmStaleOwnerReviewError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# POST /api/v1/ai/governance/km-stale-candidates/{entry_id}/queue-review
# =============================================================================

@router.post(
    "/ai/governance/km-stale-candidates/{entry_id}/queue-review",
    response_model=KnowledgeStaleOwnerReviewResponse,
)
async def post_km_stale_candidate_queue_review(
    entry_id: str,
    request: KnowledgeStaleOwnerReviewRequest,
) -> KnowledgeStaleOwnerReviewResponse:
    """
    將單筆 stale KM candidate 排入 owner review。

    這個 endpoint 只建立治理事件與 dispatch work item，不修改 KM 內容。
    實際 refresh / archive / supersede 仍需 owner 在後續流程確認。
    """
    logger.info(
        "km_stale_candidate_queue_review_request",
        entry_id=entry_id,
        owner=request.owner,
        dry_run=request.dry_run,
    )
    try:
        return await queue_km_stale_owner_review(entry_id=entry_id, request=request)
    except KmStaleOwnerReviewError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# POST /api/v1/ai/governance/km-stale-candidates/{entry_id}/complete-review
# =============================================================================

@router.post(
    "/ai/governance/km-stale-candidates/{entry_id}/complete-review",
    response_model=KnowledgeStaleOwnerReviewCompleteResponse,
)
async def post_km_stale_candidate_complete_review(
    entry_id: str,
    request: KnowledgeStaleOwnerReviewCompleteRequest,
) -> KnowledgeStaleOwnerReviewCompleteResponse:
    """
    Owner 審核後完成 stale KM 的 refresh / archive / supersede 流程。

    必須先 dry-run 取得 fingerprint；真正寫入時需 owner_approved=true。
    後端會寫 KM、terminal audit dispatch 與 stale ratio recheck dispatch。
    """
    logger.info(
        "km_stale_candidate_complete_review_request",
        entry_id=entry_id,
        dispatch_id=request.dispatch_id,
        owner=request.owner,
        review_outcome=request.review_outcome,
        dry_run=request.dry_run,
        owner_approved=request.owner_approved,
    )
    try:
        return await complete_km_stale_owner_review(
            entry_id=entry_id,
            request=request,
        )
    except KmStaleOwnerReviewError as exc:
        raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc


# =============================================================================
# GET /api/v1/ai/governance/summary
# =============================================================================

@router.get("/ai/governance/summary", response_model=GovernanceSummaryResponse)
async def get_governance_summary(
    days: Annotated[int, Query(ge=1, le=90)] = 30,
) -> GovernanceSummaryResponse:
    """
    SLO 合規統計摘要（給 /governance SLO tab 使用）。

    - days: 統計天數（1-90，default 30）
    - compliance_rate: 1 - unresolved_count / total_events（total=0 時回 1.0）
    - daily_counts: 每日分類計數時序
    """
    logger.debug("governance_summary_request", days=days)
    return await query_governance_summary(days=days)
