#!/usr/bin/env python3
import subprocess
import re
import time
from datetime import datetime, timedelta
import psutil
import os



#LOG_FILE = "/var/log/apache2/access.log"
LOG_FILE = "/var/log/nginx/access.log"

_log_fp = None
_log_inode = None
CLEANUP_INTERVAL = 3 * 24 * 60 * 60  # 3 days in seconds
last_cleanup_time = time.time()

# (uid, channel) -> last_seen_datetime
_active_clients = {}

def _read_new_log_lines():
    global _log_fp, _log_inode

    try:
        st = os.stat(LOG_FILE)
    except FileNotFoundError:
        return []

    # First run OR log rotated
    if _log_fp is None or _log_inode != st.st_ino:
        if _log_fp:
            _log_fp.close()

        _log_fp = open(LOG_FILE, "r")
        _log_fp.seek(0, os.SEEK_END)   # start from end only
        _log_inode = st.st_ino
        return []

    return _log_fp.readlines()

# ---------- Load Channel Map ----------
"""
channel_map = {}
try:
    with open("all_channel_list.txt", "r") as ch_file:
        next(ch_file)  # skip header line
        for line in ch_file:
            parts = line.strip().rsplit(None, 1)
            if len(parts) == 2:
                channel_name, stream_id = parts
                channel_map[stream_id.strip()] = channel_name.strip()
except FileNotFoundError:
    print("[!] all_channel_list.txt not found. Channel names will not be mapped.")

"""
# ---------- File Cleanup ----------
def clean_old_entries():
    """Clean old peak entries from log files."""
    now = datetime.now()

    def clean_minute_file(file_path):
        cutoff = (now - timedelta(days=3)).replace(hour=0, minute=0, second=0, microsecond=0)
        cleaned = []
        try:
            with open(file_path, "r") as f:
                for line in f:
                    match = re.match(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2})", line)
                    if match:
                        timestamp = datetime.strptime(match.group(1), "%Y-%m-%d %H:%M")
                        if timestamp >= cutoff:
                            cleaned.append(line)
            with open(file_path, "w") as f:
                f.writelines(cleaned)
            print(f"[✓] Cleaned old entries from {file_path}")
        except FileNotFoundError:
            pass

    def clean_day_file(file_path):
        cutoff = (now - timedelta(days=30)).replace(hour=0, minute=0, second=0, microsecond=0)
        cleaned = []
        try:
            with open(file_path, "r") as f:
                for line in f:
                    match = re.match(r"^(\d{4}-\d{2}-\d{2})", line)
                    if match:
                        timestamp = datetime.strptime(match.group(1), "%Y-%m-%d")
                        if timestamp >= cutoff:
                            cleaned.append(line)
            with open(file_path, "w") as f:
                f.writelines(cleaned)
            print(f"[✓] Cleaned old entries from {file_path}")
        except FileNotFoundError:
            pass

    def clean_hourly_file(file_path):
        cutoff = (now - timedelta(days=3)).replace(hour=0, minute=0, second=0, microsecond=0)
        cleaned = []
        try:
            with open(file_path, "r") as f:
                lines = f.readlines()
            i = 0
            while i < len(lines):
                line = lines[i]
                try:
                    parts = line.strip().split()
                    if len(parts) >= 2:
                        hour_key_str = f"{parts[0]} {parts[1]}"
                        timestamp = datetime.strptime(hour_key_str, "%Y-%m-%d %H:%M")
                    else:
                        i += 1
                        continue
                except Exception:
                    i += 1
                    continue

                if timestamp >= cutoff:
                    cleaned.append(line)
                    i += 1
                    while i < len(lines) and lines[i].startswith("    "):
                        cleaned.append(lines[i])
                        i += 1
                else:
                    i += 1
                    while i < len(lines) and lines[i].startswith("    "):
                        i += 1

            with open(file_path, "w") as f:
                f.writelines(cleaned)
            print(f"[✓] Cleaned old entries from {file_path}")
        except FileNotFoundError:
            pass


    def clean_quarter_file(file_path):
        """Clean old 15-min peak entries (older than 10 days)."""
        now = datetime.now()
        cutoff = (now - timedelta(days=3)).replace(hour=0, minute=0, second=0, microsecond=0)
        cleaned = []

        try:
            with open(file_path, "r") as f:
                lines = f.readlines()

            i = 0
            while i < len(lines):
                line = lines[i]
                try:
                    parts = line.strip().split()
                    if len(parts) >= 2:
                        # first two parts = date + hour
                        timestamp_str = f"{parts[0]} {parts[1]}"
                        timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M")
                    else:
                        i += 1
                        continue
                except Exception:
                    i += 1
                    continue

                if timestamp >= cutoff:
                    cleaned.append(line)
                    i += 1
                    # keep the indented viewer lines
                    while i < len(lines) and lines[i].startswith("    "):
                        cleaned.append(lines[i])
                        i += 1
                else:
                    i += 1
                    while i < len(lines) and lines[i].startswith("    "):
                        i += 1

            with open(file_path, "w") as f:
                f.writelines(cleaned)
            print(f"[✓] Cleaned old entries from {file_path}")
        except FileNotFoundError:
            pass

    clean_minute_file("peak_count_per_min.txt")
    clean_day_file("peak_client_per_day.txt")
    clean_hourly_file("hourly_peak_clients.txt")
    clean_quarter_file("quarterly_peak_clients.txt")

# ---------- Peak Updates ----------
def update_minute_peak_clients(current_clients):
    now = datetime.now()
    minute_key = now.strftime("%Y-%m-%d %H:%M")
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    filename = "peak_count_per_min.txt"
    updated_lines = []
    new_peak = False
    found = False

    try:
        with open(filename, "r") as f:
            lines = f.readlines()
    except FileNotFoundError:
        lines = []

    for line in lines:
        if line.startswith(minute_key):
            found = True
            try:
                old_clients = int(line.strip().split(":")[-1].strip())
            except ValueError:
                old_clients = 0
            if current_clients > old_clients:
                updated_lines.append(f"{minute_key} {timestamp} : {current_clients}\n")
                new_peak = True
            else:
                updated_lines.append(line)
        else:
            updated_lines.append(line)

    if not found:
        updated_lines.append(f"{minute_key} {timestamp} : {current_clients}\n")
        new_peak = True

    if new_peak:
        with open(filename, "w") as f:
            f.writelines(updated_lines)
        print(f"[✓] Per-minute peak updated: {current_clients}")
    else:
        print(f"[i] Clients this minute ({current_clients}) did not exceed previous peak.")


def update_hourly_peak_clients(current_clients, client_info_list):
    now = datetime.now()
    hour_key = now.strftime("%Y-%m-%d %H:00")
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    filename = "hourly_peak_clients.txt"
    new_lines = []
    new_peak = False
    found_hour = False

    try:
        with open(filename, "r") as f:
            lines = f.readlines()
    except FileNotFoundError:
        lines = []

    i = 0
    while i < len(lines):
        line = lines[i]
        if line.startswith(hour_key):
            found_hour = True
            try:
                old_clients = int(line.strip().split(":")[-1].strip())
            except ValueError:
                old_clients = 0
            while i < len(lines) and (lines[i].startswith(hour_key) or lines[i].startswith("    ")):
                i += 1
            if current_clients > old_clients:
                new_lines.append(f"{hour_key} {timestamp} : {current_clients}\n")
                for ip, stream_id in client_info_list:
                    new_lines.append(f"    {ip} -> stream -> {stream_id}\n")
                    #ch_name = channel_map.get(stream_id, stream_id)
                    #new_lines.append(f"    {ip} -> {stream_id} -> {ch_name}\n")
                new_peak = True
            else:
                new_lines.append(line)
                i += 1
                while i < len(lines) and lines[i].startswith("    "):
                    new_lines.append(lines[i])
                    i += 1   
        else:
            new_lines.append(line)
            i += 1

    if not found_hour:
        new_lines.append(f"{hour_key} {timestamp} : {current_clients}\n")
        for ip, stream_id in client_info_list:
            #ch_name = channel_map.get(stream_id, stream_id)
            new_lines.append(f"    {ip} -> stream -> {stream_id}\n")
        new_peak = True

    if new_peak:
        with open(filename, "w") as f:
            f.writelines(new_lines)
        print(f"[✓] Hourly peak updated: {current_clients}")
    else:
        print(f"[i] Current clients ({current_clients}) did not exceed this hour's previous peak.")


def update_daily_peak_clients(current_clients):
    today = datetime.now().strftime("%Y-%m-%d")
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    filename = "peak_client_per_day.txt"
    updated_lines = []
    new_peak = False
    found_today = False

    try:
        with open(filename, "r") as f:
            lines = f.readlines()
    except FileNotFoundError:
        lines = []

    for line in lines:
        if line.startswith(today):
            found_today = True
            try:
                old_clients = int(line.strip().split(":")[-1].strip())
            except ValueError:
                old_clients = 0
            if current_clients > old_clients:
                updated_lines.append(f"{timestamp} : {current_clients}\n")
                new_peak = True
            else:
                updated_lines.append(line)
        else:
            updated_lines.append(line)

    if not found_today:
        updated_lines.append(f"{timestamp} : {current_clients}\n")
        new_peak = True

    if new_peak:
        with open(filename, "w") as f:
            f.writelines(updated_lines)
        print(f"[✓] Daily peak updated: {current_clients}")
    else:
        print(f"[i] Current clients ({current_clients}) did not exceed today's previous peak.")


# ---------- HLS Log Parsing ----------
def get_active_hls_clients(window_seconds=180):
    """
    Returns:
        total_clients (int)
        clients_info  [(uid, channel), ...]
    """

    now = datetime.now()

    # Read ONLY new lines since last call
    for line in _read_new_log_lines():

        if "index.m3u8" not in line:
            continue

        # ---- Parse timestamp ----
        try:
            ts = line.split("[", 1)[1].split("]", 1)[0]
            log_time = datetime.strptime(
                ts.split(" ")[0],
                "%d/%b/%Y:%H:%M:%S"
            )
        except Exception:
            continue

        # ---- Parse channel ----
        # ch_match = re.search(r"/riptv/live/([^/]+)/index\.m3u8", line)  ##apache2
        ch_match = re.search(r"/([^/]+)/index\.m3u8", line)  ##nginx
        if not ch_match:
            continue
        channel = ch_match.group(1)

        # ---- Parse UID ----
        uid_match = re.search(r"[?&]uid=([^&\s\"]+)", line)
        if not uid_match:
            continue
        uid = uid_match.group(1)

        # ---- Update last seen (UID IS THE KEY) ----
        _active_clients[uid] = (channel, log_time)

    # ---- Expire old viewers ----
    expired = [
        uid for uid, (_, last_seen) in _active_clients.items()
        if (now - last_seen).total_seconds() > window_seconds
    ]

    for uid in expired:
        del _active_clients[uid]

    clients_info = [(uid, ch) for uid, (ch, _) in _active_clients.items()]
    return len(clients_info), clients_info


def get_link_speed_mbps(iface):
    """
    Returns link speed in Mbps using ethtool.
    Returns None if speed is unavailable.
    """
    try:
        output = subprocess.check_output(
            ["ethtool", iface],
            stderr=subprocess.DEVNULL,
            text=True
        )
        for line in output.splitlines():
            if "Speed:" in line:
                speed = line.split("Speed:")[1].strip()
                if speed.endswith("Mb/s"):
                    return int(speed.replace("Mb/s", ""))
    except Exception:
        pass

    return None

# ---------- Bandwidth ----------
def bytes_to_mbps(bytes_num, interval_seconds):
    return bytes_num * 8 / 1_000_000 / interval_seconds

def calculate_bandwidth(interval=1):
    net_start = psutil.net_io_counters(pernic=True)
    time.sleep(interval)
    net_end = psutil.net_io_counters(pernic=True)

    result = {}
    for iface in net_start:
        sent = net_end[iface].bytes_sent - net_start[iface].bytes_sent
        recv = net_end[iface].bytes_recv - net_start[iface].bytes_recv

        result[iface] = {
            "sent_mbps": bytes_to_mbps(sent, interval),
            "recv_mbps": bytes_to_mbps(recv, interval),
            "link_speed_mbps": get_link_speed_mbps(iface),
        }

    return result


def get_hls_port():
    try:
        output = subprocess.check_output(
            "lsof -iTCP -sTCP:LISTEN -P -n | grep nginx | awk '{print $9}'",
            shell=True
        ).decode().strip()

        # output = subprocess.check_output(
        #     "lsof -iTCP -sTCP:LISTEN -P -n | grep apache2 | awk '{print $9}'",
        #     shell=True
        # ).decode().strip()

        if output:
            first_line = output.split("\n")[0]
            port = first_line.split(":")[-1]
            return int(port)
    except Exception:
        return 80  # fallback
        

def update_quarter_peak_clients(current_clients, client_info_list):
    now = datetime.now()
    slot = (now.minute // 15) * 15
    quarter_key = f"{now.strftime('%Y-%m-%d %H')}:{slot:02d}"
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    filename = "quarterly_peak_clients.txt"

    new_lines = []
    found_quarter = False
    new_peak = False

    try:
        with open(filename, "r") as f:
            lines = f.readlines()
    except FileNotFoundError:
        lines = []

    i = 0
    while i < len(lines):
        line = lines[i]

        if line.startswith(quarter_key):
            found_quarter = True
            try:
                old_clients = int(line.strip().split(":")[-1].strip())
            except ValueError:
                old_clients = 0

            # skip old block
            while i < len(lines) and (
                lines[i].startswith(quarter_key) or lines[i].startswith("    ")
            ):
                i += 1

            if current_clients > old_clients:
                new_lines.append(f"{quarter_key} {timestamp} : {current_clients}\n")
                for uid, channel in client_info_list:
                    new_lines.append(f"    {uid} -> stream -> {channel}\n")
                new_peak = True
            else:
                new_lines.append(line)
                i += 1
                while i < len(lines) and lines[i].startswith("    "):
                    new_lines.append(lines[i])
                    i += 1
        else:
            new_lines.append(line)
            i += 1

    if not found_quarter:
        new_lines.append(f"{quarter_key} {timestamp} : {current_clients}\n")
        for uid, channel in client_info_list:
            new_lines.append(f"    {uid} -> stream -> {channel}\n")
        new_peak = True

    if new_peak:
        with open(filename, "w") as f:
            f.writelines(new_lines)
        print(f"[✓] Quarter-hour peak updated: {current_clients}")
    else:
        print(f"[i] Current clients ({current_clients}) did not exceed this 15-min slot’s previous peak.")

                
# ---------- Main Loop ----------
while True:
    total_clients, clients_info = get_active_hls_clients()

    update_minute_peak_clients(total_clients)
    update_hourly_peak_clients(total_clients, clients_info)
    update_daily_peak_clients(total_clients)
    update_quarter_peak_clients(total_clients, clients_info)

    now_ts = time.time()

    if now_ts - last_cleanup_time > CLEANUP_INTERVAL:
        clean_old_entries()
        last_cleanup_time = now_ts
        
    # clean_old_entries()

    # === Now write the snapshot file ===
    # output_file = "Parsed_RStreamer_Status_hls.txt"
    # with open(output_file, "w") as out:
    #     out.write("======= Parsed from HLS Monitor =======\n\n")
    #     out.write(f"Timestamp : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
    #     out.write(f"Total Active Clients : {total_clients}\n\n")
    #     out.write("=== Active Clients ===\n")
    #     out.write("Source IP -> Stream ID -> Channel Name\n")
    #     hls_port = get_hls_port()
    #     for uid, channel_name in clients_info:
    #         out.write(f"{uid} -> stream -> {channel_name}\n")

    output_file = "Parsed_RStreamer_Status_hls.txt"
    tmp_file = output_file + ".tmp"

    with open(tmp_file, "w") as out:
        out.write("======= Parsed from HLS Monitor =======\n\n")
        out.write(f"Timestamp : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        out.write(f"Total Active Clients : {total_clients}\n\n")
        out.write("=== Active Clients ===\n")
        out.write("Source IP -> Stream ID -> Channel Name\n")

        for uid, channel_name in clients_info:
            out.write(f"{uid} -> stream -> {channel_name}\n")

    # Atomic swap (readers never see empty file)
    os.replace(tmp_file, output_file)

    bandwidth_info = calculate_bandwidth(interval=1)
    with open("bandwidth.txt", "w") as h:
            for iface, bw in bandwidth_info.items():
                link = (
                    f"{bw['link_speed_mbps']} Mbps"
                    if bw["link_speed_mbps"] is not None
                    else "N/A"
                )
                h.write(
                    f"{iface}  "
                    f"{bw['sent_mbps']:.3f} Mbps  "
                    f"{bw['recv_mbps']:.3f} Mbps  "
                    f"{link}\n"
                )
                
    time.sleep(1)


###with parsed hls file
