#!/usr/bin/env python

import os
import sys

# Register NetAlertX directories
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])

from logger import mylog, Logger  # noqa: E402 [flake8 lint suppression]
from helper import get_setting_value  # noqa: E402 [flake8 lint suppression]
from const import logPath, fullDbPath  # noqa: E402 [flake8 lint suppression]
import conf  # noqa: E402 [flake8 lint suppression]
from pytz import timezone  # noqa: E402 [flake8 lint suppression]
from database import get_temp_db_connection  # noqa: E402 [flake8 lint suppression]

# Make sure the TIMEZONE for logging is correct
conf.tz = timezone(get_setting_value("TIMEZONE"))

# Make sure log level is initialized correctly
Logger(get_setting_value("LOG_LEVEL"))

pluginName = "DBCLNP"

LOG_PATH = logPath + "/plugins"
LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log")
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")


def main():

    PLUGINS_KEEP_HIST = int(get_setting_value("PLUGINS_KEEP_HIST"))
    HRS_TO_KEEP_NEWDEV = int(get_setting_value("HRS_TO_KEEP_NEWDEV"))
    HRS_TO_KEEP_OFFDEV = int(get_setting_value("HRS_TO_KEEP_OFFDEV"))
    DAYS_TO_KEEP_EVENTS = int(get_setting_value("DAYS_TO_KEEP_EVENTS"))
    CLEAR_NEW_FLAG = get_setting_value("CLEAR_NEW_FLAG")

    mylog("verbose", [f"[{pluginName}] In script"])

    # Execute cleanup/upkeep
    cleanup_database(
        fullDbPath,
        DAYS_TO_KEEP_EVENTS,
        HRS_TO_KEEP_NEWDEV,
        HRS_TO_KEEP_OFFDEV,
        PLUGINS_KEEP_HIST,
        CLEAR_NEW_FLAG,
    )

    mylog("verbose", [f"[{pluginName}] Cleanup complete"])

    return 0


# ===============================================================================
# Cleanup / upkeep database
# ===============================================================================
def cleanup_database(
    dbPath,
    DAYS_TO_KEEP_EVENTS,
    HRS_TO_KEEP_NEWDEV,
    HRS_TO_KEEP_OFFDEV,
    PLUGINS_KEEP_HIST,
    CLEAR_NEW_FLAG,
):
    """
    Cleaning out old records from the tables that don't need to keep all data.
    """

    mylog("verbose", [f"[{pluginName}] Upkeep Database: {dbPath}"])

    conn = get_temp_db_connection()
    cursor = conn.cursor()

    try:
        cursor.execute("REINDEX;")
        mylog("verbose", [f"[{pluginName}] REINDEX completed"])
    except Exception as e:
        mylog("none", [f"[{pluginName}] REINDEX failed: {e}"])

    # -----------------------------------------------------
    # Cleanup Online History
    mylog("verbose", [f"[{pluginName}] Online_History: Delete all but keep latest 150 entries"])
    cursor.execute(
        """DELETE from Online_History where "index" not in (
                            SELECT "index" from Online_History
                            order by scanDate desc limit 150)"""
    )
    mylog("verbose", [f"[{pluginName}] Online_History deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # Cleanup Events
    mylog("verbose", f"[{pluginName}] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)")
    sql = f"""DELETE FROM Events WHERE eveDateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
    mylog("verbose", [f"[{pluginName}] SQL : {sql}"])
    cursor.execute(sql)
    mylog("verbose", [f"[{pluginName}] Events deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # Sessions (derived snapshot — trimmed to the same window as Events so the
    # two tables stay in sync without introducing a separate setting)
    mylog("verbose", f"[{pluginName}] Sessions: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (reuses DAYS_TO_KEEP_EVENTS)")
    sql = f"""DELETE FROM Sessions WHERE sesDateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
    mylog("verbose", [f"[{pluginName}] SQL : {sql}"])
    cursor.execute(sql)
    mylog("verbose", [f"[{pluginName}] Sessions deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # Plugins_History
    mylog("verbose", f"[{pluginName}] Plugins_History: Trim to {str(PLUGINS_KEEP_HIST)} per Plugin")
    delete_query = f"""DELETE FROM Plugins_History
                            WHERE "Index" NOT IN (
                                SELECT "Index"
                                FROM (
                                    SELECT "Index",
                                        ROW_NUMBER() OVER(PARTITION BY plugin ORDER BY dateTimeChanged DESC) AS row_num
                                    FROM Plugins_History
                                ) AS ranked_objects
                                WHERE row_num <= {str(PLUGINS_KEEP_HIST)}
                            );"""
    cursor.execute(delete_query)
    mylog("verbose", [f"[{pluginName}] Plugins_History deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # Notifications
    histCount = get_setting_value("DBCLNP_NOTIFI_HIST")
    mylog("verbose", f"[{pluginName}] Notifications: Trim to {histCount}")
    delete_query = f"""DELETE FROM Notifications
                            WHERE "Index" NOT IN (
                               SELECT "Index"
                                        FROM (
                                            SELECT "Index",
                                                ROW_NUMBER() OVER(PARTITION BY "index" ORDER BY dateTimeCreated DESC) AS row_num
                                            FROM Notifications
                                        ) AS ranked_objects
                                        WHERE row_num <= {histCount}
                            );"""
    cursor.execute(delete_query)
    mylog("verbose", [f"[{pluginName}] Notifications deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # AppEvents
    histCount = get_setting_value("WORKFLOWS_AppEvents_hist")
    mylog("verbose", [f"[{pluginName}] Trim AppEvents to less than {histCount}"])
    delete_query = f"""
            DELETE FROM AppEvents
            WHERE "Index" < (
                SELECT "Index"
                FROM AppEvents
                ORDER BY dateTimeCreated DESC
                LIMIT 1 OFFSET {histCount}
            );
        """
    cursor.execute(delete_query)
    mylog("verbose", [f"[{pluginName}] AppEvents deleted rows: {cursor.rowcount}"])

    conn.commit()

    # -----------------------------------------------------
    # Cleanup New Devices
    if HRS_TO_KEEP_NEWDEV != 0:
        mylog("verbose", f"[{pluginName}] Devices: Delete New Devices older than {str(HRS_TO_KEEP_NEWDEV)} hours")
        query = f"""DELETE FROM Devices WHERE devIsNew = 1 AND devFirstConnection < date('now', '-{str(HRS_TO_KEEP_NEWDEV)} hour')"""
        mylog("verbose", [f"[{pluginName}] Query: {query}"])
        cursor.execute(query)
        mylog("verbose", [f"[{pluginName}] Devices (new) deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # Cleanup Offline Devices
    if HRS_TO_KEEP_OFFDEV != 0:
        mylog("verbose", f"[{pluginName}] Devices: Delete Offline Devices older than {str(HRS_TO_KEEP_OFFDEV)} hours")
        query = f"""DELETE FROM Devices WHERE devPresentLastScan = 0 AND devLastConnection < date('now', '-{str(HRS_TO_KEEP_OFFDEV)} hour')"""
        mylog("verbose", [f"[{pluginName}] Query: {query}"])
        cursor.execute(query)
        mylog("verbose", [f"[{pluginName}] Devices (offline) deleted rows: {cursor.rowcount}"])

    # -----------------------------------------------------
    # Clear New Flag
    if CLEAR_NEW_FLAG != 0:
        mylog("verbose", f'[{pluginName}] Devices: Clear "New Device" flag older than {str(CLEAR_NEW_FLAG)} hours')
        query = f"""UPDATE Devices SET devIsNew = 0 WHERE devIsNew = 1 AND date(devFirstConnection, '+{str(CLEAR_NEW_FLAG)} hour') < date('now')"""
        mylog("verbose", [f"[{pluginName}] Query: {query}"])
        cursor.execute(query)
        mylog("verbose", [f"[{pluginName}] Devices updated rows (clear new): {cursor.rowcount}"])

    # -----------------------------------------------------
    # De-dupe Plugins_Objects
    mylog("verbose", [f"[{pluginName}] Plugins_Objects: Delete all duplicates"])
    cursor.execute(
        """
        DELETE FROM Plugins_Objects
        WHERE rowid > (
            SELECT MIN(rowid) FROM Plugins_Objects p2
            WHERE Plugins_Objects.plugin = p2.plugin
            AND Plugins_Objects.objectPrimaryId = p2.objectPrimaryId
            AND Plugins_Objects.objectSecondaryId = p2.objectSecondaryId
            AND Plugins_Objects.userData = p2.userData
        )
    """
    )
    mylog("verbose", [f"[{pluginName}] Plugins_Objects deleted rows: {cursor.rowcount}"])

    conn.commit()

    # WAL + Vacuum
    cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
    cursor.execute("PRAGMA wal_checkpoint(FULL);")
    mylog("verbose", [f"[{pluginName}] WAL checkpoint executed to truncate file."])

    # Refresh query-planner statistics after bulk deletes so SQLite chooses
    # the right indexes on the next scan cycle (fixes CPU scaling with DB size)
    cursor.execute("ANALYZE;")
    mylog("verbose", [f"[{pluginName}] ANALYZE completed"])

    mylog("verbose", [f"[{pluginName}] Shrink Database"])
    cursor.execute("VACUUM;")

    # Lightweight incremental ANALYZE at connection close — near-zero cost,
    # only re-analyzes tables whose statistics have drifted >25%
    cursor.execute("PRAGMA optimize;")
    mylog("verbose", [f"[{pluginName}] PRAGMA optimize completed"])

    conn.close()


if __name__ == "__main__":
    main()
