import json
import functools
import logging
import time

import jwt as pyjwt

from odoo.http import request

_logger = logging.getLogger(__name__)

_jwt_secret_cache = {"secret": None, "ts": 0}
_JWT_SECRET_TTL = 300

_user_exists_cache = {}
_USER_CACHE_TTL = 60
_USER_CACHE_MAX = 200


def _get_jwt_secret():
    now = time.time()
    if _jwt_secret_cache["secret"] and (now - _jwt_secret_cache["ts"]) < _JWT_SECRET_TTL:
        return _jwt_secret_cache["secret"]
    secret = (
        request.env["ir.config_parameter"]
        .sudo()
        .get_param("encoach.jwt_secret")
    )
    if secret:
        _jwt_secret_cache["secret"] = secret
        _jwt_secret_cache["ts"] = now
    return secret


def validate_token():
    """Decode JWT Bearer token and return the corresponding ``res.users`` record or None."""
    auth_header = request.httprequest.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        return None
    token = auth_header[7:]
    secret = _get_jwt_secret()
    if not secret:
        _logger.error("System parameter 'encoach.jwt_secret' is not configured")
        return None
    try:
        payload = pyjwt.decode(token, secret, algorithms=["HS256"])
    except pyjwt.ExpiredSignatureError:
        return None
    except pyjwt.InvalidTokenError:
        return None
    user_id = payload.get("user_id")
    if not user_id:
        return None

    now = time.time()
    cache_key = int(user_id)
    cached = _user_exists_cache.get(cache_key)
    if cached and (now - cached["ts"]) < _USER_CACHE_TTL:
        return request.env["res.users"].sudo().browse(cache_key)

    user = request.env["res.users"].sudo().browse(cache_key)
    if not user.exists():
        return None

    _user_exists_cache[cache_key] = {"ts": now}
    if len(_user_exists_cache) > _USER_CACHE_MAX:
        oldest_key = min(_user_exists_cache, key=lambda k: _user_exists_cache[k]["ts"])
        del _user_exists_cache[oldest_key]
    return user


def jwt_required(func):
    """Decorator that validates the JWT token and sets request.env user context."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        user = validate_token()
        if not user:
            return _error_response("Authentication required", status=401)
        request.update_env(user=user.id)
        return func(*args, **kwargs)
    return wrapper


def _json_response(data, status=200):
    return request.make_json_response(data, status=status)


def _error_response(message, status=400, code=None):
    body = {"error": message}
    if code:
        body["code"] = code
    return request.make_json_response(body, status=status)


def _get_json_body():
    try:
        return json.loads(request.httprequest.get_data(as_text=True))
    except (ValueError, TypeError):
        return {}


def _paginate(model_or_kwargs, domain=None, page=0, size=20, order='id desc'):
    """Paginate an Odoo model search or extract params from a kwargs dict.

    Two calling conventions:
      _paginate(Model, domain, page, size, order)  → (recordset, total_count)
      _paginate(kwargs_dict)                        → (offset, limit, page)
    """
    if isinstance(model_or_kwargs, dict):
        kwargs = model_or_kwargs
        limit = min(max(int(kwargs.get("size", kwargs.get("limit", 20))), 1), 200)
        page_raw = int(kwargs.get("page", 0))
        pg = max(page_raw, 0)
        offset = pg * limit
        return offset, limit, pg

    Model = model_or_kwargs
    limit = min(max(int(size), 1), 200)
    pg = max(int(page), 0)
    offset = pg * limit
    total = Model.search_count(domain or [])
    records = Model.search(domain or [], offset=offset, limit=limit, order=order)
    return records, total


class EncoachMixin:
    """Shared authentication and response helpers for all EnCoach API controllers."""

    def _get_jwt_secret(self):
        return _get_jwt_secret()

    def _authenticate(self):
        return validate_token()

    def _json_response(self, data, status=200):
        return _json_response(data, status=status)

    def _error_response(self, message, status=400, code=None):
        return _error_response(message, status=status, code=code)

    def _get_json_body(self):
        return _get_json_body()

    def _paginate_params(self, kwargs):
        return _paginate(kwargs)

    def _serialize(self, record):
        if hasattr(record, "to_encoach_dict"):
            return record.to_encoach_dict()
        return {"id": record.id}

    def _serialize_list(self, records):
        return [self._serialize(r) for r in records]
