"""
Multi-Sig Redis Service - 簽核狀態持久化
=========================================
Phase 6.1.1: Multi-Sig Redis 遷移

Features:
- 簽核狀態 Redis Hash 持久化
- 7 天 TTL 稽核保留 (資安合規)
- 分散式鎖防止 Race Condition
- 與現有 SQLite 雙寫模式 (Phase 6.2 後可移除 SQLite)

統帥鐵律:
- 所有簽核狀態變更必須經過此模組
- 7 天 TTL 不可修改 (資安稽核要求)
- 分散式鎖必須包裹所有寫入操作
"""

import json
from datetime import UTC, datetime
from typing import Protocol, runtime_checkable
from uuid import UUID

import structlog

from src.core.redis_client import RedisLock, get_redis

logger = structlog.get_logger(__name__)


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

# Redis Key 前綴
APPROVAL_KEY_PREFIX = "approval:"
SIGNATURE_KEY_PREFIX = "signature:"

# 7 天 TTL (資安稽核要求)
APPROVAL_TTL_SECONDS = 86400 * 7  # 604800 秒


# =============================================================================
# Approval State Model
# =============================================================================

class ApprovalStateRedis:
    """
    Redis 中的簽核狀態結構

    Hash Fields:
    - id: 簽核單 ID
    - action: 操作類型 (DELETE_POD, RESTART_SERVICE, etc.)
    - description: 描述
    - status: 狀態 (pending, approved, rejected, voided, executed)
    - risk_level: 風險等級 (critical, high, medium, low)
    - required_signatures: 需要簽核數
    - current_signatures: 目前簽核數
    - signatures: 簽核列表 (JSON Array)
    - created_at: 建立時間
    - updated_at: 更新時間
    - namespace: K8s Namespace
    - resource_name: 資源名稱
    """

    @staticmethod
    def get_key(approval_id: str | UUID) -> str:
        """取得 Redis Key"""
        return f"{APPROVAL_KEY_PREFIX}{str(approval_id)}"


# =============================================================================
# Protocol Interface (Phase 17 P1 - 紅區治理)
# =============================================================================

@runtime_checkable
class IMultiSigRedisService(Protocol):
    """
    MultiSigRedisService 介面定義

    用途:
    - 依賴注入 (DI) 時的型別約束
    - 測試時 Mock 的型別檢查
    - 符合 leWOOOgo 積木化規範

    Tier 3 紅區服務: 修改需首席架構師簽核

    @see feedback_lewooogo_modular_enforcement.md
    @see docs/RED_ZONES.md
    """

    async def create_approval(
        self,
        approval_id: str | UUID,
        action: str,
        description: str,
        risk_level: str,
        required_signatures: int,
        namespace: str = "default",
        resource_name: str = "",
        blast_radius: dict | None = None,
        dry_run_checks: list | None = None,
    ) -> dict:
        """建立新的簽核單"""
        ...

    async def get_approval(self, approval_id: str | UUID) -> dict | None:
        """取得簽核狀態"""
        ...

    async def add_signature(
        self,
        approval_id: str | UUID,
        signer_id: str,
        signer_name: str,
        comment: str = "",
        source: str = "web",
        telegram_user_id: int | None = None,
        telegram_message_id: int | None = None,
    ) -> dict:
        """新增簽名 (含分散式鎖保護)"""
        ...

    async def update_status(
        self,
        approval_id: str | UUID,
        status: str,
    ) -> dict | None:
        """更新簽核狀態"""
        ...


# =============================================================================
# Multi-Sig Redis Service
# =============================================================================

class MultiSigRedisService:
    """
    Multi-Sig Redis 持久化服務

    提供簽核狀態的 CRUD 操作，包含:
    - 建立簽核單
    - 新增簽名
    - 更新狀態
    - 查詢狀態
    - 分散式鎖保護
    """

    async def create_approval(
        self,
        approval_id: str | UUID,
        action: str,
        description: str,
        risk_level: str,
        required_signatures: int,
        namespace: str = "default",
        resource_name: str = "",
        blast_radius: dict | None = None,
        dry_run_checks: list | None = None,
    ) -> dict:
        """
        建立新的簽核單

        Args:
            approval_id: 簽核單 ID
            action: 操作類型
            description: 描述
            risk_level: 風險等級
            required_signatures: 需要簽核數
            namespace: K8s Namespace
            resource_name: 資源名稱
            blast_radius: 爆炸半徑
            dry_run_checks: Dry-Run 檢查結果

        Returns:
            dict: 建立的簽核狀態
        """
        redis_client = get_redis()
        key = ApprovalStateRedis.get_key(approval_id)
        now = datetime.now(UTC).isoformat()

        state = {
            "id": str(approval_id),
            "action": action,
            "description": description,
            "status": "pending",
            "risk_level": risk_level,
            "required_signatures": required_signatures,
            "current_signatures": 0,
            "signatures": json.dumps([]),  # JSON Array
            "created_at": now,
            "updated_at": now,
            "namespace": namespace,
            "resource_name": resource_name,
            "blast_radius": json.dumps(blast_radius or {}),
            "dry_run_checks": json.dumps(dry_run_checks or []),
        }

        # 使用 HSET 寫入 Hash
        await redis_client.hset(key, mapping=state)

        # 設定 7 天 TTL (資安稽核要求)
        await redis_client.expire(key, APPROVAL_TTL_SECONDS)

        logger.info(
            "redis_approval_created",
            approval_id=str(approval_id),
            risk_level=risk_level,
            ttl_days=7,
        )

        return state

    async def get_approval(self, approval_id: str | UUID) -> dict | None:
        """
        取得簽核狀態

        Args:
            approval_id: 簽核單 ID

        Returns:
            dict | None: 簽核狀態，若不存在則返回 None
        """
        redis_client = get_redis()
        key = ApprovalStateRedis.get_key(approval_id)

        state = await redis_client.hgetall(key)

        if not state:
            return None

        # 解析 JSON 欄位
        if "signatures" in state:
            state["signatures"] = json.loads(state["signatures"])
        if "blast_radius" in state:
            state["blast_radius"] = json.loads(state["blast_radius"])
        if "dry_run_checks" in state:
            state["dry_run_checks"] = json.loads(state["dry_run_checks"])

        # 轉換數值欄位
        if "required_signatures" in state:
            state["required_signatures"] = int(state["required_signatures"])
        if "current_signatures" in state:
            state["current_signatures"] = int(state["current_signatures"])

        return state

    async def add_signature(
        self,
        approval_id: str | UUID,
        signer_id: str,
        signer_name: str,
        comment: str = "",
        source: str = "web",
        telegram_user_id: int | None = None,
        telegram_message_id: int | None = None,
    ) -> dict:
        """
        新增簽名 (含分散式鎖保護)

        防禦場景:
        - Web + Telegram 同時簽核
        - 防止 K8s Executor 被觸發兩次

        Args:
            approval_id: 簽核單 ID
            signer_id: 簽核者 ID
            signer_name: 簽核者名稱
            comment: 備註
            source: 來源 (web, telegram, api)
            telegram_user_id: Telegram User ID
            telegram_message_id: Telegram Message ID

        Returns:
            dict: 更新後的簽核狀態

        Raises:
            RuntimeError: 若無法取得鎖或簽核單不存在
        """
        redis_client = get_redis()
        key = ApprovalStateRedis.get_key(approval_id)
        lock_key = f"{str(approval_id)}:sign"

        # 使用分散式鎖保護簽核操作
        async with RedisLock(lock_key, timeout=10, blocking_timeout=5):
            # 取得目前狀態
            state = await self.get_approval(approval_id)
            if not state:
                raise RuntimeError(f"Approval not found: {approval_id}")

            # 檢查狀態是否可簽核
            if state["status"] != "pending":
                raise RuntimeError(f"Approval is not pending: {state['status']}")

            # 檢查是否已簽過
            signatures = state.get("signatures", [])
            for sig in signatures:
                if sig.get("signer_id") == signer_id:
                    raise RuntimeError(f"Already signed by: {signer_id}")

            # 新增簽名
            now = datetime.now(UTC).isoformat()
            new_signature = {
                "signer_id": signer_id,
                "signer_name": signer_name,
                "timestamp": now,
                "comment": comment,
                "source": source,
            }

            if telegram_user_id:
                new_signature["telegram_user_id"] = telegram_user_id
            if telegram_message_id:
                new_signature["telegram_message_id"] = telegram_message_id

            signatures.append(new_signature)
            current_signatures = len(signatures)

            # 檢查是否達到簽核門檻
            new_status = "pending"
            if current_signatures >= state["required_signatures"]:
                new_status = "approved"

            # 更新 Redis
            await redis_client.hset(key, mapping={
                "signatures": json.dumps(signatures),
                "current_signatures": current_signatures,
                "status": new_status,
                "updated_at": now,
            })

            # 延長 TTL (每次操作都重設 7 天)
            await redis_client.expire(key, APPROVAL_TTL_SECONDS)

            logger.info(
                "redis_signature_added",
                approval_id=str(approval_id),
                signer_id=signer_id,
                source=source,
                current=current_signatures,
                required=state["required_signatures"],
                new_status=new_status,
            )

            return await self.get_approval(approval_id)

    async def update_status(
        self,
        approval_id: str | UUID,
        status: str,
        executor_id: str | None = None,
        execution_result: dict | None = None,
    ) -> dict:
        """
        更新簽核狀態

        Args:
            approval_id: 簽核單 ID
            status: 新狀態 (approved, rejected, voided, executed)
            executor_id: 執行者 ID
            execution_result: 執行結果

        Returns:
            dict: 更新後的簽核狀態
        """
        redis_client = get_redis()
        key = ApprovalStateRedis.get_key(approval_id)
        lock_key = f"{str(approval_id)}:status"

        async with RedisLock(lock_key, timeout=10, blocking_timeout=5):
            state = await self.get_approval(approval_id)
            if not state:
                raise RuntimeError(f"Approval not found: {approval_id}")

            now = datetime.now(UTC).isoformat()

            updates = {
                "status": status,
                "updated_at": now,
            }

            if executor_id:
                updates["executor_id"] = executor_id
            if execution_result:
                updates["execution_result"] = json.dumps(execution_result)

            await redis_client.hset(key, mapping=updates)
            await redis_client.expire(key, APPROVAL_TTL_SECONDS)

            logger.info(
                "redis_status_updated",
                approval_id=str(approval_id),
                status=status,
            )

            return await self.get_approval(approval_id)

    async def reject_approval(
        self,
        approval_id: str | UUID,
        rejector_id: str,
        rejector_name: str,
        reason: str = "",
    ) -> dict:
        """
        拒絕簽核單

        Args:
            approval_id: 簽核單 ID
            rejector_id: 拒絕者 ID
            rejector_name: 拒絕者名稱
            reason: 拒絕原因

        Returns:
            dict: 更新後的簽核狀態
        """
        redis_client = get_redis()
        key = ApprovalStateRedis.get_key(approval_id)
        lock_key = f"{str(approval_id)}:reject"

        async with RedisLock(lock_key, timeout=10, blocking_timeout=5):
            state = await self.get_approval(approval_id)
            if not state:
                raise RuntimeError(f"Approval not found: {approval_id}")

            now = datetime.now(UTC).isoformat()

            await redis_client.hset(key, mapping={
                "status": "rejected",
                "updated_at": now,
                "rejector_id": rejector_id,
                "rejector_name": rejector_name,
                "rejection_reason": reason,
            })
            await redis_client.expire(key, APPROVAL_TTL_SECONDS)

            logger.info(
                "redis_approval_rejected",
                approval_id=str(approval_id),
                rejector_id=rejector_id,
            )

            return await self.get_approval(approval_id)

    async def list_pending(self, limit: int = 100) -> list[dict]:
        """
        列出所有待簽核單

        注意: 此方法使用 SCAN，在大量資料時效能較低
        建議在 Phase 6.2 加入索引機制

        Args:
            limit: 最大返回數量

        Returns:
            list[dict]: 待簽核單列表
        """
        redis_client = get_redis()
        results = []

        async for key in redis_client.scan_iter(match=f"{APPROVAL_KEY_PREFIX}*", count=100):
            if len(results) >= limit:
                break

            state = await redis_client.hgetall(key)
            if state and state.get("status") == "pending":
                # 解析 JSON 欄位
                if "signatures" in state:
                    state["signatures"] = json.loads(state["signatures"])
                if "required_signatures" in state:
                    state["required_signatures"] = int(state["required_signatures"])
                if "current_signatures" in state:
                    state["current_signatures"] = int(state["current_signatures"])
                results.append(state)

        return results

    async def exists(self, approval_id: str | UUID) -> bool:
        """
        檢查簽核單是否存在

        Args:
            approval_id: 簽核單 ID

        Returns:
            bool: 是否存在
        """
        redis_client = get_redis()
        key = ApprovalStateRedis.get_key(approval_id)
        return await redis_client.exists(key) > 0


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

_service: MultiSigRedisService | None = None


def get_multi_sig_redis_service() -> MultiSigRedisService:
    """取得全域 MultiSigRedisService 實例"""
    global _service
    if _service is None:
        _service = MultiSigRedisService()
    return _service
