"""
F-1: 멀티테넌트 데이터 격리

설계 원칙:
  - 테넌트 식별: X-Tenant-ID 헤더 > JWT claim > 기본값(DEFAULT)
  - ContextVar 기반 async-safe 테넌트 컨텍스트
  - 모든 DB 쿼리에 tenant_id 필터 자동 주입 가능
  - 테넌트별 설정(rate limit, storage quota) 지원
  - ADMIN은 모든 테넌트 데이터 열람 가능 (X-Tenant-Override)

사용법:
  # main.py에서 미들웨어 등록
  from middleware.tenant import TenantMiddleware
  app.add_middleware(TenantMiddleware)

  # 라우터에서 현재 테넌트 조회
  from middleware.tenant import get_current_tenant_id, require_tenant
  tenant_id = get_current_tenant_id()          # 현재 요청의 테넌트 ID

  # SQLAlchemy 쿼리에 테넌트 필터 적용
  from middleware.tenant import apply_tenant_filter
  stmt = apply_tenant_filter(select(SRRequest), SRRequest)

엔드포인트 (routers/tenant_mgmt.py):
  POST /api/tenants           — 테넌트 생성 (SUPER_ADMIN)
  GET  /api/tenants           — 테넌트 목록
  GET  /api/tenants/{tid}     — 테넌트 상세
  PUT  /api/tenants/{tid}     — 테넌트 수정
  DELETE /api/tenants/{tid}   — 테넌트 비활성화
  GET  /api/tenants/current   — 현재 요청의 테넌트 정보
  POST /api/tenants/{tid}/quota — 쿼터 설정
"""
from __future__ import annotations

import logging
from contextvars import ContextVar
from datetime import datetime
from typing import Any, Callable, Dict, Optional
from uuid import uuid4

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.types import ASGIApp

logger = logging.getLogger(__name__)

# ── 테넌트 컨텍스트 (async-safe ContextVar) ──────────────────────────────────
_tenant_id_ctx:  ContextVar[str]            = ContextVar("tenant_id",  default="DEFAULT")
_tenant_ctx:     ContextVar[Optional[Dict]] = ContextVar("tenant_obj", default=None)

# ── 테넌트 레지스트리 (인메모리 — 프로덕션에서는 DB 사용) ────────────────────
_tenants: Dict[str, Dict] = {
    "DEFAULT": {
        "tenant_id":    "DEFAULT",
        "name":         "기본 테넌트",
        "code":         "DEFAULT",
        "is_active":    True,
        "is_system":    True,
        "plan":         "ENTERPRISE",
        "quota": {
            "max_users":   1000,
            "max_servers": 500,
            "max_sr_per_month": 10000,
            "storage_gb":  100,
        },
        "settings": {
            "rate_limit_rpm": 600,
            "allow_external": False,
        },
        "created_at":   datetime.utcnow().isoformat(),
        "created_by":   "system",
    }
}

# ── 공개 헬퍼 함수 ───────────────────────────────────────────────────────────

def get_current_tenant_id() -> str:
    """현재 요청의 테넌트 ID 반환."""
    return _tenant_id_ctx.get()


def get_current_tenant() -> Optional[Dict]:
    """현재 요청의 테넌트 객체 반환."""
    return _tenant_ctx.get()


def set_tenant(tenant_id: str) -> None:
    """테넌트 컨텍스트 수동 설정 (테스트용)."""
    _tenant_id_ctx.set(tenant_id)
    _tenant_ctx.set(_tenants.get(tenant_id))


def get_tenant(tenant_id: str) -> Optional[Dict]:
    """테넌트 ID로 테넌트 조회."""
    return _tenants.get(tenant_id)


def list_tenants() -> list:
    """전체 테넌트 목록."""
    return list(_tenants.values())


def apply_tenant_filter(stmt: Any, model: Any,
                        tenant_field: str = "tenant_id") -> Any:
    """
    SQLAlchemy SELECT 구문에 테넌트 필터를 적용합니다.

    사용 예:
        stmt = apply_tenant_filter(select(SRRequest), SRRequest)
        results = await db.execute(stmt)
    """
    tenant_id = get_current_tenant_id()
    if tenant_id == "DEFAULT":
        return stmt  # DEFAULT 테넌트는 모든 데이터 접근

    col = getattr(model, tenant_field, None)
    if col is None:
        # 모델에 tenant_id 컬럼이 없으면 필터 미적용 (하위 호환)
        return stmt

    return stmt.where(col == tenant_id)


def require_tenant(tenant_id: str) -> None:
    """
    특정 테넌트가 활성 상태인지 검증합니다.
    비활성 테넌트면 ValueError를 발생시킵니다.
    """
    t = _tenants.get(tenant_id)
    if not t:
        raise ValueError(f"테넌트 '{tenant_id}'가 존재하지 않습니다.")
    if not t.get("is_active", False):
        raise ValueError(f"테넌트 '{tenant_id}'가 비활성 상태입니다.")


def register_tenant(
    tenant_id: str,
    name:      str,
    code:      str,
    plan:      str = "STANDARD",
    created_by: str = "system",
) -> Dict:
    """테넌트를 레지스트리에 등록합니다."""
    if tenant_id in _tenants:
        raise ValueError(f"테넌트 ID '{tenant_id}'가 이미 존재합니다.")
    record = {
        "tenant_id":  tenant_id,
        "name":       name,
        "code":       code,
        "is_active":  True,
        "is_system":  False,
        "plan":       plan,
        "quota": {
            "max_users":          100  if plan == "STANDARD" else 1000,
            "max_servers":        50   if plan == "STANDARD" else 500,
            "max_sr_per_month":   1000 if plan == "STANDARD" else 10000,
            "storage_gb":         10   if plan == "STANDARD" else 100,
        },
        "settings": {
            "rate_limit_rpm": 120 if plan == "STANDARD" else 600,
            "allow_external": False,
        },
        "created_at": datetime.utcnow().isoformat(),
        "created_by": created_by,
    }
    _tenants[tenant_id] = record
    logger.info("테넌트 등록: %s (%s)", tenant_id, name)
    return record


# ── Starlette/FastAPI 미들웨어 ────────────────────────────────────────────────

TENANT_HEADER   = "X-Tenant-ID"
OVERRIDE_HEADER = "X-Tenant-Override"   # ADMIN 전용 오버라이드
TENANT_EXEMPT_PATHS = {
    "/", "/login", "/customer", "/change-password",
    "/api/auth/login", "/api/auth/token",
    "/health", "/api/metrics/health",
    "/docs", "/openapi.json", "/redoc",
    "/static",
}


class TenantMiddleware(BaseHTTPMiddleware):
    """
    멀티테넌트 격리 미들웨어.

    처리 순서:
      1. 면제 경로 → 테넌트 컨텍스트 없이 통과
      2. X-Tenant-ID 헤더에서 테넌트 추출
      3. JWT 토큰의 tenant_id 클레임 추출 (헤더 없을 때)
      4. 기본값: "DEFAULT"
      5. 테넌트 활성 여부 검증
      6. ContextVar에 테넌트 설정 → downstream 핸들러에서 사용
    """

    def __init__(self, app: ASGIApp, default_tenant: str = "DEFAULT") -> None:
        super().__init__(app)
        self.default_tenant = default_tenant

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        # 면제 경로 처리
        path = request.url.path
        if any(path == ep or path.startswith(ep + "/") for ep in TENANT_EXEMPT_PATHS):
            return await call_next(request)

        # 테넌트 ID 추출 (우선순위: 헤더 > JWT > 기본값)
        tenant_id = (
            request.headers.get(TENANT_HEADER)
            or self._extract_from_jwt(request)
            or self.default_tenant
        )

        # 테넌트 존재·활성 검증
        tenant = _tenants.get(tenant_id)
        if tenant_id != self.default_tenant and not tenant:
            return JSONResponse(
                status_code=404,
                content={"detail": f"테넌트 '{tenant_id}'를 찾을 수 없습니다."},
            )
        if tenant and not tenant.get("is_active", True):
            return JSONResponse(
                status_code=403,
                content={"detail": f"테넌트 '{tenant_id}'가 비활성 상태입니다."},
            )

        # ContextVar 설정
        token_tid = _tenant_id_ctx.set(tenant_id)
        token_t   = _tenant_ctx.set(tenant or _tenants.get(self.default_tenant))

        try:
            response = await call_next(request)
            # 응답 헤더에 테넌트 ID 반영
            response.headers["X-Tenant-ID"] = tenant_id
            return response
        finally:
            _tenant_id_ctx.reset(token_tid)
            _tenant_ctx.reset(token_t)

    @staticmethod
    def _extract_from_jwt(request: Request) -> Optional[str]:
        """Authorization 헤더에서 JWT tenant_id 클레임 추출."""
        try:
            auth = request.headers.get("Authorization", "")
            if not auth.startswith("Bearer "):
                return None
            token = auth[7:]
            import jwt as pyjwt
            import os
            secret = os.getenv("GUARDIA_SECRET_KEY", "")
            payload = pyjwt.decode(token, secret, algorithms=["HS256"])
            return payload.get("tenant_id")
        except Exception:
            return None
