"""
动态配置管理器（全局单例）

职责：
1. 维护配置的内存缓存
2. Redis 读写
3. Pub/Sub + 轮询更新缓存
4. 提供统一的配置读取接口

使用方式：
    # Worker 侧初始化
    dynamic_config = DynamicConfig(redis_client, redis_prefix)
    await dynamic_config.start()

    # 获取配置
    rate_limit = dynamic_config.get_rate_limit("process_order")
    retry_config = dynamic_config.get_retry_config("process_order")
    system_value = dynamic_config.get_system_config("ddsketch_relative_accuracy", 0.05)

    # WebUI 侧写入配置（通过静态方法）
    await DynamicConfig.set_task_config_to_redis(redis_client, prefix, task_name, configs)
    await DynamicConfig.set_system_config_to_redis(redis_client, prefix, key, value)

Redis 存储结构：
    {prefix}:CONFIG:TASK:{task_name}  # Hash 类型，存储任务配置
    {prefix}:CONFIG:SYSTEM            # Hash 类型，存储系统配置
    {prefix}:CONFIG:VERSION           # String 类型，版本号
    {prefix}:CONFIG:TASK:INDEX        # Set 类型，任务名索引

Pub/Sub Channel：
    {prefix}:CONFIG:CHANGE            # 配置变更通知
"""

import asyncio
import json
import logging
from typing import Any, Dict, Optional, List, TYPE_CHECKING

if TYPE_CHECKING:
    from redis.asyncio import Redis

logger = logging.getLogger('app')


SYSTEM_CONFIG_DEFAULTS = {
    'ddsketch_relative_accuracy': 0.05,
    'metrics_retention_days': 30,
    'log_level': 'INFO',
}


class DynamicConfig:
    """
    动态配置管理器（全局单例）

    职责：
    1. 维护配置的内存缓存
    2. Redis 读写
    3. Pub/Sub + 轮询更新缓存
    4. 提供统一的配置读取接口
    """

    _instance: Optional['DynamicConfig'] = None  

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(
        self,
        redis_client: 'Redis',
        redis_prefix: str = "jettask",
        sync_interval: int = 30
    ):
        if self._initialized:
            return

        self._redis = redis_client
        self._prefix = redis_prefix
        self._sync_interval = sync_interval

        self._task_configs: Dict[str, Dict[str, Any]] = {}
        self._system_configs: Dict[str, Any] = {}
        self._version: int = 0

        self._sync_task: Optional[asyncio.Task] = None
        self._running = False

        self._initialized = True

    @classmethod
    def get_instance(cls) -> Optional['DynamicConfig']:
        return cls._instance

    @classmethod
    def reset_instance(cls):
        cls._instance = None


    def _get_task_config_key(self, task_name: str) -> str:
        return f"{self._prefix}:CONFIG:TASK:{task_name}"

    def _get_system_config_key(self) -> str:
        return f"{self._prefix}:CONFIG:SYSTEM"

    def _get_version_key(self) -> str:
        return f"{self._prefix}:CONFIG:VERSION"

    def _get_task_index_key(self) -> str:
        return f"{self._prefix}:CONFIG:TASK:INDEX"

    @staticmethod
    def get_task_config_key(prefix: str, task_name: str) -> str:
        return f"{prefix}:CONFIG:TASK:{task_name}"

    @staticmethod
    def get_system_config_key(prefix: str) -> str:
        return f"{prefix}:CONFIG:SYSTEM"

    @staticmethod
    def get_version_key(prefix: str) -> str:
        return f"{prefix}:CONFIG:VERSION"

    @staticmethod
    def get_task_index_key(prefix: str) -> str:
        return f"{prefix}:CONFIG:TASK:INDEX"

    @staticmethod
    def get_config_change_channel(prefix: str) -> str:
        return f"{prefix}:CONFIG:CHANGE"


    async def start(self):
        if self._running:
            logger.warning("DynamicConfig already running")
            return

        self._running = True

        await self._load_all_configs()

        self._sync_task = asyncio.create_task(self._sync_loop())

        logger.info(
            f"DynamicConfig started, loaded {len(self._task_configs)} task configs, "
            f"{len(self._system_configs)} system configs"
        )

    async def stop(self):
        self._running = False
        if self._sync_task:
            self._sync_task.cancel()
            try:
                await self._sync_task
            except asyncio.CancelledError:
                pass
        logger.info("DynamicConfig stopped")


    def get_rate_limit(self, task_name: str) -> Optional[Dict[str, Any]]:
        config = self._task_configs.get(task_name, {})
        rate_limit_type = config.get("rate_limit_type")

        if not rate_limit_type:
            return None

        if rate_limit_type == "qps":
            return {
                "type": "qps",
                "qps": int(config.get("rate_limit_qps", 10)),
                "window_size": float(config.get("rate_limit_window_size", 1.0))
            }
        elif rate_limit_type == "concurrency":
            return {
                "type": "concurrency",
                "max_concurrency": int(config.get("rate_limit_max_concurrency", 10))
            }

        return None

    def get_retry_config(self, task_name: str) -> Dict[str, Any]:
        config = self._task_configs.get(task_name, {})

        result = {}
        if "max_retries" in config:
            result["max_retries"] = int(config["max_retries"])
        if "retry_delay" in config:
            result["retry_delay"] = int(config["retry_delay"])
        if "retry_backoff" in config:
            result["retry_backoff"] = config["retry_backoff"] in (True, "true", "True", "1")
        if "retry_backoff_max" in config:
            result["retry_backoff_max"] = int(config["retry_backoff_max"])
        if "retry_on_exceptions" in config:
            val = config["retry_on_exceptions"]
            if isinstance(val, str):
                try:
                    result["retry_on_exceptions"] = json.loads(val)
                except json.JSONDecodeError:
                    result["retry_on_exceptions"] = [val]
            else:
                result["retry_on_exceptions"] = val

        return result

    def get_task_config(self, task_name: str) -> Dict[str, Any]:
        return self._task_configs.get(task_name, {}).copy()

    def get_system_config(self, key: str, default: Any = None) -> Any:
        value = self._system_configs.get(key)
        if value is not None:
            return value
        if default is not None:
            return default
        return SYSTEM_CONFIG_DEFAULTS.get(key)

    def get_all_task_names(self) -> List[str]:
        return list(self._task_configs.keys())

    def get_all_system_configs(self) -> Dict[str, Any]:
        result = dict(SYSTEM_CONFIG_DEFAULTS)
        result.update(self._system_configs)
        return result


    async def handle_config_change(self, message: Dict[str, Any]):
        try:
            config_type = message.get("type")
            action = message.get("action", "update")

            logger.info(f"[DynamicConfig] 收到配置变更通知: {message}")

            if config_type == "task":
                task_name = message.get("task_name")
                if task_name:
                    if action == "delete":
                        self._task_configs.pop(task_name, None)
                        logger.info(f"[DynamicConfig] 删除任务配置: {task_name}")
                    else:
                        await self._load_task_config(task_name)
                        logger.info(f"[DynamicConfig] 更新任务配置: {task_name}")

            elif config_type == "system":
                await self._load_system_configs()
                logger.info("[DynamicConfig] 更新系统配置")

        except Exception as e:
            logger.error(f"[DynamicConfig] 处理配置变更失败: {e}")


    async def _sync_loop(self):
        while self._running:
            try:
                await asyncio.sleep(self._sync_interval)
                if not self._running:
                    break

                await self._check_and_sync()

            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"[DynamicConfig] 同步循环异常: {e}")
                await asyncio.sleep(5)  

    async def _check_and_sync(self):
        try:
            version_key = self._get_version_key()
            remote_version = await self._redis.get(version_key)
            remote_version = int(remote_version) if remote_version else 0

            if remote_version != self._version:
                logger.debug(f"[DynamicConfig] 版本变更 {self._version} -> {remote_version}，刷新配置")
                await self._load_all_configs()
                self._version = remote_version

        except Exception as e:
            logger.error(f"[DynamicConfig] 检查版本失败: {e}")

    async def _load_all_configs(self):
        await self._load_system_configs()
        await self._load_all_task_configs()

        try:
            version_key = self._get_version_key()
            version = await self._redis.get(version_key)
            self._version = int(version) if version else 0
        except Exception as e:
            logger.error(f"[DynamicConfig] 获取版本号失败: {e}")

    async def _load_system_configs(self):
        try:
            key = self._get_system_config_key()
            data = await self._redis.hgetall(key)

            self._system_configs = {}
            for k, v in data.items():
                k = k.decode() if isinstance(k, bytes) else k
                v = v.decode() if isinstance(v, bytes) else v
                try:
                    self._system_configs[k] = json.loads(v)
                except (json.JSONDecodeError, TypeError):
                    self._system_configs[k] = v

            logger.debug(f"[DynamicConfig] 加载了 {len(self._system_configs)} 个系统配置")

        except Exception as e:
            logger.error(f"[DynamicConfig] 加载系统配置失败: {e}")

    async def _load_all_task_configs(self):
        try:
            index_key = self._get_task_index_key()
            task_names = await self._redis.smembers(index_key)

            self._task_configs = {}
            for task_name in task_names:
                if isinstance(task_name, bytes):
                    task_name = task_name.decode()
                await self._load_task_config(task_name)

            logger.debug(f"[DynamicConfig] 加载了 {len(self._task_configs)} 个任务配置")

        except Exception as e:
            logger.error(f"[DynamicConfig] 加载任务配置失败: {e}")

    async def _load_task_config(self, task_name: str):
        try:
            key = self._get_task_config_key(task_name)
            data = await self._redis.hgetall(key)

            if not data:
                self._task_configs.pop(task_name, None)
                return

            config = {}
            for k, v in data.items():
                k = k.decode() if isinstance(k, bytes) else k
                v = v.decode() if isinstance(v, bytes) else v
                config[k] = v

            self._task_configs[task_name] = config
            logger.info(f"[DynamicConfig] 加载任务配置: {task_name} = {config}")

        except Exception as e:
            logger.error(f"[DynamicConfig] 加载任务 {task_name} 配置失败: {e}")


    @staticmethod
    async def set_task_config_to_redis(
        redis_client: 'Redis',
        prefix: str,
        task_name: str,
        configs: Dict[str, Any],
        publish_change: bool = True
    ):
        config_key = DynamicConfig.get_task_config_key(prefix, task_name)
        index_key = DynamicConfig.get_task_index_key(prefix)
        version_key = DynamicConfig.get_version_key(prefix)

        flat_config = {}

        if 'rate_limit' in configs:
            rate_limit = configs['rate_limit']
            rate_type = rate_limit.get('type', 'qps')
            flat_config['rate_limit_type'] = rate_type

            if rate_type == 'qps':
                flat_config['rate_limit_qps'] = str(rate_limit.get('qps', 10))
                flat_config['rate_limit_window_size'] = str(rate_limit.get('window_size', 1.0))
            elif rate_type == 'concurrency':
                flat_config['rate_limit_max_concurrency'] = str(rate_limit.get('max_concurrency', 10))

        if 'max_retries' in configs:
            flat_config['max_retries'] = str(configs['max_retries'])

        if 'retry_delay' in configs:
            flat_config['retry_delay'] = str(configs['retry_delay'])

        if 'retry_backoff' in configs:
            backoff = configs['retry_backoff']
            if isinstance(backoff, dict):
                flat_config['retry_backoff'] = 'true' if backoff.get('type') == 'exponential' else 'false'
                if 'max' in backoff:
                    flat_config['retry_backoff_max'] = str(backoff['max'])
            elif isinstance(backoff, bool):
                flat_config['retry_backoff'] = 'true' if backoff else 'false'

        if 'retry_on_exceptions' in configs:
            exceptions = configs['retry_on_exceptions']
            if isinstance(exceptions, list):
                flat_config['retry_on_exceptions'] = json.dumps(exceptions)
            else:
                flat_config['retry_on_exceptions'] = str(exceptions)

        if flat_config:
            old_format_keys = ['rate_limit', 'retry_backoff']
            await redis_client.hdel(config_key, *old_format_keys)

            current_version = await redis_client.hget(config_key, '_version')
            new_version = int(current_version) + 1 if current_version else 1
            flat_config['_version'] = str(new_version)

            await redis_client.hset(config_key, mapping=flat_config)
            await redis_client.sadd(index_key, task_name)
            await redis_client.incr(version_key)  

            if publish_change:
                channel = DynamicConfig.get_config_change_channel(prefix)
                message = json.dumps({
                    'type': 'task',
                    'action': 'update',
                    'task_name': task_name
                })
                await redis_client.publish(channel, message)

            logger.debug(f"[DynamicConfig] 任务配置已写入 Redis: {task_name} = {flat_config}")

    @staticmethod
    async def delete_task_config_from_redis(
        redis_client: 'Redis',
        prefix: str,
        task_name: str,
        fields: Optional[List[str]] = None
    ):
        config_key = DynamicConfig.get_task_config_key(prefix, task_name)
        index_key = DynamicConfig.get_task_index_key(prefix)
        version_key = DynamicConfig.get_version_key(prefix)

        if fields:
            await redis_client.hdel(config_key, *fields)
        else:
            await redis_client.delete(config_key)
            await redis_client.srem(index_key, task_name)

        remaining = await redis_client.hlen(config_key)
        if remaining == 0:
            await redis_client.delete(config_key)
            await redis_client.srem(index_key, task_name)

        await redis_client.incr(version_key)

        channel = DynamicConfig.get_config_change_channel(prefix)
        message = json.dumps({
            'type': 'task',
            'action': 'delete' if not fields else 'update',
            'task_name': task_name
        })
        await redis_client.publish(channel, message)

        logger.debug(f"[DynamicConfig] 任务配置已从 Redis 删除: {task_name}")

    @staticmethod
    async def delete_rate_limit_from_redis(
        redis_client: 'Redis',
        prefix: str,
        task_name: str
    ):
        await DynamicConfig.delete_task_config_from_redis(
            redis_client, prefix, task_name,
            fields=[
                "rate_limit_type", "rate_limit_qps",
                "rate_limit_window_size", "rate_limit_max_concurrency"
            ]
        )

    @staticmethod
    async def set_system_config_to_redis(
        redis_client: 'Redis',
        prefix: str,
        key: str,
        value: Any,
        publish_change: bool = True
    ):
        config_key = DynamicConfig.get_system_config_key(prefix)
        version_key = DynamicConfig.get_version_key(prefix)

        value_str = json.dumps(value) if not isinstance(value, str) else value
        await redis_client.hset(config_key, key, value_str)
        await redis_client.incr(version_key)

        if publish_change:
            channel = DynamicConfig.get_config_change_channel(prefix)
            message = json.dumps({
                'type': 'system',
                'action': 'update',
                'key': key
            })
            await redis_client.publish(channel, message)

        logger.debug(f"[DynamicConfig] 系统配置已写入 Redis: {key} = {value}")

    @staticmethod
    async def delete_system_config_from_redis(
        redis_client: 'Redis',
        prefix: str,
        key: str
    ):
        config_key = DynamicConfig.get_system_config_key(prefix)
        version_key = DynamicConfig.get_version_key(prefix)

        await redis_client.hdel(config_key, key)
        await redis_client.incr(version_key)

        channel = DynamicConfig.get_config_change_channel(prefix)
        message = json.dumps({
            'type': 'system',
            'action': 'delete',
            'key': key
        })
        await redis_client.publish(channel, message)

        logger.debug(f"[DynamicConfig] 系统配置已从 Redis 删除: {key}")

    def __repr__(self) -> str:
        return (
            f"<DynamicConfig "
            f"task_configs={len(self._task_configs)} "
            f"system_configs={len(self._system_configs)} "
            f"version={self._version} "
            f"running={self._running}>"
        )


__all__ = ['DynamicConfig', 'SYSTEM_CONFIG_DEFAULTS']
