"""插件中心 API

Admin 路由（均挂在 /admin/plugins 前缀下）：
  GET  /           列出所有已发现的插件及其当前状态
  POST /{name}/toggle   切换插件启用/停用
  GET  /{name}/config   获取插件专属配置
  PUT  /{name}/config   保存插件专属配置

公共路由（无需鉴权）：
  GET  /store/plugins/status   返回所有插件的启停状态映射
"""
import importlib
import logging
from pathlib import Path
from typing import Any

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import PermissionContext, get_db, get_permission_context, require_permission, get_tenant_by_domain

logger = logging.getLogger("uvicorn.error")

router = APIRouter(prefix="/admin/plugins", tags=["插件中心"])

CONTROLLED_PLUGIN_NAMES = {"inventory", "pos_operations", "pos_sync", "advanced_shipping_rules"}

PLUGIN_ROOT = Path(__file__).parent.parent.parent.parent / "plugins"


# ── 工具函数 ──────────────────────────────────────────────────────────────────

def _discover_plugin_names() -> list[str]:
    """扫描 plugins/ 目录，返回所有插件名列表"""
    if not PLUGIN_ROOT.exists():
        return []
    return sorted(
        d.name
        for d in PLUGIN_ROOT.iterdir()
        if d.is_dir() and not d.name.startswith("_")
    )


def _load_meta(plugin_name: str) -> dict:
    """尝试从插件 __init__.py 中读取 PLUGIN_META"""
    try:
        mod = importlib.import_module(f"app.plugins.{plugin_name}")
        meta = getattr(mod, "PLUGIN_META", None)
        if meta and isinstance(meta, dict):
            return meta
    except Exception:
        # ImportError / OSError / 任何插件自身错误都不应让列表接口 500
        logger.warning("Failed to load meta for plugin %r", plugin_name, exc_info=True)
    # 默认 meta
    return {
        "name":         plugin_name,
        "display_name": plugin_name,
        "description":  "",
        "icon":         "🧩",
        "category":     "其他",
        "version":      "?",
        "author":       "-",
        "config_fields": [],
    }


def _default_active(plugin_name: str) -> bool:
    """插件未配置时的默认启停状态（PLUGIN_META.default_active，缺省 True）。"""
    return bool(_load_meta(plugin_name).get("default_active", True))


async def _get_plugin_record(db: AsyncSession, tenant_id: int, plugin_name: str):
    """查询 plugin_configs 记录"""
    r = await db.execute(
        text(
            "SELECT id, is_active, config FROM plugin_configs "
            "WHERE tenant_id=:tid AND plugin_name=:name LIMIT 1"
        ),
        {"tid": tenant_id, "name": plugin_name},
    )
    return r.fetchone()


async def _ensure_plugin_record(db: AsyncSession, tenant_id: int, plugin_name: str) -> None:
    """若不存在则插入默认记录，is_active 取插件的 default_active（默认启用）。

    必须与 UI 展示的默认状态一致：toggle 依赖"当前记录 → 翻转"，
    若这里恒插 1，则默认关闭的插件被点"启用"时会被翻成停用。
    """
    active = 1 if _default_active(plugin_name) else 0
    await db.execute(
        text(
            "INSERT IGNORE INTO plugin_configs (tenant_id, plugin_name, is_active) "
            "VALUES (:tid, :name, :active)"
        ),
        {"tid": tenant_id, "name": plugin_name, "active": active},
    )
    await db.commit()


def requires_approval_to_enable(plugin_name: str, new_state: int) -> bool:
    """受控插件在"启用"方向（new_state 真值）必须走平台审批，不能直接 toggle。
    抽成纯函数以便无 DB 单测覆盖 toggle_plugin 里的这条 409 守卫。"""
    return bool(plugin_name in CONTROLLED_PLUGIN_NAMES and new_state)


async def _require_plugin_configurable(db: AsyncSession, tenant_id: int, plugin_name: str):
    """配置入口守卫：插件不存在或已停用时禁止读取/保存配置。"""
    if plugin_name not in _discover_plugin_names():
        raise HTTPException(status_code=404, detail=f"插件 {plugin_name!r} 不存在")

    row = await _get_plugin_record(db, tenant_id, plugin_name)
    if row and not bool(row[1]):
        raise HTTPException(status_code=409, detail="插件已停用，请先启用后再配置")

    return row


# ── 路由 ─────────────────────────────────────────────────────────────────────

@router.get("/", summary="列出全部插件")
async def list_plugins(
    db:    AsyncSession = Depends(get_db),
    _user = Depends(require_permission("plugins.view")),
):
    tid   = _user.tenant_id
    names = _discover_plugin_names()

    # 批量读取已有记录
    if names:
        placeholders = ", ".join(f":n{i}" for i in range(len(names)))
        params = {"tid": tid, **{f"n{i}": n for i, n in enumerate(names)}}
        rows = (
            await db.execute(
                text(
                    f"SELECT plugin_name, is_active, config FROM plugin_configs "
                    f"WHERE tenant_id=:tid AND plugin_name IN ({placeholders})"
                ),
                params,
            )
        ).fetchall()
        db_map = {row[0]: {"is_active": bool(row[1]), "config": row[2] or {}} for row in rows}
    else:
        db_map = {}

    approval_rows = (
        await db.execute(
            text("SELECT plugin_name, status FROM plugin_activation_requests WHERE tenant_id=:tid"),
            {"tid": tid},
        )
    ).fetchall()
    approval_map = {row[0]: row[1] for row in approval_rows}

    result = []
    for name in names:
        meta   = _load_meta(name)
        status = db_map.get(name, {"is_active": _default_active(name), "config": {}})
        result.append({
            **meta,
            "is_active": status["is_active"],
            "approval_status": approval_map.get(name) if name in CONTROLLED_PLUGIN_NAMES else None,
            "has_config": bool(meta.get("config_fields")),
        })

    return result


@router.post("/{plugin_name}/request-activation", summary="申请平台审批启用插件")
async def request_activation(
    plugin_name: str,
    db: AsyncSession = Depends(get_db),
    ctx: PermissionContext = Depends(get_permission_context),
):
    if plugin_name not in CONTROLLED_PLUGIN_NAMES or plugin_name not in _discover_plugin_names():
        raise HTTPException(status_code=404, detail="该插件不需要平台审批")
    if not ctx.can("plugins.enable"):
        raise HTTPException(status_code=403, detail="Permission denied: plugins.enable")
    tid = ctx.user.tenant_id
    await _ensure_plugin_record(db, tid, plugin_name)
    row = await _get_plugin_record(db, tid, plugin_name)
    if row and bool(row[1]):
        return {"plugin_name": plugin_name, "status": "approved", "message": "插件已启用"}
    await db.execute(
        text(
            "INSERT INTO plugin_activation_requests (tenant_id, plugin_name, status, requested_by) "
            "VALUES (:tid, :name, 'pending', :uid) "
            "ON DUPLICATE KEY UPDATE status='pending', requested_by=VALUES(requested_by), "
            "reviewed_by=NULL, reviewed_at=NULL, review_note=NULL"
        ),
        {"tid": tid, "name": plugin_name, "uid": ctx.user.id},
    )
    await db.commit()
    return {"plugin_name": plugin_name, "status": "pending", "message": "已提交平台审批"}


@router.post("/{plugin_name}/toggle", summary="启用/停用插件")
async def toggle_plugin(
    plugin_name: str,
    db:          AsyncSession = Depends(get_db),
    ctx: PermissionContext = Depends(get_permission_context),
):
    tid = ctx.user.tenant_id

    # 确认插件存在
    if plugin_name not in _discover_plugin_names():
        raise HTTPException(status_code=404, detail=f"插件 {plugin_name!r} 不存在")

    await _ensure_plugin_record(db, tid, plugin_name)
    row = await _get_plugin_record(db, tid, plugin_name)
    new_state = 0 if row and row[1] else 1
    permission_key = "plugins.enable" if new_state else "plugins.disable"
    if not ctx.can(permission_key):
        raise HTTPException(status_code=403, detail=f"Permission denied: {permission_key}")
    if requires_approval_to_enable(plugin_name, new_state):
        raise HTTPException(status_code=409, detail="该插件需由平台管理员审批启用")

    await db.execute(
        text(
            "UPDATE plugin_configs SET is_active=:state "
            "WHERE tenant_id=:tid AND plugin_name=:name"
        ),
        {"state": new_state, "tid": tid, "name": plugin_name},
    )
    await db.commit()

    action = "启用" if new_state else "停用"
    logger.info("Plugin %s %s by tenant %s", plugin_name, action, tid)
    return {"plugin_name": plugin_name, "is_active": bool(new_state), "message": f"插件已{action}"}


@router.get("/{plugin_name}/config", summary="获取插件配置")
async def get_plugin_config(
    plugin_name: str,
    db:          AsyncSession = Depends(get_db),
    _user      = Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    row = await _require_plugin_configurable(db, tid, plugin_name)
    raw_cfg = (row[2] or {}) if row else {}
    # config 列可能是 JSON 字符串或已解析的 dict
    if isinstance(raw_cfg, str):
        import json as _json
        try:
            cfg = _json.loads(raw_cfg)
        except (ValueError, TypeError):
            cfg = {}
    else:
        cfg = raw_cfg

    # 以已存储的全部字段为基础（包含未在 config_fields 中声明的自定义字段，
    # 如 tax_type_mapping / country_name_mapping 等），再用字段定义补默认值、隐藏 secret 字段的值
    meta   = _load_meta(plugin_name)
    fields = meta.get("config_fields", [])
    safe   = dict(cfg)
    for f in fields:
        k = f["key"]
        if k not in safe:
            safe[k] = f.get("default", "")
        if f.get("secret") and safe.get(k):
            safe[k] = "••••••••"  # 已设置但不回显

    return {"plugin_name": plugin_name, "config": safe, "fields": fields}


@router.put("/{plugin_name}/config", summary="保存插件配置")
async def put_plugin_config(
    plugin_name: str,
    body:        dict[str, Any],
    db:          AsyncSession = Depends(get_db),
    _user      = Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id

    await _ensure_plugin_record(db, tid, plugin_name)
    row = await _require_plugin_configurable(db, tid, plugin_name)
    raw_old = (row[2] or {}) if row else {}
    if isinstance(raw_old, str):
        import json as _json
        try:
            old_cfg = _json.loads(raw_old)
        except (ValueError, TypeError):
            old_cfg = {}
    else:
        old_cfg = raw_old

    # 合并：secret 字段若传入 "••••••••" 则保留旧值
    new_cfg = dict(old_cfg)
    for k, v in body.items():
        if isinstance(v, str) and v.startswith("••"):
            continue  # 保留旧密钥
        new_cfg[k] = v

    import json
    await db.execute(
        text(
            "UPDATE plugin_configs SET config=:cfg "
            "WHERE tenant_id=:tid AND plugin_name=:name"
        ),
        {"cfg": json.dumps(new_cfg, ensure_ascii=False), "tid": tid, "name": plugin_name},
    )
    await db.commit()
    return {"plugin_name": plugin_name, "message": "配置已保存"}


# ── 公共路由（无需鉴权，供 Store / B2B / Admin 前端初始化时调用）───────────

store_router = APIRouter(tags=["插件状态"])


@store_router.get("/store/plugins/status", summary="插件启停状态（公开）")
async def plugins_status(
    db:  AsyncSession = Depends(get_db),
    tid: int = Depends(get_tenant_by_domain),
):
    """
    返回 {plugin_name: bool} 映射，前端据此决定是否渲染插件相关 UI。
    未在 plugin_configs 中设置的插件默认视为启用。
    """
    names = _discover_plugin_names()
    if not names:
        return {}

    placeholders = ", ".join(f":n{i}" for i in range(len(names)))
    params = {"tid": tid, **{f"n{i}": n for i, n in enumerate(names)}}
    rows = (
        await db.execute(
            text(
                f"SELECT plugin_name, is_active FROM plugin_configs "
                f"WHERE tenant_id=:tid AND plugin_name IN ({placeholders})"
            ),
            params,
        )
    ).fetchall()
    db_map = {row[0]: bool(row[1]) for row in rows}

    # 未配置的插件按 PLUGIN_META.default_active 决定（缺省启用）
    return {name: db_map.get(name, _default_active(name)) for name in names}
