# -*- coding: utf-8 -*-
"""Autenticación ligera (token HMAC) para el frontend React de SKEEN.

Token = base64url(payload_json) + "." + firma HMAC-SHA256, sin tabla de sesiones.
El secreto vive en ir.config_parameter 'skeen.frontend.secret' (se genera al primer uso).
"""

import base64
import functools
import hashlib
import hmac
import json
import secrets
import time

from odoo.http import request, Response

TTL_SECONDS = 60 * 60 * 12  # 12 horas
SECRET_KEY = 'skeen.frontend.secret'

RANK = {
    'lectura': 10,
    'medico': 50,
    'recepcion': 50,
    'admin': 100,
}


def _cors_headers():
    return {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    }


def _json(data, status=200):
    return Response(
        json.dumps(data, default=str),
        status=status,
        mimetype='application/json',
        headers=_cors_headers(),
    )


def _b64url_encode(raw: bytes) -> str:
    return base64.urlsafe_b64encode(raw).rstrip(b'=').decode('ascii')


def _b64url_decode(s: str) -> bytes:
    pad = '=' * (-len(s) % 4)
    return base64.urlsafe_b64decode(s + pad)


def _get_secret(env) -> str:
    ICP = env['ir.config_parameter'].sudo()
    secret = ICP.get_param(SECRET_KEY)
    if not secret:
        secret = secrets.token_urlsafe(48)
        ICP.set_param(SECRET_KEY, secret)
    return secret


def sign_token(env, user) -> str:
    payload = {
        'uid': user.id,
        'login': user.login,
        'role': user.role,
        'exp': int(time.time()) + TTL_SECONDS,
    }
    body = _b64url_encode(json.dumps(payload, separators=(',', ':')).encode('utf-8'))
    sig = hmac.new(_get_secret(env).encode('utf-8'), body.encode('ascii'), hashlib.sha256).hexdigest()
    return f'{body}.{sig}'


def verify_token(env, token: str):
    if not token or '.' not in token:
        return None
    body, sig = token.rsplit('.', 1)
    expected = hmac.new(_get_secret(env).encode('utf-8'), body.encode('ascii'), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        return None
    try:
        payload = json.loads(_b64url_decode(body).decode('utf-8'))
    except Exception:
        return None
    if int(payload.get('exp', 0)) < int(time.time()):
        return None
    return payload


def current_user(env):
    """Devuelve el usuario autenticado (record) o None."""
    auth = request.httprequest.headers.get('Authorization', '') or ''
    if not auth.lower().startswith('bearer '):
        return None
    token = auth[7:].strip()
    payload = verify_token(env, token)
    if not payload:
        return None
    user = env['skeen.frontend.user'].sudo().browse(payload.get('uid'))
    if not user.exists() or not user.active:
        return None
    return user


def require_role(min_role='lectura'):
    """Decorator: exige token válido y un rol con rango >= min_role.

    Deja pasar OPTIONS (preflight CORS) sin autenticación.
    """
    required = RANK.get(min_role, 10)

    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kw):
            if request.httprequest.method == 'OPTIONS':
                return _json({}, 200)
            user = current_user(request.env)
            if not user:
                return _json({'status': 'error', 'message': 'No autenticado'}, 401)
            if user.role_rank() < required:
                return _json({'status': 'error', 'message': 'Sin permiso'}, 403)
            request.frontend_user = user
            return func(self, *args, **kw)
        return wrapper
    return decorator
