"""
Redis Memory Provider - Working Memory 層
==========================================
Phase 6.4d: Working Memory 實作

統帥鐵律 2026-03-23:
- 7 天 TTL 預設值
- 禁止無限累積 (所有 key 必須有 TTL)
- 優雅降級：Redis 斷線不影響主流程

Features:
- 非同步連線池 (redis.asyncio)
- JSON 序列化 (Pydantic BaseModel)
- 自動重連機制
"""

import json
import os
from typing import Any, TypeVar, Type

import structlog
from pydantic import BaseModel

from ..interfaces.memory_provider import IMemoryProvider

logger = structlog.get_logger(__name__)

T = TypeVar("T", bound=BaseModel)


# =============================================================================
# Redis 常量
# =============================================================================

DEFAULT_REDIS_URL = "redis://192.168.0.188:6379/0"
DEFAULT_TTL_SECONDS = 604800  # 7 天


def get_redis_url() -> str:
    """
    取得 Redis 連線字串

    優先順序:
    1. REDIS_URL 環境變數
    2. 188 Redis 預設值
    """
    return os.getenv("REDIS_URL", DEFAULT_REDIS_URL)


# =============================================================================
# Redis 連線池 (全域單例)
# =============================================================================

_redis_pool = None


async def init_redis_pool():
    """
    初始化 Redis 連線池

    使用 redis.asyncio 的 ConnectionPool
    """
    global _redis_pool

    if _redis_pool is not None:
        return _redis_pool

    try:
        import redis.asyncio as redis

        redis_url = get_redis_url()
        _redis_pool = redis.from_url(
            redis_url,
            encoding="utf-8",
            decode_responses=True,
            socket_timeout=5.0,
            socket_connect_timeout=5.0,
        )

        # 測試連線
        await _redis_pool.ping()

        logger.info(
            "redis_pool_initialized",
            url=redis_url.split("@")[-1] if "@" in redis_url else redis_url,
        )

        return _redis_pool

    except Exception as e:
        logger.error("redis_pool_init_failed", error=str(e))
        _redis_pool = None
        raise


async def close_redis_pool():
    """關閉 Redis 連線池"""
    global _redis_pool

    if _redis_pool is not None:
        await _redis_pool.close()
        _redis_pool = None
        logger.info("redis_pool_closed")


def get_redis_pool():
    """取得 Redis 連線池"""
    if _redis_pool is None:
        raise RuntimeError("Redis pool not initialized. Call init_redis_pool() first.")
    return _redis_pool


# =============================================================================
# RedisMemoryProvider 實作
# =============================================================================


class RedisMemoryProvider(IMemoryProvider[T]):
    """
    Redis 記憶體提供者 (Working Memory)

    特性:
    - 7 天 TTL 預設值
    - JSON 序列化 (Pydantic BaseModel)
    - 優雅降級：失敗時返回 None/False，不拋異常

    統帥鐵律:
    - 所有 key 必須有 TTL (禁止無限累積)
    - 連線失敗時優雅降級，不阻塞主流程
    """

    def __init__(
        self,
        model_class: Type[T],
        key_prefix: str = "awoooi",
        default_ttl: int = DEFAULT_TTL_SECONDS,
    ):
        """
        Args:
            model_class: Pydantic Model 類別
            key_prefix: Key 前綴
            default_ttl: 預設 TTL (秒)
        """
        self._model_class = model_class
        self._key_prefix = key_prefix
        self._default_ttl = default_ttl

    def _make_key(self, key: str) -> str:
        """生成完整的 Redis key"""
        return f"{self._key_prefix}:{key}"

    async def load(self, key: str) -> T | None:
        """
        從 Redis 載入資料

        Args:
            key: 資料鍵值

        Returns:
            Model 實例或 None
        """
        try:
            redis = get_redis_pool()
            full_key = self._make_key(key)
            data = await redis.get(full_key)

            if data is None:
                return None

            # JSON 反序列化為 Pydantic Model
            return self._model_class.model_validate_json(data)

        except RuntimeError:
            # Redis 未初始化，優雅降級
            logger.warning("redis_not_initialized", key=key)
            return None

        except Exception as e:
            logger.error("redis_load_failed", key=key, error=str(e))
            return None

    async def save(self, key: str, data: T, ttl_seconds: int | None = None) -> bool:
        """
        儲存到 Redis

        Args:
            key: 資料鍵值
            data: Pydantic Model 實例
            ttl_seconds: 過期時間 (秒)，None 使用預設值

        Returns:
            是否成功
        """
        try:
            redis = get_redis_pool()
            full_key = self._make_key(key)
            ttl = ttl_seconds if ttl_seconds is not None else self._default_ttl

            # Pydantic Model → JSON
            json_data = data.model_dump_json()

            # 統帥鐵律：所有 key 必須有 TTL
            await redis.setex(full_key, ttl, json_data)

            logger.debug("redis_saved", key=full_key, ttl=ttl)
            return True

        except RuntimeError:
            logger.warning("redis_not_initialized", key=key)
            return False

        except Exception as e:
            logger.error("redis_save_failed", key=key, error=str(e))
            return False

    async def delete(self, key: str) -> bool:
        """
        從 Redis 刪除

        Args:
            key: 資料鍵值

        Returns:
            是否成功
        """
        try:
            redis = get_redis_pool()
            full_key = self._make_key(key)
            result = await redis.delete(full_key)

            logger.debug("redis_deleted", key=full_key, deleted=result > 0)
            return result > 0

        except RuntimeError:
            logger.warning("redis_not_initialized", key=key)
            return False

        except Exception as e:
            logger.error("redis_delete_failed", key=key, error=str(e))
            return False

    async def exists(self, key: str) -> bool:
        """
        檢查是否存在

        Args:
            key: 資料鍵值

        Returns:
            是否存在
        """
        try:
            redis = get_redis_pool()
            full_key = self._make_key(key)
            return await redis.exists(full_key) > 0

        except RuntimeError:
            logger.warning("redis_not_initialized", key=key)
            return False

        except Exception as e:
            logger.error("redis_exists_failed", key=key, error=str(e))
            return False

    async def update(self, key: str, updates: dict[str, Any]) -> bool:
        """
        更新 Redis 記錄 (讀取-修改-寫入)

        Args:
            key: 資料鍵值
            updates: 要更新的欄位

        Returns:
            是否成功
        """
        try:
            # 載入現有資料
            existing = await self.load(key)
            if existing is None:
                logger.warning("redis_update_not_found", key=key)
                return False

            # 更新欄位
            existing_dict = existing.model_dump()
            existing_dict.update(updates)

            # 重新建立 Model 並儲存
            updated = self._model_class.model_validate(existing_dict)

            # 取得剩餘 TTL
            redis = get_redis_pool()
            full_key = self._make_key(key)
            remaining_ttl = await redis.ttl(full_key)

            # 保留原有 TTL
            ttl = remaining_ttl if remaining_ttl > 0 else self._default_ttl

            return await self.save(key, updated, ttl)

        except Exception as e:
            logger.error("redis_update_failed", key=key, error=str(e))
            return False

    async def get_ttl(self, key: str) -> int:
        """
        取得 key 的剩餘 TTL

        Args:
            key: 資料鍵值

        Returns:
            剩餘秒數，-1 表示無 TTL，-2 表示不存在
        """
        try:
            redis = get_redis_pool()
            full_key = self._make_key(key)
            return await redis.ttl(full_key)

        except RuntimeError:
            return -2

        except Exception as e:
            logger.error("redis_ttl_failed", key=key, error=str(e))
            return -2

    async def extend_ttl(self, key: str, additional_seconds: int) -> bool:
        """
        延長 TTL

        Args:
            key: 資料鍵值
            additional_seconds: 額外秒數

        Returns:
            是否成功
        """
        try:
            redis = get_redis_pool()
            full_key = self._make_key(key)

            current_ttl = await redis.ttl(full_key)
            if current_ttl < 0:
                return False

            new_ttl = current_ttl + additional_seconds
            await redis.expire(full_key, new_ttl)

            logger.debug("redis_ttl_extended", key=full_key, new_ttl=new_ttl)
            return True

        except Exception as e:
            logger.error("redis_extend_ttl_failed", key=key, error=str(e))
            return False
