"""Nexus Configuration — Pydantic Settings with MySQL overrides

Settings loading priority:
1. .env file (bootstrap — required for DB connection to even work)
2. MySQL settings table (dynamic overrides — loaded at startup via load_settings_from_db)

Immutable settings (not overridable from DB):
  - SECRET_KEY, API_KEY, ENCRYPTION_KEY, DATABASE_URL — must be set in .env

Mutable settings (overridable from DB):
  - system_name/title, pool_size/overflow, redis_url, alert thresholds, Telegram config
"""

import logging
from typing import Optional

from pydantic_settings import BaseSettings, SettingsConfigDict
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger("nexus.config")


class Settings(BaseSettings):
    """Base settings loaded from .env file, overrideable via MySQL settings table"""

    # Brand (configurable in system settings)
    SYSTEM_NAME: str = "Nexus"
    SYSTEM_TITLE: str = "Nexus — 服务器运维管理平台"

    # Server
    HOST: str = "0.0.0.0"
    PORT: int = 8600

    # Deployment
    DEPLOY_PATH: str = "/opt/nexus"  # Base directory on the server
    CORS_ORIGINS: str = ""  # Comma-separated: "https://example.com,http://localhost:8600"
    API_BASE_URL: str = ""  # Public site URL — used for Agent HTTPS scheme (NEXUS_API_BASE_URL)

    # Database (immutable — must be set in .env)
    DATABASE_URL: str = "mysql+aiomysql://root:password@127.0.0.1:3306/nexus"
    # Pool params: install wizard auto-calculates from MySQL max_connections
    # Formula: pool_size = max(20, maxConn * 0.4), overflow = max(20, maxConn * 0.3)
    # MySQL max_connections=400 → pool=160, overflow=120, max=280
    # These defaults match install wizard; override via .env or MySQL settings table
    DB_POOL_SIZE: Optional[int] = 160
    DB_MAX_OVERFLOW: Optional[int] = 120

    # Security (immutable — changing breaks encryption/auth)
    SECRET_KEY: str = ""  # Must be set in .env; empty = startup error
    API_KEY: str = ""
    ENCRYPTION_KEY: str = ""

    # Redis (mutable — can be changed from settings UI)
    REDIS_URL: str = "redis://127.0.0.1:6379/0"

    # SSH
    SSH_STRICT_HOST_CHECKING: str = "true"

    # JWT (mutable via .env / settings table — not immutable secrets)
    JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
    JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 30

    # Health Check / Heartbeat
    HEALTH_CHECK_INTERVAL: int = 60  # seconds — Agent heartbeat interval

    # Script exec — built-in batching (servers per wave; avoids long single HTTP)
    SCRIPT_EXEC_BATCH_SIZE: int = 50
    SCRIPT_EXEC_CONCURRENCY: int = 10  # max parallel SSH exec calls per batch

    # Alert thresholds (mutable — configurable from settings UI)
    CPU_ALERT_THRESHOLD: int = 80
    MEM_ALERT_THRESHOLD: int = 80
    DISK_ALERT_THRESHOLD: int = 80

    # Telegram (mutable — configurable from settings UI)
    TELEGRAM_BOT_TOKEN: str = ""
    TELEGRAM_CHAT_ID: str = ""

    # Login IP allowlist master switch — "true" = enforce allowlist, "false" = allow all
    LOGIN_ALLOWLIST_ENABLED: str = "false"
    # Subscription URL for auto-refreshing subscription IPs (every 2h)
    LOGIN_SUBSCRIPTION_URL: str = ""
    # IPs from subscription (replaced entirely on each refresh)
    LOGIN_SUBSCRIPTION_IPS: str = ""
    # Manually added IPs (never touched by auto-refresh)
    LOGIN_MANUAL_IPS: str = ""
    # Legacy: combined view used by auth check = subscription + manual
    LOGIN_ALLOWED_IPS: str = ""

    # Notification toggles — each alert type can be independently enabled/disabled
    # Stored as "true"/"false" strings in MySQL settings table
    NOTIFY_ALERT_CPU: str = "true"       # CPU 超阈值告警
    NOTIFY_ALERT_MEM: str = "true"       # 内存超阈值告警
    NOTIFY_ALERT_DISK: str = "true"      # 磁盘超阈值告警
    NOTIFY_RECOVERY: str = "true"        # 资源恢复正常通知
    NOTIFY_TIME_DRIFT: str = "true"      # 时钟偏差严重告警（≥60s）
    NOTIFY_SYSTEM_REDIS: str = "true"    # Redis 连接异常/恢复
    NOTIFY_SYSTEM_MYSQL: str = "true"    # MySQL 连接异常/恢复
    NOTIFY_RESTART: str = "true"         # Nexus 后端重启通知

    # extra=ignore: .env 中与 MCP 共存的 MYSQL_* / NEXUS_API_BASE_URL 等不进入 Settings
    model_config = SettingsConfigDict(
        env_prefix="NEXUS_",
        env_file=".env",
        extra="ignore",
    )

    # ── MySQL settings table override ──

    # Mapping: MySQL settings.key → Settings attribute name
    # Only mutable settings are listed here; immutable ones (SECRET_KEY, API_KEY, etc.) are NOT
    DB_OVERRIDE_MAP: dict = {
        "system_name": "SYSTEM_NAME",
        "system_title": "SYSTEM_TITLE",
        "db_pool_size": "DB_POOL_SIZE",
        "db_max_overflow": "DB_MAX_OVERFLOW",
        "redis_url": "REDIS_URL",
        "heartbeat_timeout": "HEALTH_CHECK_INTERVAL",
        "cpu_alert_threshold": "CPU_ALERT_THRESHOLD",
        "mem_alert_threshold": "MEM_ALERT_THRESHOLD",
        "disk_alert_threshold": "DISK_ALERT_THRESHOLD",
        "telegram_bot_token": "TELEGRAM_BOT_TOKEN",
        "telegram_chat_id": "TELEGRAM_CHAT_ID",
        "login_allowlist_enabled": "LOGIN_ALLOWLIST_ENABLED",
        "login_subscription_url": "LOGIN_SUBSCRIPTION_URL",
        "login_subscription_ips": "LOGIN_SUBSCRIPTION_IPS",
        "login_manual_ips": "LOGIN_MANUAL_IPS",
        "login_allowed_ips": "LOGIN_ALLOWED_IPS",
        "api_base_url": "API_BASE_URL",
        "jwt_access_token_expire_minutes": "JWT_ACCESS_TOKEN_EXPIRE_MINUTES",
        "jwt_refresh_token_expire_days": "JWT_REFRESH_TOKEN_EXPIRE_DAYS",
        # Notification toggles
        "notify_alert_cpu": "NOTIFY_ALERT_CPU",
        "notify_alert_mem": "NOTIFY_ALERT_MEM",
        "notify_alert_disk": "NOTIFY_ALERT_DISK",
        "notify_recovery": "NOTIFY_RECOVERY",
        "notify_time_drift": "NOTIFY_TIME_DRIFT",
        "notify_system_redis": "NOTIFY_SYSTEM_REDIS",
        "notify_system_mysql": "NOTIFY_SYSTEM_MYSQL",
        "notify_restart": "NOTIFY_RESTART",
    }

    # Settings that require int conversion from DB string values
    INT_SETTINGS: set = {
        "DB_POOL_SIZE", "DB_MAX_OVERFLOW", "HEALTH_CHECK_INTERVAL",
        "JWT_ACCESS_TOKEN_EXPIRE_MINUTES", "JWT_REFRESH_TOKEN_EXPIRE_DAYS",
        "CPU_ALERT_THRESHOLD", "MEM_ALERT_THRESHOLD", "DISK_ALERT_THRESHOLD",
    }

    async def load_settings_from_db(self, session: AsyncSession) -> int:
        """Override mutable settings from MySQL settings table.

        Called during app startup (after init_db) to apply dynamic config.
        Immutable settings (SECRET_KEY, API_KEY, ENCRYPTION_KEY, DATABASE_URL)
        are NOT overridable — they stay as .env values.

        Returns: number of settings overridden from DB
        """
        from server.domain.models import Setting

        try:
            result = await session.execute(select(Setting))
            rows = result.scalars().all()

            overridden = 0
            for row in rows:
                attr_name = self.DB_OVERRIDE_MAP.get(row.key)
                if attr_name and hasattr(self, attr_name):
                    value = row.value
                    # Skip null values — .env value takes precedence
                    if value is None:
                        continue
                    # Type conversion
                    if attr_name in self.INT_SETTINGS:
                        try:
                            value = int(value)
                        except (ValueError, TypeError):
                            logger.warning(f"Invalid int value for {row.key}: {value}, skipping")
                            continue
                    setattr(self, attr_name, value)
                    overridden += 1

            logger.info(
                f"Settings loaded from DB: {len(rows)} keys read, "
                f"{overridden} overridden"
            )
            return overridden
        except Exception as e:
            logger.error(f"Failed to load settings from DB: {e}")
            return 0

    async def ensure_api_base_url_in_db(self, session: AsyncSession) -> None:
        """Migrate api_base_url from .env to MySQL settings table if missing.

        Ensures existing installations (where .env has NEXUS_API_BASE_URL
        but the MySQL settings table was created before api_base_url was
        added to DB_OVERRIDE_MAP) have the value available for the settings API.
        """
        from server.domain.models import Setting
        from sqlalchemy import select as sa_select

        try:
            result = await session.execute(
                sa_select(Setting).where(Setting.key == "api_base_url")
            )
            existing = result.scalar_one_or_none()
            if existing is None and self.API_BASE_URL:
                new_setting = Setting(key="api_base_url", value=self.API_BASE_URL)
                session.add(new_setting)
                await session.commit()
                logger.info(f"Migrated api_base_url to MySQL settings table: {self.API_BASE_URL}")
        except Exception as e:
            logger.warning(f"Could not migrate api_base_url to DB: {e}")


settings = Settings()