"""FastAPI 依赖注入：JWT 解析、当前用户、数据库会话"""
from dataclasses import dataclass
from typing import AsyncGenerator
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.database import AsyncSessionLocal
from app.config import settings
from app.core.models.user import User
from app.core.models.tenant import Tenant
from app.core.cache import cache_get, cache_set
from app.core.services.permission_service import EffectivePermissions, PermissionService

bearer = HTTPBearer(auto_error=True)
optional_bearer = HTTPBearer(auto_error=False)


async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        try:
            yield session
        finally:
            await session.close()


def _decode_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token 已过期")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Token 无效")


async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(bearer),
    db: AsyncSession = Depends(get_db),
) -> User:
    payload = _decode_token(credentials.credentials)
    user_id: int = payload.get("sub")
    if not user_id:
        raise HTTPException(status_code=401, detail="Token 载荷无效")

    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="用户不存在或已禁用")
    return user


async def get_admin_user(current_user: User = Depends(get_current_user)) -> User:
    if current_user.role not in ("owner", "admin", "staff"):
        raise HTTPException(status_code=403, detail="权限不足")
    return current_user


async def get_superadmin_user(
    credentials: HTTPAuthorizationCredentials = Depends(bearer),
    db: AsyncSession = Depends(get_db),
) -> User:
    """Only platform superadmins (is_superadmin=1) may pass."""
    payload = _decode_token(credentials.credentials)
    if not payload.get("sa"):
        raise HTTPException(status_code=403, detail="需要平台超管权限")
    user_id = payload.get("sub")
    if not user_id:
        raise HTTPException(status_code=401, detail="Token 载荷无效")
    result = await db.execute(select(User).where(User.id == int(user_id)))
    user = result.scalar_one_or_none()
    if not user or not user.is_active or not user.is_superadmin:
        raise HTTPException(status_code=403, detail="需要平台超管权限")
    return user


@dataclass
class PermissionContext:
    user: User
    effective: EffectivePermissions

    def can(self, permission_key: str) -> bool:
        return self.effective.can(permission_key)

    def data_scope(self, permission_key: str) -> str:
        return self.effective.data_scope(permission_key)


async def get_permission_context(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_admin_user),
) -> PermissionContext:
    effective = await PermissionService(db).get_effective_permissions(current_user)
    return PermissionContext(user=current_user, effective=effective)


def require_permission(permission_key: str):
    async def dependency(ctx: PermissionContext = Depends(get_permission_context)) -> User:
        if not ctx.can(permission_key):
            raise HTTPException(status_code=403, detail=f"Permission denied: {permission_key}")
        return ctx.user

    return dependency


def require_any_permission(permission_keys: list[str] | tuple[str, ...]):
    async def dependency(ctx: PermissionContext = Depends(get_permission_context)) -> User:
        if not any(ctx.can(key) for key in permission_keys):
            joined = ", ".join(permission_keys)
            raise HTTPException(status_code=403, detail=f"Permission denied: any of {joined}")
        return ctx.user

    return dependency


# ── 多租户域名路由 ────────────────────────────────────────────────

async def get_tenant_by_domain(
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> int:
    """Store 路由专用：从 Host / X-Forwarded-Host 头解析租户 ID，结果 Redis 缓存 5 分钟。

    - localhost / 127.0.0.1 / 裸 IP / 空 → 回退 DEFAULT_TENANT_ID（本地开发/代理）
    - X-Forwarded-Host 优先于 Host（Nuxt Nitro 反向代理场景）
    - Redis 命中且值为 -1 → 404 负缓存（已知不存在域名）
    - Redis 命中且为正整数 → 直接返回 tenant_id
    - 未命中 → 查 DB，写缓存（TTL 300s）或负缓存（TTL 60s）
    """
    import re as _re
    # X-Forwarded-Host 优先（Nuxt Nitro 代理会注入原始浏览器域名）
    raw = (
        request.headers.get("x-forwarded-host")
        or request.headers.get("host", "")
    )
    host = raw.split(":")[0].lower().strip()

    # 本地开发/代理回退：空、localhost、127.0.0.1、裸 IP 地址
    if not host or host in ("localhost", "127.0.0.1") or _re.match(r"^\d+\.\d+\.\d+\.\d+$", host):
        return settings.DEFAULT_TENANT_ID

    # Redis 缓存
    cache_key = f"domain:{host}"
    cached = await cache_get(cache_key)
    if cached is not None:
        if cached == -1:
            return settings.DEFAULT_TENANT_ID
        return int(cached)

    # 查 DB
    result = await db.execute(
        select(Tenant.id, Tenant.status).where(Tenant.domain == host)
    )
    row = result.first()

    if not row:
        await cache_set(cache_key, -1, ttl=60)   # 负缓存 1 分钟
        return settings.DEFAULT_TENANT_ID

    if row.status == "suspended":
        raise HTTPException(status_code=403, detail="该商店已停用")

    await cache_set(cache_key, row.id, ttl=300)
    return row.id


# ── 前台商城顾客 JWT 认证 ────────────────────────────────────────

from app.core.models.customer import Customer


async def get_current_customer(
    request: Request,
    credentials: HTTPAuthorizationCredentials = Depends(bearer),
    db: AsyncSession = Depends(get_db),
) -> Customer:
    """前台商城顾客 JWT 认证（兼容 H5 域名 token 和小程序 appid token）。

    小程序 JWT 带 tenant_id + appid 字段，直接用 JWT 中的 tenant_id 做校验；
    H5 JWT 无这两个字段，回退域名解析租户。
    """
    payload = _decode_token(credentials.credentials)
    if payload.get("role") not in (None, "customer"):
        raise HTTPException(status_code=403, detail="权限不足")
    customer_id = payload.get("sub")
    if not customer_id:
        raise HTTPException(status_code=401, detail="Token 载荷无效")

    # 小程序渠道校验：JWT.appid 与 X-AppID 必须一致
    jwt_appid = payload.get("appid", "")
    req_appid = request.headers.get("x-appid", "").strip()
    if jwt_appid and req_appid and jwt_appid != req_appid:
        raise HTTPException(status_code=401, detail="渠道不匹配，请重新登录")

    # 优先用 JWT 内嵌的 tenant_id（小程序渠道），否则回退域名解析
    jwt_tid = payload.get("tenant_id")
    if jwt_tid:
        tid = int(jwt_tid)
    else:
        tid = await get_tenant_by_domain(request, db)

    result = await db.execute(select(Customer).where(
        Customer.id == int(customer_id),
        Customer.tenant_id == tid,
    ))
    customer = result.scalar_one_or_none()
    if not customer or not customer.is_active:
        raise HTTPException(status_code=401, detail="用户不存在或已禁用")
    return customer


async def get_optional_customer(
    request: Request,
    credentials: HTTPAuthorizationCredentials | None = Depends(optional_bearer),
    db: AsyncSession = Depends(get_db),
) -> Customer | None:
    """可选顾客认证：无 token 时返回 None；有 token 但无效时仍返回 401。"""
    if credentials is None:
        return None
    payload = _decode_token(credentials.credentials)
    if payload.get("role") not in (None, "customer"):
        raise HTTPException(status_code=403, detail="权限不足")
    customer_id = payload.get("sub")
    if not customer_id:
        raise HTTPException(status_code=401, detail="Token 载荷无效")

    jwt_appid = payload.get("appid", "")
    req_appid = request.headers.get("x-appid", "").strip()
    if jwt_appid and req_appid and jwt_appid != req_appid:
        raise HTTPException(status_code=401, detail="渠道不匹配，请重新登录")

    jwt_tid = payload.get("tenant_id")
    if jwt_tid:
        tid = int(jwt_tid)
    else:
        tid = await get_tenant_by_domain(request, db)

    result = await db.execute(select(Customer).where(
        Customer.id == int(customer_id),
        Customer.tenant_id == tid,
    ))
    customer = result.scalar_one_or_none()
    if not customer or not customer.is_active:
        raise HTTPException(status_code=401, detail="用户不存在或已禁用")
    return customer


# ── AppID 租户解析 ────────────────────────────────────────────────

async def get_tenant_by_appid(appid: str, db: AsyncSession) -> int:
    """通过 AppID 查租户 ID，Redis 缓存 24h。status != active 时抛 403。"""
    from app.core.models.tenant_channel import TenantChannel

    cache_key = f"mp:appid:{appid}"
    cached = await cache_get(cache_key)
    if cached is not None:
        info = cached if isinstance(cached, dict) else {}
        if info.get("status") != "active":
            raise HTTPException(status_code=403, detail="该小程序渠道已停用")
        return int(info["tenant_id"])

    result = await db.execute(
        select(TenantChannel).where(TenantChannel.appid == appid)
    )
    ch = result.scalar_one_or_none()
    if not ch:
        raise HTTPException(status_code=404, detail="AppID 未注册")
    if ch.status != "active":
        raise HTTPException(status_code=403, detail="该小程序渠道已停用")

    await cache_set(cache_key, {
        "tenant_id": ch.tenant_id,
        "channel_id": ch.id,
        "status": ch.status,
        "config_version": str(ch.updated_at),
    }, ttl=86400)
    return ch.tenant_id


async def get_tenant_by_appid_or_domain(
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> int:
    """统一租户解析入口：
    - 有 JWT：从 JWT 取 tenant_id，同时校验 JWT.appid == X-AppID（若两者都存在）
    - 无 JWT + 有 X-AppID：从 AppID 查 tenant_channels
    - 其余：回退 Host/domain 解析（H5 Store 兼容）
    """
    appid = request.headers.get("x-appid", "").strip()

    # 尝试从 JWT 读取（无 JWT 不报错）
    auth_header = request.headers.get("authorization", "")
    if auth_header.startswith("Bearer "):
        token = auth_header[7:]
        try:
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
            tid = payload.get("tenant_id")
            jwt_appid = payload.get("appid", "")
            if tid:
                # 渠道一致性校验：两者都存在时必须匹配
                if appid and jwt_appid and appid != jwt_appid:
                    raise HTTPException(status_code=401, detail="渠道不匹配，请重新登录")
                return int(tid)
        except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
            pass  # JWT 无效时继续走下面的路径

    if appid:
        return await get_tenant_by_appid(appid, db)

    # 回退 H5 Store 的 Host/domain 解析
    return await get_tenant_by_domain(request, db)
