# -*- coding: utf-8 -*-
"""
check_modules.py — 检测模块清单与配置渲染

定义本期核心子集的检测模块元数据，提供：
  - get_suite(suite)        按套件返回模块列表
  - get_module(module_id)   查单个模块
  - render_config(target)   用模板 + 目标配置渲染 config.sh
  - ALL_MODULES             全部模块（本期核心子集）
"""

from __future__ import annotations

import logging
import shlex
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional

from .paths import CONFIG_TEMPLATE, COMMON_SH, SYSTEM_ASSETS_DIR, SERVICE_ASSETS_DIR

logger = logging.getLogger("service_monitor.check_modules")

# 套件常量
SUITE_QUICK = "quick"
SUITE_FULL = "full"


@dataclass(frozen=True)
class CheckModule:
    """单个检测模块的元数据。"""
    id: str                # "01_system_basic"
    name: str             # 显示名 "系统基础信息"
    category: str         # "system" | "service"
    suites: set           # {"quick", "full"} 哪些套件包含
    filename: str         # "01_system_basic.sh"

    @property
    def relpath(self) -> str:
        """相对 lib/ 的脚本路径，如 system/01_system_basic.sh"""
        return f"{self.category}/{self.filename}"

    @property
    def asset_path(self) -> Path:
        """本地 assets 中的绝对路径。"""
        base = SYSTEM_ASSETS_DIR if self.category == "system" else SERVICE_ASSETS_DIR
        return base / self.filename


# ============================================================
# 模块清单（本期核心子集）
# ============================================================

ALL_MODULES: List[CheckModule] = [
    # ---- system 类 ----
    CheckModule("01_system_basic", "系统基础信息", "system",
                {SUITE_QUICK, SUITE_FULL}, "01_system_basic.sh"),
    CheckModule("02_cpu_check", "CPU检测", "system",
                {SUITE_QUICK, SUITE_FULL}, "02_cpu_check.sh"),
    CheckModule("03_memory_check", "内存检测", "system",
                {SUITE_QUICK, SUITE_FULL}, "03_memory_check.sh"),
    CheckModule("04_disk_check", "磁盘检测", "system",
                {SUITE_QUICK, SUITE_FULL}, "04_disk_check.sh"),
    # ---- service 类 ----
    CheckModule("20_docker_basic", "Docker基础", "service",
                {SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"),
    CheckModule("22_mysql_basic", "MySQL基础", "service",
                {SUITE_FULL}, "22_mysql_basic.sh"),
    CheckModule("24_redis_basic", "Redis基础", "service",
                {SUITE_FULL}, "24_redis_basic.sh"),
]

_BY_ID: Dict[str, CheckModule] = {m.id: m for m in ALL_MODULES}


def list_modules() -> List[CheckModule]:
    """返回全部模块。"""
    return list(ALL_MODULES)


def get_module(module_id: str) -> Optional[CheckModule]:
    """按 id 查模块，无则 None。"""
    return _BY_ID.get(module_id)


def get_suite(suite: str) -> List[CheckModule]:
    """按套件返回模块列表，按 ALL_MODULES 顺序。

    suite: "quick" | "full" | "all"
    """
    if suite == SUITE_QUICK:
        return [m for m in ALL_MODULES if SUITE_QUICK in m.suites]
    if suite == SUITE_FULL:
        return [m for m in ALL_MODULES if SUITE_FULL in m.suites]
    if suite == "all":
        return list(ALL_MODULES)
    raise ValueError(f"未知套件: {suite}（可选 quick/full/all）")


# ============================================================
# config.sh 渲染
# ============================================================

def render_config(target: dict) -> str:
    """用模板 + 目标配置渲染 config.sh 文本。

    target: 监测目标 dict，可含：
      - container_overrides: {mysql: "umysql-prod", ...}  覆盖默认匹配模式
      - credential_overrides: {mysql_password: "...", redis_password: "..."} 明文（运行时解出）
      - thresholds: {cpu_warning: 90, ...} 覆盖默认阈值
    """
    text = CONFIG_TEMPLATE.read_text(encoding="utf-8")

    # 1) 凭据占位符替换（shlex.quote 输出带引号的安全 shell 值；空串输出 ''）
    creds = target.get("credential_overrides") or {}
    mysql_pw = creds.get("mysql_password", "")
    redis_pw = creds.get("redis_password", "")
    text = text.replace("__MYSQL_PASSWORD__", shlex.quote(mysql_pw))
    text = text.replace("__REDIS_PASSWORD__", shlex.quote(redis_pw))

    # 2) 容器匹配模式覆盖：默认模式已在模板里，这里用 override 改 declare 行
    overrides = target.get("container_overrides") or {}
    for key, pattern in overrides.items():
        # 替换 CONTAINERS[key]="原值" 为新模式
        old_line = f'CONTAINERS[{key}]="{_default_container_pattern(key)}"'
        new_line = f'CONTAINERS[{key}]="{pattern}"'
        if old_line in text:
            text = text.replace(old_line, new_line)
        else:
            # 模板里没有该键声明，追加到 declare 区块末尾（CONFIG= 之后）
            logger.warning("容器模式覆盖 %s 在模板中无对应声明，跳过", key)

    # 3) 阈值覆盖：通过在文件末尾追加 export 覆盖默认值
    thresholds = target.get("thresholds") or {}
    if thresholds:
        override_lines = ["# ===== 目标级阈值覆盖 ====="]
        for k, v in thresholds.items():
            # 仅允许已知阈值键，防注入
            if _is_valid_threshold_key(k):
                override_lines.append(f'export {k.upper()}={shlex.quote(str(v))}')
            else:
                logger.warning("忽略非法阈值键: %s", k)
        text = text + "\n" + "\n".join(override_lines) + "\n"

    return text


def _default_container_pattern(key: str) -> str:
    """模板里默认的容器匹配模式（与 config.sh.template 保持一致）。"""
    defaults = {
        "mysql": "mysql", "redis": "redis", "emqx": "emqx",
        "java": "java|api", "nginx": "nginx",
        "nacos": "nacos", "python": "python",
        "python_voice": "python_voice",
    }
    return defaults.get(key, key)


def _is_valid_threshold_key(key: str) -> bool:
    """阈值键白名单（防注入）。"""
    allowed = {
        "cpu_warning", "cpu_critical",
        "memory_warning", "memory_critical",
        "disk_warning", "disk_critical",
        "thread_warning", "thread_critical",
        "java_log_path", "python_log_path", "nginx_log_path", "nacos_log_path",
    }
    return key.lower() in allowed


# ============================================================
# 资产文件清单（executor 上传用）
# ============================================================

def list_asset_files() -> List[Path]:
    """返回所有需要上传到目标机的资产文件绝对路径。"""
    files = [COMMON_SH, CONFIG_TEMPLATE]
    for m in ALL_MODULES:
        files.append(m.asset_path)
    return files
