"""
Drift Adopt Service — ADR-057: Gitea PR API 實作
=================================================
職責：將合法漂移反向寫回 Git，透過 Gitea PR API 而非直接 git push

設計邊界（核心原則）：
- 不直接操作 git（不 git add/commit/push）
- 透過 Gitea REST API 建立 branch + commit + PR
- PR 需 SRE 手動 review 後 merge（不自動 merge main）
- 只修改漂移相關的 YAML 欄位（不 add -A）

流程：
  adopt() 被呼叫
    → 讀取 k8s/ 目錄對應 YAML 檔
    → 建立 drift/adopt-{report_id[:8]}-{ts} branch
    → 透過 Gitea API commit YAML 更新
    → 建立 PR + 推送 Telegram 通知

版本: v1.0
建立: 2026-04-05 (台北時區)
建立者: Claude Code (ADR-057 實作)
關聯 ADR: ADR-057
"""

from __future__ import annotations

import base64
import pathlib
from typing import TYPE_CHECKING

import httpx
import structlog

from src.core.config import get_settings
from src.utils.timezone import now_taipei

if TYPE_CHECKING:
    from src.models.drift import DriftInterpretation, DriftReport

logger = structlog.get_logger(__name__)


class DriftAdoptService:
    """
    透過 Gitea PR API 將漂移寫回 Git

    ADR-057 安全設計：
    ✅ 使用 Gitea API（不在 API Pod 內執行 git）
    ✅ 建立 PR 讓 SRE review（不直接 push main）
    ✅ 只 commit k8s/ 目錄的 YAML（不 git add -A）
    ✅ API Token 從 K8s Secret 注入（不寫死）
    """

    def __init__(self) -> None:
        settings = get_settings()
        self._api_url = settings.GITEA_API_URL.rstrip("/")
        self._token = settings.GITEA_API_TOKEN
        self._owner = settings.GITEA_REPO_OWNER
        self._repo = settings.GITEA_REPO_NAME
        self._k8s_dir = pathlib.Path("k8s")

    async def auto_adopt_if_safe(self, report: "DriftReport") -> dict:
        """
        低風險 drift 自動採納（2026-04-24 ogt + Claude Sonnet 4.6）

        自動採納條件（全部滿足）：
        1. high_count == 0（無 HIGH drift）
        2. medium_count <= 5（MEDIUM drift 不超過 5 項）
        3. actionable_count <= 10（非白名單非 trivial 項目不超過 10）
        4. interpretation.confidence >= 0.70（意圖確定性足夠）
        5. interpretation.intent NOT IN {unknown}（必須有明確意圖）
        6. interpretation.risk NOT IN {HIGH, CRITICAL}（非高風險）

        不滿足任一條件 → return {"success": False, "reason": "...", "skipped": True}
        自動採納 PR title 加 [AUTO] 前綴，通知標示「請 SRE 複核後 merge」

        Returns:
            {"success": bool, "skipped": bool, "reason": str, "pr_url": str | None}
              skipped=True  → 條件不符，人工卡片照舊推送
              skipped=False → 嘗試了，但 Gitea API 失敗
        """
        interp = report.interpretation

        # 條件 1: 無 HIGH drift
        if report.high_count > 0:
            return {
                "success": False,
                "skipped": True,
                "reason": f"存在 {report.high_count} 項 HIGH drift，不自動採納",
                "pr_url": None,
            }

        # 條件 2: MEDIUM 不超過 5 項
        if report.medium_count > 5:
            return {
                "success": False,
                "skipped": True,
                "reason": f"MEDIUM drift {report.medium_count} 項超過上限 5，不自動採納",
                "pr_url": None,
            }

        # 條件 3: actionable 不超過 10（過濾白名單 + trivial）
        actionable_items = [
            item for item in report.items
            if not item.is_allowlisted
            and not self._is_trivial_drift(item.git_value, item.actual_value)
        ]
        actionable_count = len(actionable_items)
        if actionable_count > 10:
            return {
                "success": False,
                "skipped": True,
                "reason": f"可操作漂移 {actionable_count} 項超過上限 10，不自動採納",
                "pr_url": None,
            }

        # 條件 4 / 5 / 6: 需要有 interpretation
        if not interp:
            return {
                "success": False,
                "skipped": True,
                "reason": "尚無 Nemotron 意圖分析，不自動採納",
                "pr_url": None,
            }

        if interp.confidence < 0.70:
            return {
                "success": False,
                "skipped": True,
                "reason": f"意圖信心 {interp.confidence:.0%} < 70%，不自動採納",
                "pr_url": None,
            }

        if interp.intent.value == "unknown":
            return {
                "success": False,
                "skipped": True,
                "reason": "Nemotron 意圖為 unknown，不自動採納",
                "pr_url": None,
            }

        if interp.risk.upper() in ("HIGH", "CRITICAL"):
            return {
                "success": False,
                "skipped": True,
                "reason": f"Nemotron 風險評估為 {interp.risk}，不自動採納",
                "pr_url": None,
            }

        logger.info(
            "drift_auto_adopt_eligible",
            report_id=report.report_id,
            medium_count=report.medium_count,
            actionable=actionable_count,
            intent=interp.intent.value,
            confidence=interp.confidence,
            risk=interp.risk,
        )

        # 通過全部條件 → 執行採納（suppress_notification=True 避免與下方通知重複）
        result = await self.adopt(report, field_description="[AUTO] 低風險自動採納", suppress_notification=True)
        if result.get("success"):
            # 通知 SRE：無按鈕，標示需複核
            await self._notify_auto_adopt_telegram(result["pr_url"], report, actionable_count, interp)

        return {
            "success": result.get("success", False),
            "skipped": False,
            "reason": result.get("message", ""),
            "pr_url": result.get("pr_url"),
        }

    @staticmethod
    def _is_trivial_drift(git_val, actual_val) -> bool:
        """
        判斷是否為 K8s controller 自動補齊的噪音
        （與 DriftNarratorService._is_trivial_drift 邏輯一致）
        """
        def _is_empty(v) -> bool:
            if v is None:
                return True
            s = str(v).strip()
            return s in ("", "{}", "[]", "null", "None", "false", "False", "0")
        return _is_empty(git_val) and _is_empty(actual_val)

    async def _notify_auto_adopt_telegram(
        self,
        pr_url: str,
        report: "DriftReport",
        actionable_count: int,
        interp: "DriftInterpretation",
    ) -> None:
        """
        自動採納成功後的無按鈕 Telegram 通知（TYPE-1 純資訊）
        標示「已自動建立 PR，請 SRE 複核後 merge」
        """
        try:
            from src.services.telegram_gateway import get_telegram_gateway
            tg = get_telegram_gateway()
            message = (
                f"Namespace: {report.namespace}\n"
                f"漂移摘要: {report.summary}（可操作 {actionable_count} 項）\n"
                f"Nemotron 意圖: {interp.intent.value} | 信心: {interp.confidence:.0%} | 風險: {interp.risk}\n\n"
                f"PR: {pr_url}\n\n"
                f"請 SRE 複核後 merge。"
            )
            await tg.send_info_notification(
                incident_id=report.report_id,
                title="Config Drift 已自動採納（低風險）",
                message=message,
                alertname="ConfigDriftAutoAdopt",
                severity="info",
            )
        except Exception as e:
            logger.warning("drift_auto_adopt_telegram_failed", error=str(e))

    # =========================================================================
    # Public: 人工按鈕觸發入口
    # =========================================================================

    async def adopt_drift(self, report_id: str) -> dict:
        """
        2026-04-20 ogt + Claude Opus 4.7: Telegram 按鈕呼叫入口
        從 DB 載入 DriftReport 後委派給 adopt()。
        telegram_gateway._handle_drift_action 呼叫此方法。
        """
        from src.repositories.drift_repository import get_drift_repository
        report = await get_drift_repository().get(report_id)
        if not report:
            return {"success": False, "message": f"Report {report_id} not found"}
        return await self.adopt(report)

    async def adopt(self, report: "DriftReport", field_description: str = "", suppress_notification: bool = False) -> dict:
        """
        將漂移寫回 Git：建立 branch + commit + PR

        Args:
            report: 漂移報告（含具體 drift items）
            field_description: 漂移欄位說明（用於 PR title）

        Returns:
            {"success": bool, "pr_url": str, "message": str}
        """
        if not self._token:
            return {
                "success": False,
                "message": "GITEA_API_TOKEN 未設定，請在 K8s Secret 中新增此 key",
                "pr_url": None,
            }

        ts = now_taipei().strftime("%Y%m%d%H%M%S")
        branch_name = f"drift/adopt-{report.report_id[:8]}-{ts}"
        pr_title = f"chore: adopt drift — {report.namespace} {field_description or report.summary}"

        logger.info(
            "drift_adopt_start",
            report_id=report.report_id,
            branch=branch_name,
        )

        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                headers = {
                    "Authorization": f"token {self._token}",
                    "Content-Type": "application/json",
                }

                # Step 1: 取得 main branch 的最新 SHA
                main_sha = await self._get_main_sha(client, headers)
                if not main_sha:
                    return {"success": False, "message": "無法取得 main branch SHA", "pr_url": None}

                # Step 2: 建立新 branch
                branch_ok = await self._create_branch(client, headers, branch_name, main_sha)
                if not branch_ok:
                    return {"success": False, "message": f"建立 branch {branch_name} 失敗", "pr_url": None}

                # Step 3: 找出受影響的 YAML 檔並 commit 更新
                committed_files = await self._commit_drift_yaml(client, headers, branch_name, report)
                if not committed_files:
                    return {
                        "success": False,
                        "message": "無直接匹配的 YAML 檔，未建立零 diff 承認 PR",
                        "pr_url": None,
                    }

                # Step 4: 建立 PR
                pr_url = await self._create_pr(
                    client, headers, branch_name, pr_title, report, committed_files
                )
                if not pr_url:
                    return {"success": False, "message": "建立 PR 失敗", "pr_url": None}

                # Step 5: 推送 Telegram 通知（auto_adopt_if_safe 會自己發，避免重複）
                if not suppress_notification:
                    await self._notify_telegram(pr_url, report, pr_title)

                logger.info("drift_adopt_pr_created", report_id=report.report_id, pr_url=pr_url)
                return {
                    "success": True,
                    "message": "PR 已建立，請 SRE review 後 merge",
                    "pr_url": pr_url,
                }

        except Exception as e:
            logger.error("drift_adopt_failed", report_id=report.report_id, error=str(e))
            return {"success": False, "message": f"adopt 失敗: {str(e)}", "pr_url": None}

    # =========================================================================
    # Private helpers
    # =========================================================================

    async def _get_main_sha(self, client: httpx.AsyncClient, headers: dict) -> str | None:
        """取得 main branch 最新 commit SHA"""
        resp = await client.get(
            f"{self._api_url}/api/v1/repos/{self._owner}/{self._repo}/branches/main",
            headers=headers,
        )
        if resp.status_code == 200:
            return resp.json()["commit"]["id"]
        logger.error("drift_adopt_get_main_sha_failed", status=resp.status_code)
        return None

    async def _create_branch(
        self, client: httpx.AsyncClient, headers: dict, branch_name: str, main_sha: str
    ) -> bool:
        """建立新 branch"""
        resp = await client.post(
            f"{self._api_url}/api/v1/repos/{self._owner}/{self._repo}/branches",
            headers=headers,
            json={"new_branch_name": branch_name, "old_branch_name": "main"},
        )
        if resp.status_code in (200, 201):
            return True
        logger.error("drift_adopt_create_branch_failed", status=resp.status_code, body=resp.text[:200])
        return False

    async def _commit_drift_yaml(
        self, client: httpx.AsyncClient, headers: dict, branch_name: str, report: "DriftReport"
    ) -> list[str]:
        """
        找出受漂移影響的 YAML 檔，更新實際值後 commit 到 branch

        目前策略：標記 YAML 檔為「已承認漂移」（加注解），
        等 ADR-057 Phase 2 再實作精確 YAML patch 邏輯。
        """
        committed = []
        affected_kinds = {
            item.resource_kind.lower()
            for item in report.items
            if not item.is_allowlisted
        }

        for yaml_file in sorted(self._k8s_dir.rglob("*.yaml")):
            if not yaml_file.is_file():
                continue
            # 判斷此 YAML 是否與漂移相關
            file_stem = yaml_file.stem.lower()
            if not any(kind in file_stem for kind in affected_kinds):
                continue

            try:
                content = yaml_file.read_text()

                # 取得 Gitea 上的 file SHA（用於 update API）
                file_sha = await self._get_file_sha(client, headers, str(yaml_file))

                # 在檔案末尾加入漂移承認注解
                from src.utils.timezone import now_taipei as _now
                ts_str = _now().strftime("%Y-%m-%d %H:%M:%S +0800")
                annotation = (
                    f"\n# [drift-adopted] {ts_str}\n"
                    f"# Report: {report.report_id}\n"
                    f"# Namespace: {report.namespace}\n"
                    f"# Summary: {report.summary}\n"
                )
                updated_content = content + annotation
                encoded = base64.b64encode(updated_content.encode()).decode()

                # Commit 到 branch
                payload = {
                    "message": f"chore(drift): adopt {report.namespace} drift — {yaml_file.name}",
                    "content": encoded,
                    "branch": branch_name,
                }
                if file_sha:
                    payload["sha"] = file_sha

                url = f"{self._api_url}/api/v1/repos/{self._owner}/{self._repo}/contents/{yaml_file}"
                resp = await client.put(url, headers=headers, json=payload)
                if resp.status_code in (200, 201):
                    committed.append(str(yaml_file))
                    logger.info("drift_adopt_file_committed", file=str(yaml_file))
                else:
                    logger.warning("drift_adopt_file_commit_failed", file=str(yaml_file), status=resp.status_code)

            except Exception as e:
                logger.warning("drift_adopt_file_error", file=str(yaml_file), error=str(e))

        return committed

    async def _get_file_sha(
        self, client: httpx.AsyncClient, headers: dict, file_path: str
    ) -> str | None:
        """取得 Gitea 上檔案的 SHA（update 時需要）"""
        resp = await client.get(
            f"{self._api_url}/api/v1/repos/{self._owner}/{self._repo}/contents/{file_path}",
            headers=headers,
            params={"ref": "main"},
        )
        if resp.status_code == 200:
            return resp.json().get("sha")
        return None

    async def _create_pr(
        self,
        client: httpx.AsyncClient,
        headers: dict,
        branch_name: str,
        pr_title: str,
        report: "DriftReport",
        committed_files: list[str],
    ) -> str | None:
        """建立 Pull Request，回傳 PR URL"""
        files_md = "\n".join(f"- `{f}`" for f in committed_files) if committed_files else "（無直接匹配的 YAML 檔）"

        intent_label = "❓ 意圖不明"
        if report.interpretation:
            intent_map = {
                "emergency_hotfix": "🚨 緊急 Hotfix",
                "human_error": "⚠️ 人為誤操作",
                "automated_change": "🤖 系統自動變更",
                "unknown": "❓ 意圖不明",
            }
            intent_label = intent_map.get(report.interpretation.intent.value, "❓ 意圖不明")

        body = (
            f"## Config Drift 承認\n\n"
            f"**Report ID**: `{report.report_id}`\n"
            f"**Namespace**: `{report.namespace}`\n"
            f"**漂移摘要**: {report.summary}\n"
            f"**Nemotron 意圖**: {intent_label}\n"
            f"{f'**說明**: {report.interpretation.explanation}' if report.interpretation else ''}\n\n"
            f"## 異動檔案\n\n{files_md}\n\n"
            f"## 說明\n\n"
            f"此 PR 由 AWOOOI Config Drift Detection 系統自動建立。\n"
            f"承認此次 K8s 漂移為合法變更，將漂移狀態寫回 Git。\n\n"
            f"> **SRE 確認事項**:\n"
            f"> - [ ] 確認漂移是預期的合法變更\n"
            f"> - [ ] 確認 YAML 注解正確反映變更意圖\n"
            f"> - [ ] merge 後手動更新 K8s YAML 的實際差異值\n"
        )

        resp = await client.post(
            f"{self._api_url}/api/v1/repos/{self._owner}/{self._repo}/pulls",
            headers=headers,
            json={
                "title": pr_title,
                "body": body,
                "head": branch_name,
                "base": "main",
            },
        )
        if resp.status_code in (200, 201):
            pr_data = resp.json()
            return pr_data.get("html_url") or pr_data.get("url")
        logger.error("drift_adopt_create_pr_failed", status=resp.status_code, body=resp.text[:300])
        return None

    async def _notify_telegram(self, pr_url: str, report: "DriftReport", pr_title: str) -> None:
        """推送 Telegram 通知 SRE"""
        try:
            from src.services.telegram_gateway import get_telegram_gateway
            tg = get_telegram_gateway()
            await tg.send_text(
                f"📋 <b>Config Drift 承認 PR 已建立</b>\n"
                f"Namespace: {report.namespace}\n"
                f"漂移: {report.summary}\n\n"
                f"PR: {pr_url}\n\n"
                f"請 SRE review 後 merge。"
            )
        except Exception as e:
            logger.warning("drift_adopt_telegram_failed", error=str(e))


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

_adopt_service: DriftAdoptService | None = None


def get_drift_adopt_service() -> DriftAdoptService:
    global _adopt_service
    if _adopt_service is None:
        _adopt_service = DriftAdoptService()
    return _adopt_service
