import requests
import datetime


class NatiLogClient:
    """
    Python client to interact with the NatiLog API.

    This client:
    - Authenticates via the login endpoint and stores the access token.
    - Checks the application state before sending events.
    - Sends events (DEBUG, INFO, WARNING, ERROR, CRITICAL) to the API.
    - Retries once on HTTP 401 by refreshing the token.

    Args:
        api_url (str): Base URL of the NatiLog API (e.g., "http://host/api").
        api_url_login (str): Login URL to obtain authentication tokens.
        app_id (int): Application ID used to tag events.
        username (str): API username.
        password (str): API password.
    """
    def __init__(self, api_url, api_url_login, app_id, username, password):
        """
        Initialize the client, obtain a token, and fetch application state.
        """
        self.api_url = api_url.rstrip("/")  # Strip trailing slash if present
        self.api_url_login = api_url_login
        self.app_id = app_id
        self.username = username
        self.password = password
        self.token = None
        self.app_estado = True  # Default: application is active
        self.get_natilog_token()
        self.actualizar_estado_aplicacion()

    def get_natilog_token(self):
        """
        Obtain an access token from the login endpoint.

        On failure (network/HTTP errors), sets `self.token` to None.
        """
        payload = {"username": self.username, "password": self.password}
        try:
            response = requests.post(self.api_url_login, json=payload, timeout=10)
            response.raise_for_status()  # Raises an error if the status code is not 200
            data = response.json()
            self.token = data.get("access") or data.get("token")
        except requests.exceptions.RequestException as e:
            self.token = None

    def actualizar_estado_aplicacion(self):
        """
        Refresh the application active state from the API.

        Behavior:
        - If there is no token, it tries to obtain one. If still unavailable,
          `self.app_estado` is set to False to block event sending.
        - Performs a GET to `/aplicaciones/{id}/`. If it gets 401, it refreshes
          the token and retries (also supports the URL without trailing slash).
        - On HTTP/network errors, defaults to `app_estado = True` (fail-open).
        """
        if not self.token: # if there is no token, the state cannot be verified, block event sending
            self.get_natilog_token()
            if not self.token:
                self.app_estado = False # to block records
            return
        headers = {"Authorization": f"Bearer {self.token}"} # authorization header
        try:
            response = requests.get(
                    f"{self.api_url}/aplicaciones/{self.app_id}/",
                    headers=headers,
                    timeout=5,
            )
            if response.status_code == 401:
                self.get_natilog_token() # if the token has expired, obtain a new one
                if not self.token:
                    self.app_estado = False
                    return
                headers["Authorization"] = f"Bearer {self.token}"
                response = requests.get(
                        f"{self.api_url}/aplicaciones/{self.app_id}",
                        headers=headers,
                        timeout=5,
                )
            response.raise_for_status()  # Raises an error if the response is not 200 OK
            data = response.json() or {}

            # Update the application state based on the response
            val = data.get("estado", False)
            if isinstance(val, bool):
                self.app_estado = val
            elif isinstance(val, (int, float)):
                self.app_estado = bool(val)
            elif isinstance(val, str):
                self.app_estado = val.strip().lower() in ("true", "1", "t", "yes", "y")
                # interprets common strings as booleans
            else:
                self.app_estado = True

        except requests.exceptions.RequestException: # in case of error, assume the app is active
            self.app_estado = True
            # if the app is inactive, events will not be recorded

    def registrar_evento(self, tipo_evento, mensaje, datos=None, fecha=None):
        """
        Send an event to the NatiLog API.

        Args:
            tipo_evento (str): Event type (e.g., "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG").
            mensaje (str): Event message.
            datos (dict, optional): Extra payload for the event. Defaults to {}.
            fecha (str, optional): ISO 8601 timestamp. If None, it is auto-generated.

        Returns:
            dict: API JSON response.

        Raises:
            requests.exceptions.HTTPError: If the final response is an HTTP error.
        """
        try:
            self.actualizar_estado_aplicacion()
        except Exception:
            pass
        if not self.app_estado:
            return {"detail": "Application inactive. Event omitted."}

        if fecha is None:
            fecha = (
                datetime.datetime.now().isoformat()
            )  # Current date and time in ISO 8601 format

        payload = {
            "aplicacion": self.app_id,
            "tipo_evento": tipo_evento,
            "mensaje": mensaje,
            "datos": datos
            or {},  # Additional data, if no data is provided, sends an empty dictionary
            "fecha": fecha,
        }

        if not self.token:
            self.get_natilog_token()

        headers = {"Content-Type": "application/json"}

        if self.token:
            headers["Authorization"] = f"Bearer {self.token}"

        response = requests.post(
            f"{self.api_url}/evento/", json=payload, headers=headers, timeout=5
        )

        # On 401, refresh token and retry once
        if response.status_code == 401:
            self.get_natilog_token()
            if not self.token:
                response.raise_for_status()
            headers["Authorization"] = f"Bearer {self.token}"
            response = requests.post(
                f"{self.api_url}/evento/", json=payload, headers=headers, timeout=5
            )

        response.raise_for_status()  # Raises an error if the response is not 200 OK
        return response.json()  # Returns the response in JSON format

    def debug(self, mensaje, datos=None, fecha=None):
        """Register a DEBUG event."""
        return self.registrar_evento("DEBUG", mensaje, datos, fecha)

    def error(self, mensaje, datos=None, fecha=None):
        """Register an ERROR event."""
        return self.registrar_evento("ERROR", mensaje, datos, fecha)

    def warning(self, mensaje, datos=None, fecha=None):
        """Register a WARNING event."""
        return self.registrar_evento("WARNING", mensaje, datos, fecha)

    def info(self, mensaje, datos=None, fecha=None):
        """Register an INFO event."""
        return self.registrar_evento("INFO", mensaje, datos, fecha)

    def critical(self, mensaje, datos=None, fecha=None):
        """Register a CRITICAL event."""
        return self.registrar_evento("CRITICAL", mensaje, datos, fecha)
