# -*- coding: utf-8 -*-
"""
service_manage.py — 服务管理模块业务层

功能：
  1. 目标配置：管理员维护对接的目标服务器（如 5.44 统一管理平台）
     凭据：密码 Fernet 加密存储（复用 service_monitor.utils.crypto）
  2. 项目信息：保存/读取授权项目信息
  3. 授权文件：上传/下载/状态查询（本地暂存模式）

与 service_monitor 的目标完全独立（两套数据、两套存储），互不干扰。
"""

from __future__ import annotations

import json
import logging
import re
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional

from utils.paths import (
    SERVICE_MANAGE_DATA_DIR,
    MANAGE_TARGETS_FILE,
    MANAGE_PROJECTS_FILE,
    MANAGE_UPLOADS_DIR,
)

logger = logging.getLogger("service_manage")

# 校验白名单
_HOST_RE = re.compile(r"^[A-Za-z0-9.\-]{1,255}$")
_NAME_RE = re.compile(r"^.{1,64}$")


# ============================================================
# 密码加密工具（复用 service_monitor 的 crypto）
# ============================================================

try:
    from service_monitor.utils.crypto import encrypt_password, decrypt_password, is_encrypted
except ImportError:
    logger.warning("无法导入 crypto 模块，将使用明文存储（仅开发环境）")

    def encrypt_password(plain: str) -> str:
        return plain

    def decrypt_password(token: str) -> str:
        return token

    def is_encrypted(value: str) -> bool:
        return False


# ============================================================
# 数据读写
# ============================================================

def _ensure_dirs():
    """确保数据目录存在。"""
    SERVICE_MANAGE_DATA_DIR.mkdir(parents=True, exist_ok=True)
    MANAGE_UPLOADS_DIR.mkdir(parents=True, exist_ok=True)


def _load_targets() -> list:
    """加载目标列表。"""
    _ensure_dirs()
    if not MANAGE_TARGETS_FILE.exists():
        return []
    try:
        data = json.loads(MANAGE_TARGETS_FILE.read_text(encoding="utf-8"))
        return data if isinstance(data, list) else []
    except (json.JSONDecodeError, OSError) as e:
        logger.error("加载 targets.json 失败: %s", e)
        return []


def _save_targets(targets: list) -> None:
    """保存目标列表。"""
    _ensure_dirs()
    MANAGE_TARGETS_FILE.write_text(
        json.dumps(targets, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def _now() -> str:
    return datetime.now().strftime("%Y-%m-%dT%H:%M:%S")


# ============================================================
# 目标配置 CRUD
# ============================================================

def list_targets() -> list:
    """列出全部目标（脱敏：密码→has_password 布尔）。"""
    targets = _load_targets()
    result = []
    for t in targets:
        result.append({
            "id": t["id"],
            "name": t["name"],
            "host": t["host"],
            "port": t.get("port", 443),
            "protocol": t.get("protocol", "https"),
            "auth_type": t.get("auth_type", "password"),
            "username": t.get("username", ""),
            "has_password": bool(t.get("password_enc")),
            "connected": t.get("connected", False),
            "created_at": t.get("created_at"),
            "updated_at": t.get("updated_at"),
        })
    return result


def get_target(target_id: str) -> Optional[dict]:
    """获取单个目标原始记录（含密文，内部用）。"""
    for t in _load_targets():
        if t["id"] == target_id:
            return t
    return None


def get_target_view(target_id: str) -> Optional[dict]:
    """获取单个目标对外视图（脱敏）。"""
    t = get_target(target_id)
    if not t:
        return None
    return {
        "id": t["id"],
        "name": t["name"],
        "host": t["host"],
        "port": t.get("port", 443),
        "protocol": t.get("protocol", "https"),
        "auth_type": t.get("auth_type", "password"),
        "username": t.get("username", ""),
        "has_password": bool(t.get("password_enc")),
        "connected": t.get("connected", False),
        "created_at": t.get("created_at"),
        "updated_at": t.get("updated_at"),
    }


def create_target(data: dict) -> dict:
    """新增目标。

    字段：name, host, port, protocol, auth_type, username, password
    """
    name = (data.get("name") or "").strip()
    host = (data.get("host") or "").strip()
    port = data.get("port", 443)
    protocol = data.get("protocol", "https")
    auth_type = data.get("auth_type", "password")
    username = (data.get("username") or "").strip()
    password = data.get("password") or ""

    if not name or not _NAME_RE.match(name):
        raise ValueError("目标名称不能为空（最长 64 字符）")
    if not host or not _HOST_RE.match(host):
        raise ValueError("主机地址非法")
    if protocol not in ("http", "https"):
        raise ValueError("协议必须为 http 或 https")
    if auth_type not in ("password", "token", "none"):
        raise ValueError("认证方式非法")
    if auth_type == "password" and not username:
        raise ValueError("用户名不能为空")
    if auth_type == "password" and not password:
        raise ValueError("密码不能为空")

    try:
        port = int(port)
        if port < 1 or port > 65535:
            raise ValueError
    except (TypeError, ValueError):
        raise ValueError("端口必须为 1-65535 的数字")

    now = _now()
    target = {
        "id": str(uuid.uuid4())[:12],
        "name": name,
        "host": host,
        "port": port,
        "protocol": protocol,
        "auth_type": auth_type,
        "username": username if auth_type == "password" else "",
        "password_enc": encrypt_password(password) if password else "",
        "connected": False,
        "created_at": now,
        "updated_at": now,
    }

    targets = _load_targets()
    targets.append(target)
    _save_targets(targets)
    logger.info("新增管理目标: %s (%s)", name, host)
    return get_target_view(target["id"])


def update_target(target_id: str, data: dict) -> dict:
    """更新目标。"""
    targets = _load_targets()
    target = next((t for t in targets if t["id"] == target_id), None)
    if not target:
        raise ValueError("目标不存在")

    if "name" in data and data["name"]:
        name = str(data["name"]).strip()
        if not _NAME_RE.match(name):
            raise ValueError("目标名称非法")
        target["name"] = name
    if "host" in data and data["host"]:
        host = str(data["host"]).strip()
        if not _HOST_RE.match(host):
            raise ValueError("主机地址非法")
        target["host"] = host
    if "port" in data:
        try:
            target["port"] = int(data["port"])
        except (TypeError, ValueError):
            raise ValueError("端口必须为数字")
    if "protocol" in data:
        if data["protocol"] not in ("http", "https"):
            raise ValueError("协议必须为 http 或 https")
        target["protocol"] = data["protocol"]
    if "auth_type" in data:
        if data["auth_type"] not in ("password", "token", "none"):
            raise ValueError("认证方式非法")
        target["auth_type"] = data["auth_type"]
    if "username" in data:
        target["username"] = str(data["username"]).strip()
    if data.get("password"):
        target["password_enc"] = encrypt_password(data["password"])

    target["updated_at"] = _now()
    _save_targets(targets)
    logger.info("更新管理目标: %s", target_id)
    return get_target_view(target_id)


def delete_target(target_id: str) -> bool:
    """删除目标。"""
    targets = _load_targets()
    new_list = [t for t in targets if t["id"] != target_id]
    if len(new_list) == len(targets):
        return False
    _save_targets(new_list)
    logger.info("删除管理目标: %s", target_id)
    return True


def test_connection(data: dict) -> dict:
    """测试 HTTP(S) 连通性。

    发送 GET 请求到目标服务器根路径，检查响应状态码。
    Returns: {"success": bool, "message": str}
    """
    host = (data.get("host") or "").strip()
    port = data.get("port", 443)
    protocol = data.get("protocol", "https")
    username = (data.get("username") or "").strip()
    password = data.get("password") or ""

    if not host or not _HOST_RE.match(host):
        return {"success": False, "message": "主机地址非法"}
    try:
        port = int(port)
    except (TypeError, ValueError):
        return {"success": False, "message": "端口必须为数字"}

    url = f"{protocol}://{host}:{port}/"
    try:
        import urllib.request
        req = urllib.request.Request(url, method="GET")
        # 设置超时 10 秒
        with urllib.request.urlopen(req, timeout=10) as resp:
            status = resp.getcode()
            if 200 <= status < 400:
                return {"success": True, "message": f"连接成功（HTTP {status}）"}
            else:
                return {"success": False, "message": f"服务器返回异常状态码: {status}"}
    except Exception as e:
        return {"success": False, "message": f"连接失败: {str(e)}"}


# ============================================================
# 项目信息
# ============================================================

def save_project(data: dict) -> dict:
    """保存项目信息（密码脱敏后存储）。"""
    _ensure_dirs()
    project = {
        "salesPeople": (data.get("salesPeople") or "").strip(),
        "deployer": (data.get("deployer") or "").strip(),
        "orderTime": (data.get("orderTime") or "").strip(),
        "serverPassword": encrypt_password(data.get("serverPassword") or ""),
        "projectOverview": (data.get("projectOverview") or "").strip(),
        "updated_at": _now(),
    }
    MANAGE_PROJECTS_FILE.write_text(
        json.dumps(project, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    logger.info("保存项目信息")
    return {"success": True, "message": "项目信息保存成功"}


def get_project() -> Optional[dict]:
    """读取项目信息（脱敏：密码→has_password 布尔）。"""
    _ensure_dirs()
    if not MANAGE_PROJECTS_FILE.exists():
        return None
    try:
        data = json.loads(MANAGE_PROJECTS_FILE.read_text(encoding="utf-8"))
        data["has_password"] = bool(data.get("serverPassword"))
        data.pop("serverPassword", None)
        return data
    except (json.JSONDecodeError, OSError):
        return None


# ============================================================
# 授权文件操作（本地暂存模式）
# ============================================================

def _make_placeholder_file(filename: str, content: str = "占位文件") -> Path:
    """生成占位文件（阶段一：实际文件待对接目标服务器后替换）。"""
    _ensure_dirs()
    filepath = MANAGE_UPLOADS_DIR / filename
    filepath.write_text(content, encoding="utf-8")
    return filepath


def get_activation_file() -> Path:
    """获取激活文件（本地生成占位文件）。"""
    return _make_placeholder_file("licences.zip", "激活文件占位内容")


def get_deploy_view_file() -> Path:
    """获取部署视图文件（本地生成占位文件）。"""
    ts = datetime.now().strftime("%Y%m%d%H%M%S")
    return _make_placeholder_file(
        f"deploymentView{ts}.xlsx",
        "部署视图占位内容",
    )


def get_self_check_file() -> Path:
    """获取自检文档文件（本地生成占位文件）。"""
    ts = datetime.now().strftime("%Y%m%d%H%M%S")
    return _make_placeholder_file(
        f"checkList{ts}.xlsx",
        "自检文档占位内容",
    )


def save_uploaded_file(file_storage, subdir: str = "") -> dict:
    """保存上传文件到 uploads 目录。

    Args:
        file_storage: Flask request.files 中的文件对象
        subdir: 子目录名（如 "license"、"deploy-view"、"self-check"）

    Returns:
        {"success": True, "filename": str, "path": str, "size": int}
    """
    _ensure_dirs()
    target_dir = MANAGE_UPLOADS_DIR
    if subdir:
        target_dir = target_dir / subdir
        target_dir.mkdir(parents=True, exist_ok=True)

    filename = file_storage.filename or f"upload_{uuid.uuid4().hex[:8]}"
    filepath = target_dir / filename
    file_storage.save(str(filepath))
    size = filepath.stat().st_size

    logger.info("上传文件保存: %s (%d bytes)", filename, size)
    return {
        "success": True,
        "filename": filename,
        "path": str(filepath.relative_to(SERVICE_MANAGE_DATA_DIR)),
        "size": size,
    }