import secrets
import json
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
from threading import RLock

import mysql.connector
from fastapi import Request, Response

from app.settings import (
    DB_CONNECTION_TIMEOUT_SECONDS,
    DB_HOST,
    DB_PASSWORD,
    DB_USER,
    MYSQL_PORT,
    PASTA_PROCESSAMENTOS,
    nome_pasta_processamentos_para_escola,
)
from app.tenant import get_database_name, get_school
from app.db import auth_usa_banco

COOKIE_NAME = "remember_token"
DIAS_VALIDADE = 30
AUTH_FILE_LOCK = RLock()


def _caminho_auth() -> Path:
    pasta = PASTA_PROCESSAMENTOS / nome_pasta_processamentos_para_escola(get_school())
    pasta.mkdir(parents=True, exist_ok=True)
    return pasta / "auth.json"


def _carregar_auth() -> dict:
    try:
        dados = json.loads(_caminho_auth().read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        dados = {}
    return dados if isinstance(dados, dict) else {}


def _salvar_auth(dados: dict) -> None:
    caminho = _caminho_auth()
    temporario = caminho.with_suffix(".tmp")
    temporario.write_text(json.dumps(dados, ensure_ascii=False), encoding="utf-8")
    os.replace(temporario, caminho)


def obter_conexao_banco(database: str | None = None):
    return mysql.connector.connect(
        host=DB_HOST,
        port=MYSQL_PORT,
        user=DB_USER,
        password=DB_PASSWORD,
        database=str(database or get_database_name()),
        connection_timeout=DB_CONNECTION_TIMEOUT_SECONDS,
    )


def gerenciar_lembre_me(response: Response, usuario_id: int):
    token = secrets.token_hex(32)
    expira_em = datetime.now(timezone.utc) + timedelta(days=DIAS_VALIDADE)

    if not auth_usa_banco():
        with AUTH_FILE_LOCK:
            dados = _carregar_auth()
            tokens = dict(dados.get("tokens") or {})
            tokens[token] = {"usuario_id": usuario_id, "expira_em": expira_em.isoformat()}
            dados["tokens"] = tokens
            _salvar_auth(dados)
        response.set_cookie(key=COOKIE_NAME, value=token, expires=expira_em, httponly=True, secure=False, samesite="lax")
        return

    conexao = obter_conexao_banco()
    cursor = conexao.cursor()
    try:
        query = """
            INSERT INTO tokens_lembre_me (usuario_id, token, expira_em)
            VALUES (%s, %s, %s);
        """
        cursor.execute(query, (usuario_id, token, expira_em))
        conexao.commit()

        response.set_cookie(
            key=COOKIE_NAME,
            value=token,
            expires=expira_em,
            httponly=True,
            secure=False,
            samesite="lax",
        )
    finally:
        cursor.close()
        conexao.close()


def verificar_autologin_cookie(request: Request) -> int | None:
    token_cookie = request.cookies.get(COOKIE_NAME)
    if not token_cookie:
        return None

    if not auth_usa_banco():
        with AUTH_FILE_LOCK:
            dados = _carregar_auth()
            token = dict(dados.get("tokens") or {}).get(token_cookie) or {}
            try:
                expira_em = datetime.fromisoformat(str(token.get("expira_em") or ""))
            except ValueError:
                expira_em = datetime.min.replace(tzinfo=timezone.utc)
            if expira_em <= datetime.now(timezone.utc):
                remover_token_banco(token_cookie)
                return None
            return token.get("usuario_id")

    conexao = None
    cursor = None
    try:
        conexao = obter_conexao_banco()
        cursor = conexao.cursor(dictionary=True)
        query = """
            SELECT usuario_id, expira_em FROM tokens_lembre_me
            WHERE token = %s;
        """
        cursor.execute(query, (token_cookie,))
        resultado = cursor.fetchone()

        if resultado:
            if datetime.now() > resultado["expira_em"]:
                remover_token_banco(token_cookie)
                return None
            return resultado["usuario_id"]
    except mysql.connector.Error:
        # An unavailable database must not prevent the login form from loading.
        return None
    finally:
        if cursor:
            cursor.close()
        if conexao:
            conexao.close()
    return None


def remover_token_banco(token: str):
    if not auth_usa_banco():
        with AUTH_FILE_LOCK:
            dados = _carregar_auth()
            tokens = dict(dados.get("tokens") or {})
            tokens.pop(token, None)
            dados["tokens"] = tokens
            _salvar_auth(dados)
        return

    conexao = obter_conexao_banco()
    cursor = conexao.cursor()
    try:
        query = "DELETE FROM tokens_lembre_me WHERE token = %s;"
        cursor.execute(query, (token,))
        conexao.commit()
    finally:
        cursor.close()
        conexao.close()


def salvar_sessao_usuario(usuario_id, session_id, database: str | None = None):
    if not auth_usa_banco():
        with AUTH_FILE_LOCK:
            dados = _carregar_auth()
            sessoes = dict(dados.get("sessoes") or {})
            sessoes[str(usuario_id)] = str(session_id)
            dados["sessoes"] = sessoes
            _salvar_auth(dados)
        return

    conn = obter_conexao_banco(database)
    cursor = conn.cursor()
    try:
        # UPSERT atômico: DELETE+INSERT deixava uma janela em que o middleware
        # lia sessoes_ativas vazio e respondia "sessão expirada" no /painel.
        cursor.execute(
            """
            INSERT INTO sessoes_ativas (usuario_id, session_id)
            VALUES (%s, %s)
            ON DUPLICATE KEY UPDATE
                session_id = VALUES(session_id),
                atualizado_em = CURRENT_TIMESTAMP
            """,
            (usuario_id, session_id),
        )
        conn.commit()
    finally:
        cursor.close()
        conn.close()


def obter_sessao_usuario(usuario_id, database: str | None = None):
    if not auth_usa_banco():
        with AUTH_FILE_LOCK:
            return str((_carregar_auth().get("sessoes") or {}).get(str(usuario_id)) or "") or None

    conn = obter_conexao_banco(database)
    cursor = conn.cursor(dictionary=True)
    try:
        cursor.execute(
            """
            SELECT session_id
            FROM sessoes_ativas
            WHERE usuario_id = %s
            LIMIT 1
            """,
            (usuario_id,),
        )
        resultado = cursor.fetchone()
        if resultado:
            return resultado["session_id"]
        return None
    finally:
        cursor.close()
        conn.close()


def remover_sessao_usuario(usuario_id, database: str | None = None):
    if not auth_usa_banco():
        with AUTH_FILE_LOCK:
            dados = _carregar_auth()
            sessoes = dict(dados.get("sessoes") or {})
            sessoes.pop(str(usuario_id), None)
            dados["sessoes"] = sessoes
            _salvar_auth(dados)
        return

    conn = obter_conexao_banco(database)
    cursor = conn.cursor()
    try:
        cursor.execute(
            "DELETE FROM sessoes_ativas WHERE usuario_id = %s",
            (usuario_id,),
        )
        conn.commit()
    finally:
        cursor.close()
        conn.close()
