import time
import logging
import requests
import os
import random

import psutil
import hydra

from lablink_client_service.conf.structured_config import Config
from lablink_client_service.logger_utils import CloudAndConsoleLogger

# Default logger setup
logger = logging.getLogger(__name__)

MAX_API_RETRIES = 5
API_RETRY_DELAY = 10  # seconds


def is_process_running(process_name: str) -> bool:
    """
    Check if a specific process is running.
    """
    for proc in psutil.process_iter():
        try:
            if any(process_name in part for part in proc.cmdline()):
                if "update_inuse_status" in " ".join(proc.cmdline()):
                    continue
                return True
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            pass
    return False


def default_callback(process_name: str):
    """Default callback to log when a process is detected."""
    logger.info(f"Process '{process_name}' started.")


def listen_for_process(
    process_name: str, interval: int = 20, callback_func=None
) -> None:
    """Listen for a specific process to start or stop.

    Args:
        process_name (str): The name of the process to listen for.
        interval (int, optional): The interval (in seconds) to check the process status.
        callback_func (callable, optional): A callback function to execute when the
            process state changes.
    """

    # Set up a default callback function if none is provided
    if callback_func is None:
        callback_func = lambda: default_callback(process_name)

    logger.info(f"Monitoring process '{process_name}'")

    # Continuously check if the process is running
    process_running_prev = is_process_running(process_name)
    while True:
        # Check if the process is running now
        process_running_curr = is_process_running(process_name)

        # Compare the current state with the previous state
        if process_running_prev != process_running_curr:
            if callback_func:
                logger.info(f"Process '{process_name}' state changed.")
                callback_func()

        # Update the previous state
        process_running_prev = process_running_curr

        # Wait for the specified interval before checking again
        jitter = random.uniform(0, 5)
        time.sleep(interval + jitter)


def call_api(process_name, url, api_token=""):
    hostname = os.getenv("VM_NAME")
    status = is_process_running(process_name=process_name)

    headers = {}
    if api_token:
        headers["Authorization"] = f"Bearer {api_token}"

    retry_count = 0

    while retry_count < MAX_API_RETRIES:
        try:
            response = requests.post(
                url,
                json={"hostname": hostname, "status": status},
                headers=headers,
                timeout=(10, 20),
            )
            response.raise_for_status()
            logger.info(f"Updated in-use status: {status}")
            break  # Success, exit retry loop
        except requests.exceptions.Timeout:
            logger.warning(
                f"Status update timed out "
                f"(attempt {retry_count + 1}/{MAX_API_RETRIES})"
            )
        except requests.RequestException as e:
            logger.warning(
                f"Status update failed: {e} "
                f"(attempt {retry_count + 1}/{MAX_API_RETRIES})"
            )

        retry_count += 1
        if retry_count < MAX_API_RETRIES:
            jitter = random.uniform(0, 5)
            time.sleep(API_RETRY_DELAY + jitter)
    else:
        logger.error(
            f"Failed to update in-use status after {MAX_API_RETRIES} attempts"
        )


def api_callback(process_name: str, url: str, api_token: str = ""):
    """Callback to call the API when process state changes."""
    call_api(process_name, url, api_token=api_token)


@hydra.main(version_base=None, config_path="conf", config_name="config")
def main(cfg: Config) -> None:
    # Configure the logger
    global logger
    logger = CloudAndConsoleLogger(module_name="update_inuse_status")
    logger.info("Starting in-use status monitoring service")

    # Define the URL for the POST request
    # Use ALLOCATOR_URL env var if set (supports HTTPS),
    # otherwise use host:port with HTTP
    allocator_url = os.getenv("ALLOCATOR_URL")
    if allocator_url:
        base_url = allocator_url.rstrip("/")
    else:
        base_url = f"http://{cfg.allocator.host}:{cfg.allocator.port}"
    url = f"{base_url}/api/update_inuse_status"

    url = url.replace("://.", "://")

    api_token = os.getenv("API_TOKEN", "")

    # Start listening for the process
    listen_for_process(
        process_name=cfg.client.software,
        interval=20,
        callback_func=lambda: api_callback(cfg.client.software, url, api_token),
    )


if __name__ == "__main__":
    main()
