"""
角色权限中间件 — 管理会计OS
4角色: ceo(CEO/总览), finance(财务), business(业务), it(IT/运维)
权限配置支持从数据库动态加载
"""

from fastapi import Request, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import User, RolePermission
import secrets
import json

# 角色定义（固定）
ROLES = {
    "ceo": {"name": "CEO", "priority": 1},
    "finance": {"name": "财务", "priority": 2},
    "business": {"name": "业务", "priority": 3},
    "it": {"name": "IT运维", "priority": 4},
}

# 默认权限（数据库没有时的 fallback）
DEFAULT_ROUTE_PERMISSIONS = {
    "ceo": ["dashboard", "kpis", "kpi_detail", "maps", "alerts", "ai_analysis", "data_source", "user_manage", "system_config"],
    "finance": ["dashboard", "kpis", "kpi_detail", "maps", "alerts", "ai_analysis", "data_source"],
    "business": ["dashboard", "kpis", "kpi_detail", "alerts"],
    "it": ["dashboard", "kpis", "kpi_detail", "alerts", "data_source", "user_manage", "system_config"],
}

DEFAULT_KPI_VISIBILITY = {
    "ceo": ["*"],                                               # CEO看全部维度
    "finance": ["finance_*"],                                   # 财务只看财务
    "business": ["customer_*", "process_*", "learning_*"],      # 业务看客户/流程/学习
    "it": ["*"],                                                # IT看全部（运维）
}

DEFAULT_ACTION_PERMISSIONS = {
    "ceo": ["read", "approve"],
    "finance": ["read", "write", "import", "export"],
    "business": ["read", "write"],
    "it": ["read", "write", "delete", "admin"],
}

import logging
logger = logging.getLogger("cma.auth")

# Redis token 存储（跨 worker 共享）
try:
    import redis as redis_lib
    _redis = redis_lib.Redis(
        host="127.0.0.1", port=6379, db=1,
        decode_responses=True, socket_connect_timeout=2, socket_timeout=3
    )
    _redis.ping()
    _redis_available = True
except Exception:
    _redis = None
    _redis_available = False
    logger.warning("Redis不可用，token存储降级到内存（不支持多worker）")

# 内存 fallback
_token_store: dict[str, dict] = {}

TOKEN_PREFIX = "cma:token:"
TOKEN_TTL = 86400  # 24小时

# 缓存权限配置（每5分钟刷新）
_permissions_cache = {"route": None, "action": None, "ts": 0}
_PERM_CACHE_TTL = 300


def _load_permissions(db: Session = None):
    """从数据库加载权限配置"""
    import time
    now = time.time()
    if db is None:
        if now - _permissions_cache["ts"] < _PERM_CACHE_TTL:
            return _permissions_cache["route"] or DEFAULT_ROUTE_PERMISSIONS, _permissions_cache["action"] or DEFAULT_ACTION_PERMISSIONS
        return DEFAULT_ROUTE_PERMISSIONS, DEFAULT_ACTION_PERMISSIONS

    try:
        route_perm = db.query(RolePermission).filter(RolePermission.key == "route_permissions").first()
        action_perm = db.query(RolePermission).filter(RolePermission.key == "action_permissions").first()

        routes = route_perm.value if route_perm else DEFAULT_ROUTE_PERMISSIONS
        actions = action_perm.value if action_perm else DEFAULT_ACTION_PERMISSIONS

        _permissions_cache["route"] = routes
        _permissions_cache["action"] = actions
        _permissions_cache["ts"] = now

        return routes, actions
    except Exception:
        return DEFAULT_ROUTE_PERMISSIONS, DEFAULT_ACTION_PERMISSIONS


def create_token(user_id: int, entity_id: int = None) -> str:
    """签发token：Redis存储 JSON {user_id, entity_id}（账套模式）
    兼容旧调用 create_token(user_id) → entity_id=None（切换器会重新签发）
    """
    token = secrets.token_hex(32)
    payload = json.dumps({"user_id": user_id, "entity_id": entity_id}, ensure_ascii=False)
    if _redis_available:
        _redis.setex(f"{TOKEN_PREFIX}{token}", TOKEN_TTL, payload)
    else:
        _token_store[token] = payload
    return token


def _resolve_token_data(token: str) -> dict | None:
    """解析token → {user_id, entity_id}
    - 新格式 JSON → 返回 dict
    - 旧格式 int（改造前）→ 返回 None（强制下线，账套模式需重新登录）
    - 不存在 → None
    """
    if _redis_available:
        val = _redis.get(f"{TOKEN_PREFIX}{token}")
        if val is None:
            return None
        try:
            data = json.loads(val)
            if isinstance(data, dict) and "user_id" in data:
                return data
        except (json.JSONDecodeError, ValueError, TypeError):
            pass
        # 旧格式纯 int → 强制下线
        return None
    val = _token_store.get(token)
    if val is None:
        return None
    if isinstance(val, dict):
        return val
    try:
        data = json.loads(val)
        if isinstance(data, dict) and "user_id" in data:
            return data
    except (json.JSONDecodeError, ValueError, TypeError):
        pass
    return None


def _resolve_user_id(token: str) -> int | None:
    data = _resolve_token_data(token)
    return int(data["user_id"]) if data else None


def get_token_entity_id(token: str) -> int | None:
    """从token解析绑定的entity_id（token不存在/旧格式 → None）"""
    data = _resolve_token_data(token)
    if not data:
        return None
    eid = data.get("entity_id")
    return int(eid) if eid else None


def user_has_entity(db: Session, user_id: int, entity_id: int) -> bool:
    """校验用户是否被授权访问指定企业（账套授权表 user_entities）"""
    from app.models import UserEntity, Entity
    ent = db.query(Entity).filter(Entity.id == entity_id).first()
    if not ent or ent.status != "active":
        return False
    rel = db.query(UserEntity).filter(
        UserEntity.user_id == user_id,
        UserEntity.entity_id == entity_id,
    ).first()
    return rel is not None


def extract_bearer_token(request) -> str | None:
    """从Request提取Bearer token（无/非Bearer格式 → None）"""
    auth = request.headers.get("Authorization", "")
    if auth.startswith("Bearer "):
        return auth[7:].strip()
    return None


def require_auth(
    credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=True)),
    db: Session = Depends(get_db),
) -> User:
    token = credentials.credentials
    user_id = _resolve_user_id(token)
    if user_id is None:
        raise HTTPException(401, "无效的token，请重新登录")
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(401, "用户不存在")
    return user


def require_role(*roles: str):
    async def role_checker(
        current_user: User = Depends(require_auth),
    ) -> User:
        if current_user.role not in roles:
            raise HTTPException(403, f"权限不足: 需要 {', '.join(roles)} 角色")
        return current_user
    return role_checker


def has_permission(user: User, module: str, db: Session = None) -> bool:
    routes, _ = _load_permissions(db)
    return module in routes.get(user.role, [])


def has_action(user: User, action: str, db: Session = None) -> bool:
    _, actions = _load_permissions(db)
    return action in actions.get(user.role, [])


# ─── KPI可见性（按维度/分类过滤） ───

def _load_kpi_visibility(db: Session = None):
    """从RolePermission表加载kpi_visibility配置（独立缓存）"""
    import time
    if not hasattr(_load_kpi_visibility, "_cache"):
        _load_kpi_visibility._cache = {"data": None, "ts": 0}
    cache = _load_kpi_visibility._cache
    now = time.time()
    if db is None or (cache["data"] and now - cache["ts"] < _PERM_CACHE_TTL):
        return cache["data"] or DEFAULT_KPI_VISIBILITY
    try:
        perm = db.query(RolePermission).filter(RolePermission.key == "kpi_visibility").first()
        cache["data"] = perm.value if perm else DEFAULT_KPI_VISIBILITY
        cache["ts"] = now
        return cache["data"]
    except Exception:
        return DEFAULT_KPI_VISIBILITY


def kpi_visible_dims(role: str, db: Session = None) -> list[str]:
    """返回角色可见的维度列表（空列表=全部可见）"""
    vis = _load_kpi_visibility(db)
    rules = vis.get(role, ["*"])
    if "*" in rules:
        return []  # 空=全部可见
    # 提取维度前缀：finance_* -> finance
    dims = set()
    for r in rules:
        if r.endswith("_*"):
            dims.add(r[:-2])
    return list(dims)


def filter_kpis_by_role(kpis: list, role: str, db: Session = None) -> list:
    """按角色可见性过滤KPI列表"""
    dims = kpi_visible_dims(role, db)
    if not dims:
        return kpis  # 全部可见
    return [k for k in kpis if k.dimension in dims or (hasattr(k, 'dimension') and k.dimension in dims)]
