# -*- coding: utf-8 -*-
# Copyright 2025 AgentUnion Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
集中式 TLS/SSL 配置模块

所有 TLS 决策（是否校验、用哪个 CA、是否禁用告警）只在此模块中做，
调用方只需使用以下 API：

- init_tls(app_path, debug, ca_bundle, verify)  初始化（通常在 AgentID.__init__ 中调用一次）
- requests_verify()          -> bool | str       给 requests 库用
- ssl_context()              -> ssl.SSLContext    给 websockets / urllib 用
- websocket_sslopt()         -> dict             给 websocket-client 用
- aiohttp_ssl()              -> SSLContext | bool 给 aiohttp 用
- allow_insecure()           -> bool             查询当前是否处于不安全模式
- ca_bundle_path()           -> str | None       获取 CA bundle 路径
- install_warning_filters()                      仅在开发模式+显式允许时禁用 InsecureRequestWarning

配置优先级：显式参数 > 环境变量 > 默认值

环境变量：
- AGENTCP_TLS_VERIFY           是否验证证书 (默认 true)
- AGENTCP_TLS_ALLOW_INSECURE   是否允许不安全模式 (默认 false)
- AGENTCP_TLS_CA_BUNDLE        自定义 CA bundle 路径
- AGENTCP_TLS_DISABLE_WARNINGS 是否禁用 InsecureRequestWarning (默认 false)
"""

import os
import ssl
import threading
from dataclasses import dataclass
from typing import Optional, Union

from agentcp.base.log import log_info, log_warning, log_error


# ==================== 内部状态 ====================

# 使用 RLock 以支持同一线程内的嵌套调用（如 ssl_context -> _state -> init_tls）
_STATE_LOCK = threading.RLock()
_STATE: Optional["TLSState"] = None
_SSL_CTX_CACHE: Optional[ssl.SSLContext] = None
_WS_SSLOPT_CACHE: Optional[dict] = None


@dataclass
class TLSState:
    """TLS 配置状态"""
    verify: bool
    allow_insecure: bool
    ca_bundle: Optional[str]
    debug: bool


# ==================== 工具函数 ====================

def _env_bool(name: str, default: bool) -> bool:
    """从环境变量读取布尔值"""
    val = os.getenv(name)
    if val is None:
        return default
    return val.strip().lower() in ("1", "true", "yes", "on")


def _strip_bom(text: str) -> str:
    """移除 UTF-8 BOM（Windows 记事本编辑过的文件常见）"""
    if text.startswith('\ufeff'):
        return text[1:]
    return text


def _build_ca_bundle(app_path: Optional[str], override_path: Optional[str]) -> Optional[str]:
    """构建 CA bundle 文件

    将内置根证书 + Certs/root 目录下的 .crt 文件合并为一个 PEM bundle。
    注意：只读取 CARoot 内置证书，不调用 set_ca_root_crt，避免覆盖单例状态。

    Args:
        app_path: 应用数据目录
        override_path: 用户指定的 CA bundle 路径（优先使用）

    Returns:
        bundle 文件路径，或 None
    """
    if override_path:
        if os.path.isfile(override_path):
            return override_path
        log_warning(f"[TLS] CA bundle path does not exist or is not a file: {override_path}, falling back to default")

    if not app_path:
        return None

    root_dir = os.path.join(app_path, "Certs", "root")
    os.makedirs(root_dir, exist_ok=True)
    bundle = os.path.join(root_dir, "ca-bundle.pem")

    parts = []

    # 收集系统 CA 证书（certifi 是 requests 的依赖，一定存在）
    try:
        import certifi
        with open(certifi.where(), "r", encoding="utf-8") as f:
            system_cas = _strip_bom(f.read()).strip()
            if system_cas:
                parts.append(system_cas)
    except Exception as e:
        log_warning(f"[TLS] 加载系统 CA 证书失败: {e}")

    # 收集内置根证书（只读取内置列表，不修改 CARoot 单例的 __ca_root_path）
    try:
        from agentcp.ca.ca_root import CARoot
        ca = CARoot()
        # 只读取内置证书数量（__ca_crt 列表），不调用 set_ca_root_crt
        for i in range(len(ca._CARoot__ca_crt)):
            pem = ca._CARoot__ca_crt[i]
            if pem:
                parts.append(_strip_bom(pem).strip())
    except Exception as e:
        log_warning(f"[TLS] 加载内置根证书失败: {e}")

    # 收集目录内 .crt 文件（排除 bundle 自身）
    try:
        for name in sorted(os.listdir(root_dir)):
            if name.endswith(".crt") and name != "ca-bundle.pem":
                filepath = os.path.join(root_dir, name)
                with open(filepath, "r", encoding="utf-8") as f:
                    content = _strip_bom(f.read()).strip()
                    if content and content not in parts:
                        parts.append(content)
    except Exception as e:
        log_warning(f"[TLS] 扫描证书目录失败: {e}")

    if parts:
        try:
            with open(bundle, "w", encoding="utf-8", newline="\n") as f:
                f.write("\n".join(parts) + "\n")
            return bundle
        except Exception as e:
            log_error(f"[TLS] 写入 CA bundle 失败: {e}")

    return None


# ==================== 公开 API ====================

def init_tls(
    app_path: Optional[str] = None,
    debug: bool = False,
    ca_bundle: Optional[str] = None,
    verify: Optional[bool] = None,
) -> None:
    """初始化 TLS 配置（通常只需调用一次）

    Args:
        app_path: 应用数据目录（用于定位 Certs/root）
        debug: 是否为调试模式
        ca_bundle: 自定义 CA bundle 路径
        verify: 是否验证证书（None 表示从环境变量读取）
    """
    global _STATE, _SSL_CTX_CACHE, _WS_SSLOPT_CACHE
    with _STATE_LOCK:
        _SSL_CTX_CACHE = None
        _WS_SSLOPT_CACHE = None

        insecure = _env_bool("AGENTCP_TLS_ALLOW_INSECURE", False)
        if verify is None:
            verify = _env_bool("AGENTCP_TLS_VERIFY", True)

        bundle_override = ca_bundle or os.getenv("AGENTCP_TLS_CA_BUNDLE")
        bundle = _build_ca_bundle(app_path, bundle_override)

        _STATE = TLSState(
            verify=bool(verify),
            allow_insecure=insecure,
            ca_bundle=bundle,
            debug=debug,
        )

        # 初始化时打印一次模式信息
        if not verify and insecure:
            log_warning("[TLS] Insecure mode enabled: certificate verification is DISABLED")
        elif not verify and not insecure:
            log_warning(
                "[TLS] AGENTCP_TLS_VERIFY is disabled, but AGENTCP_TLS_ALLOW_INSECURE is not set. "
                "Certificate verification remains ENABLED for safety. "
                "Set AGENTCP_TLS_ALLOW_INSECURE=1 to explicitly allow insecure mode."
            )
        elif bundle:
            log_info(f"[TLS] Certificate verification enabled, CA bundle: {bundle}")
        else:
            log_info("[TLS] Certificate verification enabled, using system CA store")


def _state() -> TLSState:
    """获取当前 TLS 状态（线程安全，double-check locking）

    使用 RLock 保证 ssl_context() -> _state() -> init_tls() 嵌套调用不会死锁。
    """
    global _STATE
    if _STATE is None:
        with _STATE_LOCK:
            if _STATE is None:
                init_tls()
    return _STATE


def allow_insecure() -> bool:
    """当前是否处于不安全模式（verify=False 且 allow_insecure=True）"""
    s = _state()
    return (not s.verify) and s.allow_insecure


def ca_bundle_path() -> Optional[str]:
    """获取 CA bundle 路径"""
    return _state().ca_bundle


def requests_verify() -> Union[bool, str]:
    """获取 requests 库的 verify 参数

    Returns:
        - False: 不安全模式，跳过验证
        - str: CA bundle 路径
        - True: 使用系统默认 CA
    """
    s = _state()
    if (not s.verify) and s.allow_insecure:
        return False
    return s.ca_bundle if s.ca_bundle else True


def ssl_context() -> ssl.SSLContext:
    """获取 SSLContext（带缓存）

    用于 websockets 库和 urllib。
    """
    global _SSL_CTX_CACHE
    if _SSL_CTX_CACHE is not None:
        return _SSL_CTX_CACHE

    with _STATE_LOCK:
        if _SSL_CTX_CACHE is not None:
            return _SSL_CTX_CACHE

        s = _state()
        if (not s.verify) and s.allow_insecure:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
        elif s.ca_bundle:
            ctx = ssl.create_default_context(cafile=s.ca_bundle)
        else:
            ctx = ssl.create_default_context()

        _SSL_CTX_CACHE = ctx
        return ctx


def aiohttp_ssl() -> Union[ssl.SSLContext, bool]:
    """获取 aiohttp 的 ssl 参数

    Returns:
        - False: 不安全模式
        - SSLContext: 正常验证
    """
    if allow_insecure():
        return False
    return ssl_context()


def websocket_sslopt() -> dict:
    """获取 websocket-client 库的 sslopt 参数（带缓存）"""
    global _WS_SSLOPT_CACHE
    if _WS_SSLOPT_CACHE is not None:
        return _WS_SSLOPT_CACHE

    with _STATE_LOCK:
        if _WS_SSLOPT_CACHE is not None:
            return _WS_SSLOPT_CACHE

        if allow_insecure():
            opt = {
                "cert_reqs": ssl.CERT_NONE,
                "check_hostname": False,
            }
        else:
            ca = ca_bundle_path()
            if ca:
                opt = {
                    "cert_reqs": ssl.CERT_REQUIRED,
                    "check_hostname": True,
                    "ca_certs": ca,
                }
            else:
                opt = {
                    "cert_reqs": ssl.CERT_REQUIRED,
                    "check_hostname": True,
                }

        _WS_SSLOPT_CACHE = opt
        return opt


def install_warning_filters() -> None:
    """在不安全模式下禁用 InsecureRequestWarning

    仅当 allow_insecure=True 且 AGENTCP_TLS_DISABLE_WARNINGS=True 时生效。
    """
    if allow_insecure() and _env_bool("AGENTCP_TLS_DISABLE_WARNINGS", False):
        try:
            import urllib3
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
            log_warning("[TLS] InsecureRequestWarning has been suppressed")
        except Exception:
            pass
