import json
import os
import re
import unicodedata
from pathlib import Path
from urllib.parse import urlparse

from dotenv import load_dotenv


# O backend é um repositório independente: configurações e dados locais ficam
# nele, sem depender da pasta que o contém.
BASE_DIR = Path(__file__).resolve().parent.parent
REPO_DIR = BASE_DIR

load_dotenv(BASE_DIR / ".env")


def _escola_padrao_keepedu() -> str:
    """Escola padrão: develop no ambiente local e produção nos demais."""
    escola_configurada = os.getenv("KEEPEDU_DEFAULT_SCHOOL", "").strip().lower()
    if escola_configurada:
        return escola_configurada
    ambiente = os.getenv("APP_ENV", "production").strip().lower() or "production"
    return "develop" if ambiente in {"local", "development", "dev"} else "proposito"


def _json_dict_from_env(nome_variavel: str) -> dict:
    valor = os.getenv(nome_variavel, "").strip()

    if not valor:
        return {}

    try:
        carregado = json.loads(valor)
    except json.JSONDecodeError:
        return {}

    return carregado if isinstance(carregado, dict) else {}


def _env_url_only(nome_variavel: str) -> str:
    """Só lê override explícito no .env — URLs multi-escola resolvem em runtime via tenant.keepedu_url()."""
    return os.getenv(nome_variavel, "").strip()


def _url_from_base(nome_variavel: str, caminho_padrao: str = "", fallback: str = "") -> str:
    valor = os.getenv(nome_variavel, "").strip()

    if valor:
        return valor

    base = os.getenv("KEEPEDU_API_BASE_URL", "").strip().rstrip("/")
    caminho_padrao = str(caminho_padrao or "").strip().lstrip("/")

    if not base and caminho_padrao:
        school = os.getenv("KEEPEDU_LOGIN_SCHOOL", "").strip().lower()
        if not school:
            school = _inferir_keepedu_school() or _escola_padrao_keepedu()
        base = f"https://{school}.keepedu.com.br/api"

    if base and caminho_padrao:
        return f"{base}/{caminho_padrao}"

    return str(fallback or "").strip()


def _keepedu_origin_from_school(school: str) -> str:
    slug = str(school or "").strip().lower() or _escola_padrao_keepedu()
    return f"https://{slug}.keepedu.com.br"


def _inferir_keepedu_school() -> str:
    valor = os.getenv("KEEPEDU_LOGIN_SCHOOL", "").strip()

    if valor:
        return valor

    for url in (
        os.getenv("KEEPEDU_LOGIN_URL", "").strip(),
        os.getenv("KEEPEDU_API_BASE_URL", "").strip(),
    ):
        if not url:
            continue

        try:
            hostname = urlparse(url).hostname or ""
        except ValueError:
            hostname = ""

        partes = [parte for parte in hostname.split(".") if parte]
        if len(partes) >= 3 and partes[0].lower() != "www":
            return partes[0]

    return ""


def _nome_banco_keepedu(school: str = "") -> str:
    slug = re.sub(r"[^a-z0-9_]", "_", str(school or "").strip().lower())
    slug = slug.strip("_") or "proposito"
    prefix = os.getenv("MYSQL_DATABASE_PREFIX", "").strip().rstrip("_")
    if prefix:
        return f"{prefix}_{slug}_omrcheck"
    return f"{slug}_omrcheck"


def _host_mysql_local(host: str) -> bool:
    return str(host or "").strip().lower() in {"127.0.0.1", "localhost"}


def _ambiente_local() -> bool:
    app_env = os.getenv("APP_ENV", "production").strip().lower() or "production"
    return app_env in {"local", "development", "dev"}


def ambiente_local() -> bool:
    return _ambiente_local()


def _database_padrao_mysql() -> str:
    """Nome da única base MySQL compartilhada entre as schools.

    A separação dos arquivos de processamento é feita em
    ``processamentos/keep_{school}_omrcheck``. Portanto, a school da sessão
    nunca deve alterar o nome da base MySQL.
    """
    database = os.getenv("MYSQL_DATABASE", "").strip()
    if database:
        return database

    # Compatibilidade: muitos .env de cPanel usam DB_NAME no lugar de MYSQL_DATABASE.
    db_name = os.getenv("DB_NAME", "").strip()
    if db_name:
        return db_name

    prefix = os.getenv("MYSQL_DATABASE_PREFIX", "").strip().rstrip("_")
    return f"{prefix}_omrcheck" if prefix else "omrcheck"


KEEPEDU_LOGIN_SCHOOL = _inferir_keepedu_school() or _escola_padrao_keepedu()

APP_ENV = os.getenv("APP_ENV", "production").strip().lower() or "production"
APP_TIMEZONE = os.getenv("APP_TIMEZONE", "America/Sao_Paulo").strip() or "America/Sao_Paulo"
# Define onde os lotes OMR são persistidos. Jobs, Kanban e autenticação usam
# MySQL sempre que MYSQL_ENABLED estiver ativo.
APP_STORAGE_BACKEND = os.getenv("APP_STORAGE_BACKEND", "file").strip().lower() or "file"
_mysql_enabled_default = "true" if APP_STORAGE_BACKEND == "mysql" else "false"
MYSQL_ENABLED = os.getenv("MYSQL_ENABLED", _mysql_enabled_default).strip().lower() in {
    "1", "true", "yes", "on"
}
# api = só JSON/endpoints (+ SSO/login/logout); web = também serve HTML legado
APP_MODE = os.getenv("APP_MODE", "api").strip().lower() or "api"
# Cartões originais são temporários: mesmo sem envio ao KeepEdu, não podem
# permanecer indefinidamente no servidor.
APP_OMR_ORIGINAIS_RETENTION_DAYS = max(
    1,
    int(os.getenv("APP_OMR_ORIGINAIS_RETENTION_DAYS", "7")),
)


def app_modo_api() -> bool:
    return APP_MODE == "api"

DATABASE_URL = os.getenv("DATABASE_URL", "").strip()
MYSQL_HOST = os.getenv("MYSQL_HOST", "mysql").strip() or "mysql"
MYSQL_PORT = max(1, int(os.getenv("MYSQL_PORT", "3306")))
MYSQL_DATABASE = _database_padrao_mysql()
MYSQL_USER = os.getenv("MYSQL_USER", "omrcheck").strip() or "omrcheck"
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "").strip()
MYSQL_SQL_ECHO = os.getenv("MYSQL_SQL_ECHO", "false").strip().lower() in {
    "1", "true", "yes", "on"
}

DB_HOST = os.getenv("DB_HOST", "").strip() or MYSQL_HOST
DB_USER = os.getenv("DB_USER", "").strip() or MYSQL_USER
DB_PASSWORD = os.getenv("DB_PASSWORD", "").strip() or MYSQL_PASSWORD
DB_NAME = os.getenv("DB_NAME", "").strip() or MYSQL_DATABASE
DB_CONNECTION_TIMEOUT_SECONDS = max(
    1,
    int(os.getenv("DB_CONNECTION_TIMEOUT_SECONDS", "5")),
)

PASTA_PROCESSAMENTOS = BASE_DIR / "processamentos"
PASTA_UPLOADS_TEMP = BASE_DIR / "uploads_temp"
PASTA_RUNTIME = BASE_DIR / "runtime"
PASTA_LOGS = BASE_DIR / "logs"
# Backups continuam fora do backend para não misturar dados arquivados com o runtime.
PASTA_BACKUPS = REPO_DIR / "backups"
PASTA_TEMPLATES_OMR = BASE_DIR / "templates_omr"
PASTA_BASE = BASE_DIR / "base"
PASTA_ASSETS = BASE_DIR / "assets"

CAMINHO_MODELO_CARTAO = PASTA_TEMPLATES_OMR / "modelo_cartao.xtmpl"
CAMINHO_ICONE = PASTA_ASSETS / "omrcheck.ico"
CAMINHO_JOBS = PASTA_RUNTIME / "jobs.json"
CAMINHO_KANBAN = PASTA_RUNTIME / "kanban.json"
CAMINHO_LOG_APP = PASTA_LOGS / f"omrcheck-{APP_ENV}.log"

KEEPEDU_ORIGIN_URL = _keepedu_origin_from_school(KEEPEDU_LOGIN_SCHOOL)
KEEPEDU_GESTOR_LOGIN_URL = f"{KEEPEDU_ORIGIN_URL}/gestor/login"
KEEPEDU_API_BASE_URL_CONFIGURADA = os.getenv("KEEPEDU_API_BASE_URL", "").strip().rstrip("/")
KEEPEDU_API_BASE_URL = (
    KEEPEDU_API_BASE_URL_CONFIGURADA
    or f"{KEEPEDU_ORIGIN_URL}/api"
)

KEEPEDU_BUSCAR_ID_URL = _env_url_only("KEEPEDU_BUSCAR_ID_URL")

KEEPEDU_API_KEY = os.getenv("KEEPEDU_API_KEY", "")

KEEPEDU_INSTITUTE = os.getenv(
    "KEEPEDU_INSTITUTE",
    ""
)
ID_PROVA_KEEPEDU = os.getenv("ID_PROVA_KEEPEDU", "")
KEEPEDU_IMPORTAR_RESPOSTAS_URL = _env_url_only("KEEPEDU_IMPORTAR_RESPOSTAS_URL")
KEEPEDU_SIMULAR_IMPORTAR_RESPOSTAS_URL = os.getenv(
     "KEEPEDU_SIMULAR_IMPORTAR_RESPOSTAS_URL",
     ""
).strip()
KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL = _env_url_only("KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL")
KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL = _env_url_only("KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL")
KEEPEDU_VER_FOLHA_RESPOSTA_URL = _env_url_only("KEEPEDU_VER_FOLHA_RESPOSTA_URL")
KEEPEDU_LOGIN_URL = _url_from_base(
    "KEEPEDU_LOGIN_URL",
    "gestor/user/login",
)
KEEPEDU_LISTAGEM_AVALIACAO_URL = _env_url_only("KEEPEDU_LISTAGEM_AVALIACAO_URL")
KEEPEDU_LISTAR_USUARIOS_URL = _env_url_only("KEEPEDU_LISTAR_USUARIOS_URL")
KEEPEDU_LISTAR_UNIDADES_URL = _env_url_only("KEEPEDU_LISTAR_UNIDADES_URL")
KEEPEDU_LISTAR_SERIES_URL = _env_url_only("KEEPEDU_LISTAR_SERIES_URL")
KEEPEDU_LISTAR_TURMAS_URL = _env_url_only("KEEPEDU_LISTAR_TURMAS_URL")
BERNOULLI_USUARIOS_URL = os.getenv(
    "BERNOULLI_USUARIOS_URL",
    "http://api.bernoulli.com.br/api/gerenciar/acessos/usuarios/listar"
).strip()
BERNOULLI_PAGE_SIZE = max(1, int(os.getenv("BERNOULLI_PAGE_SIZE", "10")))
BERNOULLI_GRUPO_USUARIO = os.getenv("BERNOULLI_GRUPO_USUARIO", "5").strip() or "5"
BERNOULLI_FRONT_VERSION = os.getenv("BERNOULLI_FRONT_VERSION", "4.25.72").strip()
BERNOULLI_PLATAFORMA = os.getenv("BERNOULLI_PLATAFORMA", "2").strip()
BERNOULLI_ORIGIN = os.getenv("BERNOULLI_ORIGIN", "https://mb4.bernoulli.com.br").strip()
BERNOULLI_REFERER = os.getenv("BERNOULLI_REFERER", "https://mb4.bernoulli.com.br/").strip()
BERNOULLI_LOGIN_URL = os.getenv("BERNOULLI_LOGIN_URL", "").strip()
BERNOULLI_LOGIN_USERNAME = os.getenv("BERNOULLI_LOGIN_USERNAME", "").strip()
BERNOULLI_LOGIN_PASSWORD = os.getenv("BERNOULLI_LOGIN_PASSWORD", "").strip()
BERNOULLI_LOGIN_USERNAME_FIELD = os.getenv("BERNOULLI_LOGIN_USERNAME_FIELD", "usuario").strip() or "usuario"
BERNOULLI_LOGIN_PASSWORD_FIELD = os.getenv("BERNOULLI_LOGIN_PASSWORD_FIELD", "senha").strip() or "senha"
BERNOULLI_LOGIN_HEADERS = _json_dict_from_env("BERNOULLI_LOGIN_HEADERS")
BERNOULLI_PARAMETROS_URL = os.getenv(
    "BERNOULLI_PARAMETROS_URL",
    "https://api.bernoulli.com.br/api/autenticado/parametros"
).strip()
BERNOULLI_PARAMETROS_METHOD = os.getenv("BERNOULLI_PARAMETROS_METHOD", "POST").strip().upper() or "POST"
BERNOULLI_PARAMETROS_USE_FORM = os.getenv("BERNOULLI_PARAMETROS_USE_FORM", "false").strip().lower() in {
    "1", "true", "yes", "on"
}
BERNOULLI_PARAMETROS_PAYLOAD = _json_dict_from_env("BERNOULLI_PARAMETROS_PAYLOAD")
BERNOULLI_PARAMETROS_HEADERS = _json_dict_from_env("BERNOULLI_PARAMETROS_HEADERS")
BERNOULLI_PARAMETROS_TOKEN_PATH = os.getenv(
    "BERNOULLI_PARAMETROS_TOKEN_PATH",
    "access_token"
).strip() or "access_token"
BERNOULLI_AUTH_CACHE_FILE = PASTA_RUNTIME / "bernoulli_auth.json"

def nome_banco_para_escola(school: str = "") -> str:
    """Compatibilidade para chamadas legadas: todas as schools usam a mesma base."""
    return MYSQL_DATABASE


def nome_pasta_processamentos_para_escola(school: str = "") -> str:
    """Nome técnico e estável da raiz de processamentos da escola.

    O nome exibido em ``KEEPEDU_SCHOOL_NAMES`` é apenas para a interface.
    O armazenamento precisa usar o slug da escola. A base MySQL é
    compartilhada, mas os arquivos de processamento permanecem separados.
    """
    return _nome_banco_keepedu(slug_escola(school) or KEEPEDU_LOGIN_SCHOOL)


def url_gestor_para_escola(school: str = "") -> str:
    slug = re.sub(r"[^a-z0-9-]", "", str(school or "").strip().lower()) or KEEPEDU_LOGIN_SCHOOL
    return f"https://{slug}.keepedu.com.br/gestor/"


def url_gestor_home_para_escola(school: str = "") -> str:
    return f"{url_gestor_para_escola(school)}home/"


def gestor_home_url_para_sessao(sessao: dict) -> str:
    url = str(sessao.get("gestor_home_url") or "").strip()
    if url:
        return url
    school = str(sessao.get("school") or sessao.get("keepedu_school") or "").strip()
    if not school:
        return ""
    return url_gestor_home_para_escola(school)


def _lista_escolas_keepedu() -> list[str]:
    bruto = os.getenv("KEEPEDU_SCHOOLS", "").strip()
    if bruto:
        return [
            item.strip().lower()
            for item in bruto.split(",")
            if item.strip()
        ]
    if KEEPEDU_LOGIN_SCHOOL:
        return [KEEPEDU_LOGIN_SCHOOL]
    return ["proposito"]


KEEPEDU_SCHOOLS = _lista_escolas_keepedu()

_SCHOOL_NAMES_PADRAO = {
    "proposito": "Colégio Propósito",
    "greatschool": "Great School",
    "develop": "Develop",
}
KEEPEDU_SCHOOL_NAMES = {
    **{k: str(v).strip() for k, v in _SCHOOL_NAMES_PADRAO.items() if str(v).strip()},
    **{
        str(k).strip().lower(): str(v).strip()
        for k, v in _json_dict_from_env("KEEPEDU_SCHOOL_NAMES").items()
        if str(k).strip() and str(v).strip()
    },
}


def slug_escola(sessao: dict | str = "") -> str:
    if isinstance(sessao, dict):
        school = str(sessao.get("school") or sessao.get("keepedu_school") or "").strip().lower()
    else:
        school = str(sessao or "").strip().lower()
    return school or KEEPEDU_LOGIN_SCHOOL or "proposito"


def nome_escola_para_sessao(sessao: dict) -> str:
    for chave in ("school_name", "nome_escola", "schoolName"):
        nome = str(sessao.get(chave) or "").strip()
        if nome:
            return nome

    school = slug_escola(sessao)
    nome_mapeado = KEEPEDU_SCHOOL_NAMES.get(school)
    if nome_mapeado:
        return nome_mapeado

    if school:
        return school.replace("-", " ").replace("_", " ").title()
    return "KeepEdu"


KEEPEDU_READING_BASE_URL = (
    os.getenv("KEEPEDU_READING_BASE_URL", "").strip().rstrip("/")
    or (KEEPEDU_ORIGIN_URL if _ambiente_local() else "https://reading.keepedu.com.br")
)
APP_AUTH_MODE = os.getenv("APP_AUTH_MODE", "keepedu_login").strip().lower() or "keepedu_login"

SSO_MYSQL_HOST = os.getenv("SSO_MYSQL_HOST", "").strip() or MYSQL_HOST
SSO_MYSQL_PORT = max(1, int(os.getenv("SSO_MYSQL_PORT", str(MYSQL_PORT))))
SSO_MYSQL_DATABASE = os.getenv("SSO_MYSQL_DATABASE", "").strip()
SSO_MYSQL_USER = os.getenv("SSO_MYSQL_USER", "").strip() or MYSQL_USER
SSO_MYSQL_PASSWORD = os.getenv("SSO_MYSQL_PASSWORD", "").strip() or MYSQL_PASSWORD
SSO_ENVIRONMENT = os.getenv("SSO_ENVIRONMENT", APP_ENV).strip() or APP_ENV

KEEPEDU_INSTITUTES = {
    str(chave).strip().lower(): str(valor).strip()
    for chave, valor in _json_dict_from_env("KEEPEDU_INSTITUTES").items()
    if str(chave).strip() and str(valor).strip()
}


def institute_id_para_escola(school: str = "") -> str:
    slug = re.sub(r"[^a-z0-9-]", "", str(school or "").strip().lower())
    if not slug:
        return ""
    return str(KEEPEDU_INSTITUTES.get(slug) or "").strip()


def tenant_debug_para_sessao(sessao: dict) -> dict:
    school = slug_escola(sessao)
    institute = str(sessao.get("keepedu_institute") or "").strip() or institute_id_para_escola(school)
    return {
        "escola": nome_escola_para_sessao(sessao),
        "school": school,
        "institute": institute,
        "database": nome_banco_para_escola(school) if school else "",
        "api_base_url": f"{_keepedu_origin_from_school(school)}/api" if school else "",
    }

APP_ENABLE_AUTH = os.getenv("APP_ENABLE_AUTH", "false").strip().lower() in {
    "1", "true", "yes", "on"
}
APP_AUTH_SESSION_VALIDATION_SECONDS = max(
    1,
    int(os.getenv("APP_AUTH_SESSION_VALIDATION_SECONDS", "30")),
)
APP_SESSION_SECRET = (
    os.getenv("APP_SESSION_SECRET", "").strip()
    or "omrcheck-session-secret-change-me"
)
APP_SESSION_COOKIE_NAME = os.getenv("APP_SESSION_COOKIE_NAME", "omrcheck_session").strip() or "omrcheck_session"
APP_ALLOWED_HOSTS = [
    host.strip()
    for host in os.getenv("APP_ALLOWED_HOSTS", "").split(",")
    if host.strip()
]
APP_CORS_ORIGINS = [
    origin.strip().rstrip("/")
    for origin in os.getenv("APP_CORS_ORIGINS", "").split(",")
    if origin.strip()
]
FRONTEND_PUBLIC_URL = os.getenv("FRONTEND_PUBLIC_URL", "http://127.0.0.1:5173").strip().rstrip("/")


def host_frontend_publico() -> str:
    try:
        return (urlparse(FRONTEND_PUBLIC_URL).hostname or "").strip().lower()
    except ValueError:
        return ""


def app_session_same_site() -> str:
    """Lax entre subdomínios keepedu; None quando o React está em outro site (ex.: Pages)."""
    configurado = os.getenv("APP_SESSION_SAME_SITE", "").strip().lower()
    if configurado in {"lax", "strict", "none"}:
        return configurado

    host_front = host_frontend_publico()
    if not host_front:
        return "lax"
    if host_front.endswith(".keepedu.com.br") or host_front == "keepedu.com.br":
        return "lax"
    if host_front in {"localhost", "127.0.0.1"}:
        return "lax"
    # Cloudflare Pages / outro domínio → cookie precisa ir no fetch cross-site
    return "none"


HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", "8000"))


def garantir_pastas():
    if not (MYSQL_ENABLED and APP_STORAGE_BACKEND == "mysql"):
        PASTA_PROCESSAMENTOS.mkdir(parents=True, exist_ok=True)
    PASTA_UPLOADS_TEMP.mkdir(parents=True, exist_ok=True)
    PASTA_RUNTIME.mkdir(parents=True, exist_ok=True)
    PASTA_LOGS.mkdir(parents=True, exist_ok=True)
    PASTA_BACKUPS.mkdir(parents=True, exist_ok=True)
    PASTA_TEMPLATES_OMR.mkdir(parents=True, exist_ok=True)
    PASTA_BASE.mkdir(parents=True, exist_ok=True)
    PASTA_ASSETS.mkdir(parents=True, exist_ok=True)
