"""
Config Drift Detection API Router - Phase 25 P2
================================================
GitOps 守門員 HTTP 端點

leWOOOgo 積木化原則:
- Router 層只做 HTTP 轉發
- 不直接存取 Redis/DB
- 業務邏輯委託給 Service 層

版本: v1.0
建立: 2026-04-04 (台北時區)
建立者: Claude Code (Phase 25 P2)
"""

from typing import Literal

from fastapi import APIRouter, BackgroundTasks, HTTPException
from pydantic import BaseModel, Field

from src.core.csrf import CSRFToken  # Phase 20: CSRF Protection
from src.models.drift import (
    DriftListResponse,
    DriftReport,
    DriftScanRequest,
    DriftScanResponse,
    DriftStatus,
)
from src.repositories.drift_repository import get_drift_repository
from src.services.drift_adopt_service import get_drift_adopt_service
from src.services.drift_analyzer import get_drift_analyzer
from src.services.drift_detector import get_drift_detector
from src.services.drift_fingerprint_state_service import (
    DriftFingerprintStateNotFoundError,
    get_drift_fingerprint_state_service,
)
from src.services.drift_interpreter import get_drift_interpreter
from src.services.drift_remediator import get_drift_remediator
from src.utils.timezone import now_taipei

router = APIRouter(prefix="/drift", tags=["drift"])

# 2026-04-09 Claude Sonnet 4.6: B4 drift_reports 持久化 — 改用 DB repository


class DriftFingerprintHandoffRequest(BaseModel):
    """Record-only handoff request for a stable drift fingerprint."""

    report_id: str | None = Field(default=None, min_length=1)
    namespace: str | None = Field(default="awoooi-prod", min_length=1)
    handoff_kind: Literal[
        "open_pr_review",
        "manual_investigation",
        "zero_diff_pr_cleanup",
    ] = "open_pr_review"
    pr_url: str | None = Field(default=None, min_length=1)
    note: str | None = Field(default=None, max_length=500)


class DriftFingerprintRemediationRequest(BaseModel):
    """Record-only remediation request for a stable drift fingerprint."""

    report_id: str | None = Field(default=None, min_length=1)
    namespace: str | None = Field(default="awoooi-prod", min_length=1)
    remediation_kind: Literal[
        "live_env_rollback",
        "git_adopted",
        "git_rollback",
        "zero_diff_pr_cleanup",
        "manual_noop",
    ] = "live_env_rollback"
    remediation_status: Literal[
        "executed_unverified",
        "verified_no_drift",
        "verification_failed",
    ] | None = None
    verification_report_id: str | None = Field(default=None, min_length=1)
    note: str | None = Field(default=None, max_length=1000)
    commands_summary: list[str] = Field(default_factory=list, max_length=12)


@router.post("/scan", response_model=DriftScanResponse, summary="觸發漂移掃描")
async def trigger_drift_scan(
    request: DriftScanRequest,
    background_tasks: BackgroundTasks,
) -> DriftScanResponse:
    """
    觸發 Config Drift 掃描

    - 比對 Git YAML vs K8s 實際狀態
    - Nemotron 分析漂移意圖
    - 高/中嚴重度漂移自動推送 Telegram

    適合由 Gitea CD Webhook 或手動呼叫觸發
    """
    detector = get_drift_detector()
    analyzer = get_drift_analyzer()

    repo = get_drift_repository()
    all_items = []
    last_report: DriftReport | None = None

    for namespace in request.namespaces:
        raw_report = await detector.scan(namespace, triggered_by=request.triggered_by)
        classified_report = analyzer.classify(raw_report)
        all_items.extend(classified_report.items)

        # 持久化到 DB
        await repo.save(classified_report)

        if analyzer.needs_alert(classified_report):
            background_tasks.add_task(_analyze_and_notify, classified_report)
            last_report = classified_report

    # 若多 namespace，彙總第一個 report 的計數
    if last_report:
        return DriftScanResponse(
            report_id=last_report.report_id,
            summary=last_report.summary,
            high_count=last_report.high_count,
            medium_count=last_report.medium_count,
            info_count=last_report.info_count,
            has_critical_drift=last_report.has_critical_drift,
        )

    return DriftScanResponse(
        report_id="no-drift",
        summary="無漂移",
        high_count=0,
        medium_count=0,
        info_count=0,
        has_critical_drift=False,
    )


@router.get("/reports", response_model=DriftListResponse, summary="列出最近漂移報告")
async def list_drift_reports() -> DriftListResponse:
    """列出最近 50 筆漂移報告（倒序）"""
    repo = get_drift_repository()
    items = await repo.list_recent(limit=50)
    return DriftListResponse(items=items, total=len(items))


@router.get("/fingerprints/state", summary="查詢 Config Drift fingerprint 狀態")
async def get_drift_fingerprint_state(
    report_id: str | None = None,
    namespace: str | None = "awoooi-prod",
) -> dict:
    """
    以 stable fingerprint 聚合漂移狀態。

    此 endpoint 只建立 read model：重複次數、PR 狀態、是否零 diff、
    人工交接歷史與下一步。它不修改 drift / incident / auto-repair 狀態。
    """
    svc = get_drift_fingerprint_state_service()
    try:
        return await svc.get_state(report_id=report_id, namespace=namespace)
    except DriftFingerprintStateNotFoundError as exc:
        raise HTTPException(status_code=404, detail="drift_report_not_found") from exc


@router.post("/fingerprints/handoff", summary="記錄 Config Drift fingerprint 交接")
async def record_drift_fingerprint_handoff(
    request: DriftFingerprintHandoffRequest,
) -> dict:
    """
    記錄 stable fingerprint 已轉人工 / PR review 的歷史證據。

    安全邊界：只寫 alert_operation_log / timeline_events，不修改 drift 狀態、
    incident 狀態、自動修復結果，不建立外部 ticket，也不 merge PR。
    """
    svc = get_drift_fingerprint_state_service()
    try:
        return await svc.record_handoff(
            report_id=request.report_id,
            namespace=request.namespace,
            handoff_kind=request.handoff_kind,
            pr_url=request.pr_url,
            note=request.note,
        )
    except DriftFingerprintStateNotFoundError as exc:
        raise HTTPException(status_code=404, detail="drift_report_not_found") from exc


@router.post("/fingerprints/remediation", summary="記錄 Config Drift fingerprint 修復")
async def record_drift_fingerprint_remediation(
    request: DriftFingerprintRemediationRequest,
) -> dict:
    """
    記錄 stable fingerprint 已完成的修復 / 驗證證據。

    安全邊界：只寫 alert_operation_log / timeline_events，不修改 drift 狀態、
    incident 狀態、自動修復結果，不建立外部 ticket，也不執行 kubectl。
    """
    svc = get_drift_fingerprint_state_service()
    try:
        return await svc.record_remediation(
            report_id=request.report_id,
            namespace=request.namespace,
            remediation_kind=request.remediation_kind,
            remediation_status=request.remediation_status,
            verification_report_id=request.verification_report_id,
            note=request.note,
            commands_summary=request.commands_summary,
        )
    except DriftFingerprintStateNotFoundError as exc:
        raise HTTPException(status_code=404, detail="drift_report_not_found") from exc


@router.post("/reports/{report_id}/rollback", summary="覆蓋回 Git 狀態")
async def rollback_drift(report_id: str, _csrf_token: CSRFToken) -> dict:  # Phase 20: CSRF Protection (驗證用，不需要使用值)
    """
    將 K8s 狀態覆蓋回 Git YAML（kubectl apply）

    人工確認後才執行，DriftRemediator 負責確定性修復
    """
    repo = get_drift_repository()
    report = await repo.get(report_id)
    if not report:
        raise HTTPException(status_code=404, detail=f"Report {report_id} not found")

    remediator = get_drift_remediator()
    result = await remediator.rollback(report)
    return result


@router.post("/reports/{report_id}/adopt", summary="承認變更並建立 Git PR")
async def adopt_drift(report_id: str, _csrf_token: CSRFToken) -> dict:  # Phase 20: CSRF Protection (驗證用，不需要使用值)
    """
    承認 K8s 漂移，透過 Gitea PR API 將漂移寫回 Git

    2026-04-05 Claude Code: ADR-057 實作 — 改用 Gitea PR API（不再 git push main）
    流程: 建立 drift/adopt-* branch → commit YAML 注解 → 建立 PR → Telegram 通知 SRE
    """
    repo = get_drift_repository()
    report = await repo.get(report_id)
    if not report:
        raise HTTPException(status_code=404, detail=f"Report {report_id} not found")

    from src.services.drift_adopt_service import get_drift_adopt_service
    adopt_svc = get_drift_adopt_service()
    result = await adopt_svc.adopt(report)
    return result


# =============================================================================
# Internal endpoint（供 K8s CronJob 呼叫）
# =============================================================================

@router.post("/internal/scan", include_in_schema=False, summary="CronJob 觸發掃描")
async def internal_scan(background_tasks: BackgroundTasks) -> dict:
    """內部 CronJob 端點，每小時自動掃描 awoooi-prod"""
    from src.core.config import get_settings
    settings = get_settings()
    namespaces = getattr(settings, "DRIFT_SCAN_NAMESPACES", "awoooi-prod").split(",")

    background_tasks.add_task(
        _run_full_scan,
        [ns.strip() for ns in namespaces],
    )
    return {"status": "scan_triggered", "namespaces": namespaces}


# =============================================================================
# Background helpers
# =============================================================================

async def _analyze_and_notify(report: DriftReport) -> None:
    """
    背景：Nemotron 意圖分析 + 低風險自動採納嘗試 + Telegram 推送

    2026-04-24 ogt + Claude Sonnet 4.6: 新增低風險自動採納
    流程：
      1. Nemotron 意圖分析（同原先）
      2. 嘗試 auto_adopt_if_safe()：
         - 通過 → 發 TYPE-1 無按鈕通知（PR 已建立，請 SRE 複核），不再推送帶按鈕卡片
         - 未通過（skipped=True）→ 走原有 narrator TYPE-4D 卡片流程
         - 採納失敗（skipped=False, success=False）→ 同樣走 narrator 讓人工介入
    """
    import structlog as _structlog
    _logger = _structlog.get_logger(__name__)
    try:
        interpreter = get_drift_interpreter()

        interpretation = await interpreter.analyze(report)
        repo = get_drift_repository()
        await repo.update_interpretation(report.report_id, interpretation)
        # 2026-05-04 ogt + Claude Sonnet 4.6: 修根因 — report 是 in-memory 物件，
        # update_interpretation 只更新 DB，不會回寫 report.interpretation，
        # 導致 auto_adopt_if_safe 永遠看到 None → 觸發「尚無 Nemotron 意圖分析」條件
        report.interpretation = interpretation

        # 2026-04-24: 嘗試低風險自動採納
        auto_adopted = False
        auto_block_reason = ""
        from src.core.config import get_settings as _gs
        _drift_auto_enabled = _gs().DRIFT_AUTO_ADOPT_ENABLED
        # flag=False 視為「停用」，不設 auto_block_reason 避免誤觸 escalation
        try:
            if _drift_auto_enabled:
                adopt_svc = get_drift_adopt_service()
                auto_result = await adopt_svc.auto_adopt_if_safe(report)
                if auto_result.get("success"):
                    # 自動採納成功：更新狀態，跳過人工卡片
                    await repo.update_status(
                        report.report_id,
                        DriftStatus.ADOPTED,
                        resolved_at=now_taipei(),
                    )
                    auto_adopted = True
                    _logger.info(
                        "drift_auto_adopted",
                        report_id=report.report_id,
                        pr_url=auto_result.get("pr_url"),
                    )
                else:
                    auto_block_reason = auto_result.get("reason", "") or "auto adopt skipped"
                    _logger.info(
                        "drift_auto_adopt_skipped",
                        report_id=report.report_id,
                        reason=auto_block_reason,
                        skipped=auto_result.get("skipped", True),
                    )
        except Exception as e:
            auto_block_reason = f"auto adopt error: {str(e)[:120]}"
            _logger.warning("drift_auto_adopt_error", report_id=report.report_id, error=str(e))

        if auto_adopted:
            # 自動採納成功，Telegram 通知已在 auto_adopt_if_safe 內發出，不再推送按鈕卡片
            return

        if auto_block_reason:
            await _escalate_drift_auto_adopt_blocked(
                report=report,
                reason=auto_block_reason,
                interpretation=interpretation,
            )

        # ADR-075: drift_narrator_service 負責發送 TYPE-4D 卡片（含按鈕）
        # 舊的 send_text() 已移除，改由 narrate_and_notify() 統一處理
        try:
            from src.services.drift_narrator_service import get_drift_narrator_service
            narrator = get_drift_narrator_service()
            await narrator.narrate_and_notify(report, interpretation)
        except Exception as e:
            _logger.warning("drift_narrator_failed", error=str(e))

    except Exception as e:
        import structlog
        structlog.get_logger(__name__).error("drift_analyze_notify_failed", error=str(e))


async def _escalate_drift_auto_adopt_blocked(
    *,
    report: DriftReport,
    reason: str,
    interpretation,
) -> None:
    """Delegate drift emergency escalation to the service layer."""

    from src.services.emergency_escalation_service import (
        escalate_drift_auto_adopt_blocked,
    )

    await escalate_drift_auto_adopt_blocked(
        report=report,
        reason=reason,
        interpretation=interpretation,
    )


async def _run_full_scan(namespaces: list[str]) -> None:
    """背景：完整漂移掃描"""
    detector = get_drift_detector()
    analyzer = get_drift_analyzer()

    repo = get_drift_repository()
    for namespace in namespaces:
        try:
            raw = await detector.scan(namespace, triggered_by="cron")
            classified = analyzer.classify(raw)
            await repo.save(classified)

            if analyzer.needs_alert(classified):
                await _analyze_and_notify(classified)
        except Exception as e:
            import structlog
            structlog.get_logger(__name__).error(
                "full_scan_namespace_failed", namespace=namespace, error=str(e)
            )
