from contextlib import contextmanager
from contextvars import ContextVar, Token

from fastapi import HTTPException, Request

from .database import TenantDatabaseManager
from .service import TenantConfigService

_current_actor: ContextVar[str | None] = ContextVar(
    "current_actor",
    default=None,
)


class MultitenantConfig:
    def __init__(
        self,
        secret_dir: str,
        *,
        tenant_header: str = "X-Tenant",
        user_header: str = "X-UserId",
        default_tenant: str | None = None,
        default_actor: str | None = None,
        allowed_tenants: set[str] | None = None,
        strict_whitelist: bool = False,
        mode: str = "testing",  # testing | production
        echo_sql: bool = False,
        pool_size: int = 3,
        max_overflow: int = 0,
        pool_timeout: int = 5,
        pool_pre_ping: bool = True,
        app_name_prefix: str = "fastapi",
    ):
        self.tenant_header = tenant_header
        self.user_header = user_header
        self.default_tenant = default_tenant
        self.default_actor = default_actor

        self.allowed_tenants = allowed_tenants
        self.strict_whitelist = strict_whitelist

        self.mode = mode
        self.echo_sql = echo_sql
        self.pool_size = pool_size
        self.max_overflow = max_overflow
        self.pool_timeout = pool_timeout
        self.pool_pre_ping = pool_pre_ping
        self.app_name_prefix = app_name_prefix

        self.service = TenantConfigService(secret_dir)
        self.db = TenantDatabaseManager(self)

    # -------------------------
    # TENANT
    # -------------------------

    def get_tenant(self, request: Request) -> str:
        tenant = request.headers.get(self.tenant_header)

        if tenant and tenant.strip():
            tenant = tenant.strip().lower()
        else:
            tenant = self.default_tenant

        if not tenant:
            raise HTTPException(
                status_code=400,
                detail=f"Header {self.tenant_header} mancante",
            )

        # whitelist
        if self.allowed_tenants:
            if self.strict_whitelist and tenant not in self.allowed_tenants:
                raise HTTPException(status_code=403, detail="Tenant non consentito")

        # esistenza tenant
        try:
            self.service.get(tenant)
        except Exception:
            raise HTTPException(
                status_code=404,
                detail=f"Tenant '{tenant}' non trovato",
            )

        return tenant

    # -------------------------
    # ACTOR
    # -------------------------

    def get_actor(self, request: Request) -> str | None:
        actor = request.headers.get(self.user_header)
        return actor.strip() if actor else self.default_actor

    @contextmanager
    def actor_context(self, actor: str | None):
        token: Token = _current_actor.set(actor)
        try:
            yield
        finally:
            _current_actor.reset(token)

    def current_actor(self) -> str | None:
        return _current_actor.get()

    # -------------------------
    # CONFIG
    # -------------------------

    def get_tenant_config(self, request: Request):
        tenant = self.get_tenant(request)
        return self.service.get(tenant)

    # -------------------------
    # DB SESSION (dependency)
    # -------------------------

    async def get_db_session(self, request: Request):
        tenant = self.get_tenant(request)
        actor = self.get_actor(request)

        with self.actor_context(actor):
            async with self.db.session(tenant):
                yield

    # -------------------------
    # SESSION HELPERS
    # -------------------------

    def current_session(self):
        session = self.db.get_current_session()
        if session is None:
            raise RuntimeError("Nessuna sessione DB attiva")
        return session

    async def commit(self):
        await self.current_session().commit()

    async def rollback(self):
        await self.current_session().rollback()

    # -------------------------
    # UTILITY
    # -------------------------

    def list_tenants(self) -> list[str]:
        return self.service.list_tenants()

    def invalidate(self):
        self.service.invalidate()

    async def dispose(self):
        await self.db.dispose_all()