#!/opt/cloudlinux/venv/bin/python3 -bb
# coding:utf-8
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

"""
Symlinked to /opt/apache2nginx/max-webserver-stats in .spec
and used by cloudlinux-summary
"""

import subprocess
import datetime
import os
import sys
import re
import json
import logging

from dataclasses import dataclass, field
from apache2nginx.sentry import sentry_init, sentry_send
from apache2nginx.maxws_setup import get_setup_status
from apache2nginx.envdata import EnvData

RELOAD_START = re.compile(r"(\w{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}) .*systemd.*Reloading nginx")
RELOAD_END = re.compile(r"(\w{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}) .*systemd.*Reloaded nginx")

SCAN_PATTERN = re.compile(r'time=(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z).*msg="end full scan".*elapsed_time=([\d.]+)s')

GENERAL_LOG = "/var/log/messages"
CLMONITOR_LOG = "/var/log/clmonitor.log"

class HtaccessGenerationStats:
    STATS = {}

    @staticmethod
    def save(stat: dict, env: EnvData):
        logger = init_stat_logger(env.MAXWEBSERVER_STAT_LOG)
        try:
            logger.info("Raw statistics: %s", str(stat) )
            calculated_stats = {
                k: stat[k].get_metric_per_domain()
                for k in stat if stat[k].get_metric_per_domain() >= 0
            }
            logger.info(
                "[for cloudlinux-summary]: htaccess and config modification stat: %s",
                json.dumps(calculated_stats)
            )
        except Exception:
            logger.exception("Failed to save htaccess modification time statistics")


@dataclass
class HtaccessStatsPerDomain:
    config_before_mtime: float = 0.0
    config_after_mtime: float = 0.0
    htaccess_mtime: list[float] = field(default_factory=list)

    def get_metric_per_domain(self) -> float:
        if not self.config_before_mtime or not self.config_after_mtime:
            return -1

        modified_htaccess_since_last_config_build = [
            i for i in self.htaccess_mtime
            if not self.config_before_mtime or self.config_before_mtime < i
        ]

        if not modified_htaccess_since_last_config_build:
            return -1

        return max([self.config_after_mtime - i for i in modified_htaccess_since_last_config_build])




@dataclass
class NginxReloadStat:
    """
    Stores some statistics regarding nginx reload
    """
    max_elapsed: float
    avg_elapsed: float
    count: int


@dataclass
class ClMonitorStat:
    """
    Stores some statistics regarding clmonitor
    """
    max_scan_elapsed: float
    avg_scan_elapsed: float
    count: float


@dataclass
class HtaccessStat:
    """
    Stores some statistics regarding htaccesses
    and their relevant apache2nginx configs regeneration
    values are in seconds
    """
    max_time_gap: float
    avg_time_gap: float


def init_stat_logger(maxwebserver_stat_log):
    logger = logging.getLogger("max_webserver_stat")
    logger.setLevel(logging.INFO)
    logger.propagate = False
    formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s')
    file_handler = logging.FileHandler(maxwebserver_stat_log)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    return logger


def get_lines_from_log(command):
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    return result.stdout.splitlines()


def get_htaccess_modification_lines(maxwebserver_stat_log):
    """
    example:
    2025-02-27 16:12:08,356 | INFO | [for cloudlinux-summary]: htaccess and config modification stat:
    {"tuser.com": 10.217, "mytestuser-test-3.com": 8.463920831680298}
    """
    today_date = datetime.datetime.now().strftime("%Y-%m-%d")
    cmd = f"grep \"{today_date}\" {maxwebserver_stat_log} | grep \"htaccess and config modification stat\""
    return get_lines_from_log(cmd)


def get_nginx_reload_lines():
    """
    example:
    Feb 26 12:17:41 vm-id-3978077 systemd[1]: Reloading nginx - high performance web server.
    Feb 26 12:17:41 vm-id-3978077 systemd[1]: Reloaded nginx - high performance web server.
    Feb 26 12:18:09 vm-id-3978077 systemd[1]: Reloading nginx - high performance web server.
    Feb 26 12:18:09 vm-id-3978077 systemd[1]: Reloaded nginx - high performance web server.
    """
    cmd = f"""grep -E "$(date '+%b[ ]?%e')" {GENERAL_LOG} | grep -E "Reloading nginx|Reloaded nginx" """
    return get_lines_from_log(cmd)


def get_clmonitor_lines():
    """
    example:
    time=2025-02-26T13:33:56.794Z level=INFO msg="end full scan" elapsed_time=680.225929ms
    """
    today_date = datetime.datetime.now().strftime("%Y-%m-%d")
    cmd = f'grep "{today_date}" {CLMONITOR_LOG} | grep "end full scan"'
    return get_lines_from_log(cmd)


def parse_timestamp(log_date_str):
    """Convert log timestamp to a datetime object (without year)."""
    now = datetime.datetime.now()
    log_datetime = datetime.datetime.strptime(log_date_str, "%b %d %H:%M:%S")
    log_datetime = log_datetime.replace(year=now.year)
    return log_datetime


def calculate_scan_times():
    scan_times = []

    log_lines = get_clmonitor_lines()
    for line in log_lines:
        match = SCAN_PATTERN.search(line)
        if match:
            elapsed_time = round(float(match.group(2)), 3)
            scan_times.append(elapsed_time)
    if scan_times:
        max_time = max(scan_times)
        avg_time = sum(scan_times) / len(scan_times)
        count = len(scan_times)
        return ClMonitorStat(
            max_scan_elapsed=max_time,
            avg_scan_elapsed=avg_time,
            count=count
        )
    return ClMonitorStat(max_scan_elapsed=0, avg_scan_elapsed=0, count=0)


def calculate_htaccess_mtime_gaps(maxwebserver_stat_log):
    log_lines = get_htaccess_modification_lines(maxwebserver_stat_log)
    values = []
    for line in log_lines:
        match = re.search(r':\s*({.*})', line)
        if not match:
            continue
        try:
            recorded_stat = json.loads(match.group(1))
        except json.JSONDecodeError:
            continue
        values.extend(recorded_stat.values())
    if values:
        return HtaccessStat(
            max_time_gap=round(max(values), 2),
            avg_time_gap=round(sum(values) / len(values), 2)
        )
    else:
        return HtaccessStat(max_time_gap=0, avg_time_gap=0)


def calculate_nginx_reload():
    reload_times = []
    start_time = None

    log_lines = get_nginx_reload_lines()
    for line in log_lines:
        match_start = RELOAD_START.search(line)
        if match_start:
            start_time = parse_timestamp(match_start.group(1))
            continue

        match_end = RELOAD_END.search(line)
        if match_end and start_time:
            end_time = parse_timestamp(match_end.group(1))
            elapsed = (end_time - start_time).total_seconds()
            reload_times.append(elapsed)
            start_time = None  # Reset for next reload

    if reload_times:
        max_time = max(reload_times)
        avg_time = sum(reload_times) / len(reload_times)
        return NginxReloadStat(
            max_elapsed=max_time,
            avg_elapsed=avg_time,
            count=len(reload_times)
        )
    else:
        return NginxReloadStat(max_elapsed=0, avg_elapsed=0, count=0)


def get_stat(maxwebserver_stat_log, main_dir):
    statistics = {}

    try:
        nginx_reload_stat = calculate_nginx_reload()
        statistics["nginx_reload_elapsed_max"] = nginx_reload_stat.max_elapsed
        statistics["nginx_reload_elapsed_avg"] = nginx_reload_stat.avg_elapsed
        statistics["nginx_reload_count"] = nginx_reload_stat.count
    except Exception as e:
        sentry_send(
            message="Failed to collect nginx reload metrics",
            level="error",
            extra={
                "reason": str(e),
            },
            fingerprint=["metrics-collection-failed"]
        )

    try:
        clmonitor_stat = calculate_scan_times()
        statistics["clmonitor_full_scan_elapsed_max"] = clmonitor_stat.max_scan_elapsed
        statistics["clmonitor_full_scan_elapsed_avg"] = clmonitor_stat.max_scan_elapsed
        statistics["clmonitor_full_scan_count"] = clmonitor_stat.count
    except Exception as e:
        sentry_send(
            message="Failed to collect clmonitor metrics",
            level="error",
            extra={
                "reason": str(e),
            },
            fingerprint=["metrics-collection-failed"]
        )

    try:
        htaccess_mod_gaps = calculate_htaccess_mtime_gaps(maxwebserver_stat_log)
        statistics["htaccess_modification_time_gap_max"] = htaccess_mod_gaps.max_time_gap
        statistics["htaccess_modification_time_gap_avg"] = htaccess_mod_gaps.avg_time_gap
    except Exception as e:
        sentry_send(
            message="Failed to collect htaccess modification metrics",
            level="error",
            extra={
                "reason": str(e),
            },
            fingerprint=["metrics-collection-failed"]
        )

    try:
        statistics["setup_state"] = get_setup_status(main_dir)
    except Exception as e:
        sentry_send(
            message="Failed to collect setup_state metric",
            level="error",
            extra={
                "reason": str(e),
            },
            fingerprint=["metrics-collection-failed"]
        )

    return json.dumps(statistics)


def load_environment_variable():
    """
    Reads the /etc/environment file and updates the os.environ variable for SENTRY_ENVIRONMENT.
    """
    import os
    try:
        with open("/etc/environment", "r") as f:
            for line in f:
                if line.startswith("SENTRY_ENVIRONMENT="):
                    # Remove the key, equal sign and any quotes
                    key, value = line.strip().split("=", 1)
                    os.environ[key] = value.strip('"')
                    break
    except FileNotFoundError:
        pass


if __name__ == "__main__":
    env = EnvData(dict(os.environ))
    if os.geteuid() != 0:
        print('Root only!')
        sys.exit(1)
    load_environment_variable()
    sentry_init()
    print(get_stat(env.MAXWEBSERVER_STAT_LOG, env.MAIN_DIR))
    open(env.MAXWEBSERVER_STAT_LOG, 'w').close()
    sys.exit(0)
