"""
Servidor mínimo de ejemplo (Flask) para las tablas de Ramaflex. Monta GET, PUT y DELETE en
{RAMAFLEX_RUTA_BASE}/{TABLA} y le pasa todo al núcleo (ramaflex_tablas.py).

Configuración por variables de entorno (ver LEEME.txt):
    RAMAFLEX_MOTOR      sqlserver | postgresql | mysql            (por defecto sqlserver)
    RAMAFLEX_CONEXION   cadena de conexión del driver (en mysql, un JSON con los argumentos de pymysql.connect)
    RAMAFLEX_TOKEN      token de servicio de ejemplo
    RAMAFLEX_RUTA_BASE  por defecto /ramaflex/tablas
    RAMAFLEX_PUERTO     por defecto 8080

El catálogo de las tablas no se configura: el núcleo lo lee de la base (XESQUEMA.CATALOGO).

Ejecutar: python servidor.py
El servidor de desarrollo de Flask no es para producción: en producción, montar la ruta en
el servidor de la API existente o usar waitress o gunicorn.
"""

import hmac
import json
import logging
import os
import sys
from urllib.parse import parse_qsl

from flask import Flask, Response, request

from ramaflex_tablas import TablasRamaflex

MOTOR = os.environ.get('RAMAFLEX_MOTOR', 'sqlserver')
CONEXION = os.environ.get('RAMAFLEX_CONEXION', '')
TOKEN = os.environ.get('RAMAFLEX_TOKEN', '')
RUTA_BASE = os.environ.get('RAMAFLEX_RUTA_BASE', '/ramaflex/tablas').rstrip('/')
PUERTO = int(os.environ.get('RAMAFLEX_PUERTO', '8080'))

if not CONEXION or not TOKEN:
    sys.exit('Faltan las variables RAMAFLEX_CONEXION y RAMAFLEX_TOKEN.')


def fabrica_de_conexiones():
    """Devuelve una función que abre una conexión DB-API nueva (sin autocommit) del motor elegido.
    Solo se importa el driver del motor configurado."""
    if MOTOR == 'sqlserver':
        import pyodbc  # pyodbc mantiene un pool de conexiones ODBC por su cuenta
        return lambda: pyodbc.connect(CONEXION)
    if MOTOR == 'postgresql':
        import psycopg2  # sin pool: para mucho tráfico, conviene psycopg2.pool
        return lambda: psycopg2.connect(CONEXION)
    if MOTOR == 'mysql':
        import pymysql
        opciones = {'charset': 'utf8mb4', **json.loads(CONEXION)}
        return lambda: pymysql.connect(**opciones)
    sys.exit(f'RAMAFLEX_MOTOR desconocido: {MOTOR}')


logging.basicConfig(level=logging.INFO)
tablas = TablasRamaflex(MOTOR, fabrica_de_conexiones())
app = Flask(__name__)


def autenticado():
    # SOLO DE EJEMPLO: reemplazar por la autenticación del resto de la API del ERP, con una
    # credencial de servicio. Un token fijo en una variable de entorno no es la idea en producción.
    recibido = request.headers.get('Authorization', '').encode('utf-8')
    return hmac.compare_digest(recibido, ('Bearer ' + TOKEN).encode('utf-8'))


# POST y PATCH se aceptan aquí solo para que el núcleo responda 405 METODO_NO_PERMITIDO con el
# cuerpo de error de la especificación.
@app.route(RUTA_BASE + '/<tabla>', methods=['GET', 'PUT', 'DELETE', 'POST', 'PATCH'])
def tabla_ramaflex(tabla):
    if not autenticado():
        estado, cuerpo = 401, json.dumps({'ok': False, 'codigo': 'NO_AUTENTICADO', 'mensaje': 'Credencial inválida.'})
    else:
        # parse_qsl conserva el orden y los repetidos (?KOFU=a&KOFU=b), que el núcleo necesita.
        parametros = parse_qsl(request.query_string.decode('utf-8', 'replace'), keep_blank_values=True)
        estado, cuerpo = tablas.atender(request.method, tabla, parametros, request.get_data(as_text=True))
    return Response(cuerpo, status=estado, content_type='application/json; charset=utf-8')


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=PUERTO)
