"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Fri, 14th Nov, 2025

    OBJECTIVE:

        This is the central location for the Quart module.
        We define the app here, and import and attach all blueprints here.

    REFERENCES:

        N/A

    DOWNLOADS:

        N/A

"""


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# To make sibling directories accessible for imports:
import sys
sys.path.append("blueprints")
sys.path.append("")

# For system level activities:
import gc
import os
import psutil

# For using Quart:
from quart import Quart, current_app
from quart_cors import cors

# My utils:
from utils_v2.date_time import date_time
from utils_v2.system import files
from utils_v2.api.async_quart import (
    set_api_version,
    log_request_to_mongo,
)

# Shared elements:
from backend.shared import constants

# To make REST API calls:
import httpx

# For asynchronous activities:
import asyncio

# For debugging:
from icecream import IceCreamDebugger

# All the blueprints:
from backend.api.blueprints.reports.in_out_report import in_out_report_bp
from backend.api.blueprints.reports.muster_roll import muster_roll_report_bp

# The helpers:
from backend.api.helpers import muster_roll


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# Quart related:
MODULE_BASE = constants.MODULE_NAME
APP_VERSION = constants.APP_VERSION


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# The Quart app:
app = Quart(__name__)
app = cors(app)
# ---
app.register_blueprint(in_out_report_bp, url_prefix = f"/{MODULE_BASE}/reports/in-out")
app.register_blueprint(muster_roll_report_bp, url_prefix = f"/{MODULE_BASE}/reports/muster-roll")


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


@app.before_serving
@set_api_version(api_version = APP_VERSION)
# @log_request_to_mongo(
#     attr_name = "logs_mongo",
#     project = constants.PROJECT_NAME,
#     log_type = constants.MODULE_NAME,
#     operation = "apiStart",
#     log_input = True,
#     log_output = True
# )
async def app_startup(**kwargs):

    """
    To initialize the variables that you would like to use in this module.
    WARNING: ALL VARIABLES WILL BE INITIALIZED 'n' NUMBER OF TIMES, WHERE 'n' IS THE COUNT OF WORKERS DEPLOYED.
    SO, IF YOU WANT TO CONNECT TO A DATABASE AND YOU ALLOW A POOL-SIZE OF 10 AND IF YOU DEPLOY 4 WORKERS, YOU WILL END
    UP WITH 40 CONNECTIONS TO THE DATABASE.
    :return: None.
    """

    # Safe-halt mechanism for upgrades (for a single-worker run):
    current_app.is_under_maintenance = False

    # ┳┓  ┓       •
    # ┃┃┏┓┣┓┓┏┏┓┏┓┓┏┓┏┓
    # ┻┛┗ ┗┛┗┻┗┫┗┫┗┛┗┗┫
    #          ┛ ┛    ┛

    # Debugging:
    enable_debugging = True if os.environ["DEBUG"].strip().lower() == "true" else False
    current_app.printer = IceCreamDebugger(prefix = f"{MODULE_BASE} (Q) | ", includeContext = True)
    if not enable_debugging: current_app.printer.disable()
    current_app.printer("initializing worker.")

    # ┓┏┏┳┓┏┳┓┏┓  ┏┓┓•
    # ┣┫ ┃  ┃ ┃┃  ┃ ┃┓┏┓┏┓╋
    # ┛┗ ┻  ┻ ┣┛  ┗┛┗┗┗ ┛┗┗

    # # Make an instance of an HTTP client to use to make API calls:
    # current_app.http_client = httpx.AsyncClient(
    #     limits = httpx.Limits(
    #         max_connections = 100,  # ............ Maximum number of connections allowed in the pool.
    #         max_keepalive_connections = 50,  # ... Maximum number of connections that can be kept alive.
    #     ),
    #     timeout = httpx.Timeout(
    #         pool = 120.0,  # .... Time to wait for a free connection from the pool.
    #         connect = 2.5,  # ... Time to wait for establishing a connection to the server.
    #         write = 10.0,  # .... Time to wait for sending data.
    #         read = 9.9  # ....... Time to wait for receiving data.
    #     )
    # )
    # current_app.printer("HTTP client ready.")

    # ┏┓     ┓       ┓  ┳┓
    # ┃ ┏┓┏┓┏┫  ┏┓┏┓┏┫  ┃┃┏┓╋┏┓
    # ┗┛┛ ┗ ┗┻  ┗┻┛┗┗┻  ┻┛┗┻┗┗┻

    # # Get the script credentials and data:
    # script_id = os.environ["SCRIPT_ID"]
    # response = await current_app.http_client.get(
    #     url = r"https://nexcom.ditscentre.in/internal/cred/get",
    #     headers = {"X-Script-Id": script_id}
    # )
    # script_cred = response.json().get("data")
    # response = await current_app.http_client.get(
    #     url = r"https://nexcom.ditscentre.in/internal/data/get",
    #     headers = {"X-Script-Id": script_id}
    # )
    # current_app.script_data = response.json().get("data")
    # current_app.printer("Cred and data ready.", type(script_cred), type(current_app.script_data))

    # ┏┓
    # ┗┓┓┏┏╋┏┓┏┳┓
    # ┗┛┗┫┛┗┗ ┛┗┗
    #    ┛

    pass

    # ┏┓   ┓
    # ┃ ┏┓┏┣┓┏┓
    # ┗┛┗┻┗┛┗┗

    pass

    # ┳┳┓        ┳┓┳┓
    # ┃┃┃┏┓┏┓┏┓┏┓┃┃┣┫
    # ┛ ┗┗┛┛┗┗┫┗┛┻┛┻┛
    #         ┛

    pass

    # ┏┓
    # ┃ ┏┓┏┓┏┓┏┓┏╋┏┓┏┓┏
    # ┗┛┗┛┛┗┛┗┗ ┗┗┗┛┛ ┛

    pass

    # ┏┓         ┓┓
    # ┃ ┏┓┏┓╋┏┓┏┓┃┃┏┓┏┓┏
    # ┗┛┗┛┛┗┗┛ ┗┛┗┗┗ ┛ ┛

    pass

    # ┳┳┓•
    # ┃┃┃┓┏┏
    # ┛ ┗┗┛┗

    # Semaphores:
    current_app.chrome_semaphore = asyncio.Semaphore(1)

    # Helpers:
    muster_roll.init()

    # ┏┓┓
    # ┃ ┃┏┓┏┓┏┓┓┏┏┓
    # ┗┛┗┗ ┗┻┛┗┗┻┣┛
    #            ┛

    # Remove unwanted/sensitive variables from RAM:
    # del script_cred
    gc.collect()

    # Done here!
    current_app.printer("Worker ready!")


# ---------------------------------------------------------------------------------------------------------------------


@app.after_serving
@set_api_version(api_version = APP_VERSION)
# @log_request_to_mongo(
#     attr_name = "logs_mongo",
#     project = constants.PROJECT_NAME,
#     log_type = constants.MODULE_NAME,
#     operation = "apiStop",
#     log_input = True,
#     log_output = True
# )
async def app_shutdown(**kwargs):

    """
    This is called when "app.shutdown()" is called.
    :return: None.
    """

    current_app.printer("Shutting down...")


# ---------------------------------------------------------------------------------------------------------------------


@app.route(f"/", methods = ["GET", "POST"])
@app.route(f"/{MODULE_BASE}", methods = ["GET", "POST"])
async def root():

    """
    To check if the service is running or not.
    Use this to monitor the service from your "watchman" script.
    :return: only "ok"
    """

    return "ok"


# ---------------------------------------------------------------------------------------------------------------------


@app.route(f"/metrics/memory", methods = ["GET", "POST"])
@app.route(f"/{MODULE_BASE}/metrics/memory", methods = ["GET", "POST"])
async def memory_metrics():

    """
    TO measure the metrics of the app.
    :return: A JSON of the metrics.
    """

    # Figure pout the parent process.
    # This is important for multi-worker environments:
    parent_pid = os.getppid()
    parent_process = psutil.Process(parent_pid)
    parent_mem_info = parent_process.memory_info()

    # Figure out all the children of the parent process:
    child_pids = [child.pid for child in parent_process.children()]
    child_processes = [psutil.Process(child_pid) for child_pid in child_pids]
    child_stats = []
    for pid, process in zip(child_pids, child_processes):
        mem_info = process.memory_info()
        child_stats.append({
            "pid": pid,
            "mem": round(mem_info.rss / (1024 ** 2), 2)
        })

    # Construct the response:
    metrics_json = {
        "project": constants.PROJECT_NAME,
        "parent": {
            "pid": parent_pid,
            "mem": round(parent_mem_info.rss / (1024 ** 2), 2)
        },
        "children": child_stats,
        "unit": {
            "mem": "MB"
        },
        "ts": date_time.get_current_ist_date_time(as_string = True)
    }

    # Done here:
    return metrics_json


# ---------------------------------------------------------------------------------------------------------------------


@app.route(f"/{MODULE_BASE}/debug/<action>", methods = ["POST", "GET"])
async def change_debug(action):

    """
    Enable or disable debugging for the entire microservice.
    WARNING: NOT RECOMMENDED FOR MULTI-WORKER DEPLOYMENTS.
    :param action: "enable" to allow debugging on the terminal, or "disable".
    :return: "enabled"/"disabled" if successful, else "ok"
    """

    # Enable or disable debugging only if the password matches:
    action = action.lower()
    if action == "enable": current_app.printer.enable()
    elif action == "disable": current_app.printer.disable()
    return "ok"


# ---------------------------------------------------------------------------------------------------------------------


@app.route(f"/{MODULE_BASE}/maintenance/<action>", methods = ["POST", "GET"])
async def change_maintenance(action):

    """
    Enable or disable debugging for the entire microservice.
    WARNING: NOT RECOMMENDED FOR MULTI-WORKER DEPLOYMENTS.
    :param action: "enable" to stop taking new requests on the API, or "disable".
    :return: "enabled"/"disabled" if successful, else "ok"
    """

    # Enable or disable debugging only if the password matches:
    action = action.lower()
    if action == "enable":
        current_app.is_under_maintenance = True
        os.environ["IS_UNDER_MAINTENANCE"] = "True"
    elif action == "disable":
        current_app.is_under_maintenance = False
        os.environ["IS_UNDER_MAINTENANCE"] = "False"
    return "ok"


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    # To get args from the terminal:
    import argparse

    # To run the ASGI:
    import uvicorn
    from multiprocessing import freeze_support

    # Get the config from the command-line:
    parser = argparse.ArgumentParser(description = f"Microservice for '{MODULE_BASE}' API.")
    parser.add_argument(
        "--workers",
        type = int,
        help = "The no. of threads to spin up for this instance!",
        default = 2
    )
    parser.add_argument(
        "--host",
        type = str,
        help = "The host for the app. e.g.: '0.0.0.0' or '127.0.0.1'.",
        default = "127.0.0.1"
    )
    parser.add_argument(
        "--port",
        type = int,
        help = "The port no. to bind the app to.",
        default = 8080
    )
    parser.add_argument(
        "--script-id",
        type = str,
        help = "The id of this script (will affect the loaded config)."
    )
    parser.add_argument(
        "--debug",
        action = "store_true",
        help = "Whether, or not, you want to see debugging messages in the terminal.",
        default = False
    )
    args = parser.parse_args()

    # Note down the config;
    os.environ["SCRIPT_ID"] = args.script_id
    os.environ["DEBUG"] = str(args.debug)

    # Run the gateway:
    freeze_support()
    uvicorn.run(
        app = "main:app",
        workers = args.workers,
        host = args.host,
        port = args.port
    )
