from __future__ import annotations

import functools
import os
from contextvars import ContextVar
from typing import Any, Callable, TypeVar

from starlette.concurrency import run_in_threadpool as _run_in_threadpool

from app.settings import (
    KEEPEDU_API_BASE_URL,
    KEEPEDU_API_BASE_URL_CONFIGURADA,
    KEEPEDU_INSTITUTE,
    KEEPEDU_LOGIN_SCHOOL,
    MYSQL_DATABASE,
    _keepedu_origin_from_school,
    institute_id_para_escola,
)

_tenant_school: ContextVar[str] = ContextVar("tenant_school", default="")
_tenant_institute: ContextVar[str] = ContextVar("tenant_institute", default="")

T = TypeVar("T")


def set_tenant_context(school: str = "", institute: str = "") -> None:
    _tenant_school.set(str(school or "").strip().lower())
    _tenant_institute.set(str(institute or "").strip())


def clear_tenant_context() -> None:
    _tenant_school.set("")
    _tenant_institute.set("")


def snapshot_tenant_context() -> tuple[str, str]:
    return _tenant_school.get(), _tenant_institute.get()


async def run_in_threadpool(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
    """Como o run_in_threadpool do Starlette, mas reaplica school/institute na thread.

    Sem isso, rotas como /imagem perdem o Institute da sessão, a consulta ao
    KeepEdu falha e a galeria devolve um canvas em branco (HTTP 200).
    """
    school, institute = snapshot_tenant_context()

    @functools.wraps(func)
    def _com_tenant(*a: Any, **kw: Any) -> T:
        set_tenant_context(school, institute)
        try:
            return func(*a, **kw)
        finally:
            clear_tenant_context()

    return await _run_in_threadpool(_com_tenant, *args, **kwargs)


def get_school() -> str:
    school = _tenant_school.get().strip().lower()
    if school:
        return school
    return KEEPEDU_LOGIN_SCHOOL or "proposito"


def get_database_name() -> str:
    """A base operacional é compartilhada por todas as schools."""
    return MYSQL_DATABASE


def get_api_base_url() -> str:
    # Uma URL declarada no .env representa uma integração fixa (ex.: Genesis
    # no ambiente local) e deve prevalecer sobre o slug legado da sessão.
    if KEEPEDU_API_BASE_URL_CONFIGURADA:
        return KEEPEDU_API_BASE_URL_CONFIGURADA
    school = _tenant_school.get().strip().lower()
    if school:
        return f"{_keepedu_origin_from_school(school)}/api"
    return KEEPEDU_API_BASE_URL


def get_institute_id() -> str:
    institute = _tenant_institute.get().strip()
    if institute:
        return institute

    school = _tenant_school.get().strip().lower()
    if school:
        # Em desenvolvimento pode haver uma escola na sessão sem um mapa
        # específico em KEEPEDU_INSTITUTES. Nesse caso, use o instituto global
        # configurado em KEEPEDU_INSTITUTE.
        return institute_id_para_escola(school) or KEEPEDU_INSTITUTE

    return KEEPEDU_INSTITUTE


def apply_tenant_from_session(sessao: dict) -> None:
    school = str(sessao.get("school") or sessao.get("keepedu_school") or "").strip().lower()
    institute = str(sessao.get("keepedu_institute") or "").strip()
    if not institute and school:
        institute = institute_id_para_escola(school) or KEEPEDU_INSTITUTE
    set_tenant_context(school, institute)


def keepedu_url(caminho: str) -> str:
    base = get_api_base_url().rstrip("/")
    caminho_limpo = str(caminho or "").strip().lstrip("/")
    if not caminho_limpo:
        return base
    return f"{base}/{caminho_limpo}"


def resolve_keepedu_url(caminho: str, env_var: str = "") -> str:
    """Override explícito no .env ou URL da escola ativa na sessão."""
    if env_var:
        valor = os.getenv(env_var, "").strip()
        if valor:
            return valor
    return keepedu_url(caminho)
