#!/usr/bin/env python3
# NEMS Central Command - Tactical TTY NOC Display
import sqlite3
import time
import os
import sys
import socket
import shutil
import re
from datetime import datetime, timedelta

DB_PATH = "/usr/local/share/nems/ncc/ncc_history.db"

# ANSI Terminal Styling
C_RESET   = "\033[0m"
C_BOLD    = "\033[1m"
C_CYAN    = "\033[38;5;51m"
C_GREEN   = "\033[38;5;46m"
C_AMBER   = "\033[38;5;214m"
C_RED     = "\033[38;5;196m"
C_MUTED   = "\033[38;5;240m"
C_WHITE   = "\033[38;5;255m"

# Regex to strip ANSI formatting for true string length calculation
ANSI_ESCAPE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')

def get_plain_len(text):
    """Returns the true visual length of a string by ignoring ANSI color codes."""
    return len(ANSI_ESCAPE.sub('', text))

def box_line(visible_content, cols):
    """Formats content to fit an exact screen width without line-wrapping."""
    inner_width = max(10, cols - 4)
    plain_length = get_plain_len(visible_content)
    padding = max(0, inner_width - plain_length)
    return f"{C_CYAN}│{C_RESET} {visible_content}{' ' * padding} {C_CYAN}│{C_RESET}"

def border_line(char_start, char_mid, char_end, cols):
    """Generates precise horizontal border frames."""
    return f"{C_CYAN}{char_start}{char_mid * (cols - 2)}{char_end}{C_RESET}"

def get_nems_config():
    """Fetch the NEMS alias and tv_24 clock mode from nems.conf."""
    config = {'alias': socket.gethostname().upper(), 'tv_24': '1'}
    for conf_path in ["/usr/local/share/nems/nems.conf", "/etc/nems/nems.conf"]:
        if os.path.exists(conf_path):
            try:
                with open(conf_path, "r") as f:
                    for line in f:
                        line = line.strip()
                        if line.startswith("alias="):
                            val = line.split("=", 1)[1].strip('"\' ')
                            if val:
                                config['alias'] = val.upper()
                        elif line.startswith("tv_24="):
                            val = line.split("=", 1)[1].strip('"\' ')
                            if val:
                                config['tv_24'] = str(val)
            except Exception:
                pass
    return config

def calculate_stateful_24h_blocks(cursor, now_ts):
    """Calculates stateful 48-block ribbon where incidents persist until RECOVERY."""
    window_start_ts = now_ts - 86400
    blocks = [0] * 48

    try:
        # Fetch all historical events up to current time ordered chronologically
        cursor.execute("""
            SELECT CAST(timestamp AS INTEGER) as ts, event_type, host_name, service_description 
            FROM event_ledger 
            WHERE CAST(timestamp AS INTEGER) <= ? 
            ORDER BY CAST(timestamp AS INTEGER) ASC
        """, (now_ts,))
        all_events = cursor.fetchall()
    except Exception:
        return blocks

    active_incidents = set()
    acked_incidents = set()
    
    ev_idx = 0
    n_events = len(all_events)

    # 1. Synchronize system state up to window_start_ts (-24 hours ago)
    while ev_idx < n_events and all_events[ev_idx][0] < window_start_ts:
        ts, etype, host, svc = all_events[ev_idx]
        key = (host or "NEMS", svc or "HOST")
        if etype == 'INCIDENT':
            active_incidents.add(key)
            acked_incidents.discard(key)
        elif etype == 'RECOVERY':
            active_incidents.discard(key)
            acked_incidents.discard(key)
        elif etype == 'ACK':
            if key in active_incidents:
                acked_incidents.add(key)
        ev_idx += 1

    # 2. Step through each 30-minute block across the 24-hour window
    for i in range(48):
        b_start = window_start_ts + (i * 1800)
        b_end = b_start + 1800
        
        block_had_incident = bool(active_incidents)

        # Process events that occurred during this specific 30-minute block
        while ev_idx < n_events and all_events[ev_idx][0] < b_end:
            ts, etype, host, svc = all_events[ev_idx]
            key = (host or "NEMS", svc or "HOST")
            
            if etype == 'INCIDENT':
                active_incidents.add(key)
                acked_incidents.discard(key)
                block_had_incident = True
            elif etype == 'RECOVERY':
                active_incidents.discard(key)
                acked_incidents.discard(key)
            elif etype == 'ACK':
                if key in active_incidents:
                    acked_incidents.add(key)
            ev_idx += 1

        # 3. Assign block color based on state within this block
        if block_had_incident or active_incidents:
            if active_incidents and active_incidents == acked_incidents:
                blocks[i] = 2  # Cyan / Acknowledged
            else:
                blocks[i] = 1  # Red / Critical
        else:
            blocks[i] = 0  # Green / Nominal

    return blocks

def get_telemetry(max_ledger_count):
    now_dt = datetime.now()
    now_ts = int(now_dt.timestamp())

    thirty_dt = now_dt - timedelta(days=30)
    thirty_str = thirty_dt.strftime("%Y-%m-%d")

    today_dt = now_dt.replace(hour=0, minute=0, second=0, microsecond=0)
    today_ts = int(today_dt.timestamp())

    sla = 100
    incidents = 0
    acks = 0
    blocks = [0] * 48
    ledger = []

    if not os.path.exists(DB_PATH):
        return sla, incidents, acks, blocks, ledger

    conn = None
    try:
        conn = sqlite3.connect(DB_PATH, timeout=2.0)
        cursor = conn.cursor()

        # 1. Fetch Event Ledger for Display
        try:
            cursor.execute("SELECT timestamp, event_type, host_name, service_description, plugin_output FROM event_ledger ORDER BY CAST(timestamp AS INTEGER) DESC LIMIT ?", (max_ledger_count,))
            ledger = cursor.fetchall()
        except Exception:
            pass

        # 2. Rolling 30-Day SLA & Metrics
        try:
            cursor.execute("SELECT AVG(sla_average), SUM(total_incidents), SUM(total_acks) FROM daily_rollups WHERE date_stamp >= ?", (thirty_str,))
            row_roll = cursor.fetchone()
            
            sla_avg = row_roll[0]
            roll_inc = int(row_roll[1]) if row_roll and row_roll[1] is not None else 0
            roll_ack = int(row_roll[2]) if row_roll and row_roll[2] is not None else 0

            cursor.execute("SELECT COUNT(*) FROM event_ledger WHERE event_type = 'INCIDENT' AND CAST(timestamp AS INTEGER) >= ?", (today_ts,))
            today_inc = cursor.fetchone()[0]

            cursor.execute("SELECT COUNT(*) FROM event_ledger WHERE event_type = 'ACK' AND CAST(timestamp AS INTEGER) >= ?", (today_ts,))
            today_ack = cursor.fetchone()[0]

            incidents = roll_inc + today_inc
            acks = roll_ack + today_ack

            if sla_avg is not None:
                sla = round((float(sla_avg) + (100 if today_inc == 0 else max(50, 100 - today_inc * 10))) / 2)
            else:
                sla = 100 if incidents == 0 else max(50, 100 - (incidents * 5))
        except Exception:
            pass

        # 3. Calculate Stateful 24-Hour Ribbon
        try:
            blocks = calculate_stateful_24h_blocks(cursor, now_ts)
        except Exception:
            pass

    except Exception:
        pass
    finally:
        if conn:
            try:
                conn.close()
            except Exception:
                pass

    return sla, incidents, acks, blocks, ledger

def format_ledger_line(date_ts_raw, event_type, host_name, service_desc, plugin_output, inner_width):
    try:
        date_ts = int(float(date_ts_raw))
        ts_str = datetime.fromtimestamp(date_ts).strftime("%b %d %H:%M:%S")
    except Exception:
        ts_str = "Jan 01 00:00:00"

    if event_type == "INCIDENT":
        badge_color = f"{C_RED}[INCIDENT]{C_RESET}"
        badge_plain = "[INCIDENT]"
    elif event_type == "RECOVERY":
        badge_color = f"{C_GREEN}[RECOVERY]{C_RESET}"
        badge_plain = "[RECOVERY]"
    else:
        badge_color = f"{C_CYAN}[  ACK   ]{C_RESET}"
        badge_plain = "[  ACK   ]"

    host = (host_name or "NEMS")
    svc = (service_desc or "HOST")
    raw_output = (plugin_output or "").replace("\n", " ").replace("\r", "").strip()

    prefix_plain = f"  {ts_str} {badge_plain} {host} -> {svc}"
    prefix_color = f"  {C_MUTED}{ts_str}{C_RESET} {badge_color} {C_BOLD}{host}{C_RESET} -> {svc}"

    if len(prefix_plain) >= inner_width:
        avail_svc = inner_width - len(f"  {ts_str} {badge_plain} {host} -> ")
        if avail_svc > 3:
            svc_trunc = svc[:avail_svc - 3] + "..."
        else:
            svc_trunc = svc[:max(1, avail_svc)]
        return f"  {C_MUTED}{ts_str}{C_RESET} {badge_color} {C_BOLD}{host}{C_RESET} -> {svc_trunc}"

    if raw_output:
        needed_for_separator = 2
        rem_chars = inner_width - len(prefix_plain) - needed_for_separator
        if rem_chars > 5:
            if len(raw_output) > rem_chars:
                out_trunc = raw_output[:rem_chars - 3] + "..."
            else:
                out_trunc = raw_output
            return f"{prefix_color}: {C_MUTED}{out_trunc}{C_RESET}"

    return prefix_color

def draw_noc(conf):
    cols, rows = shutil.get_terminal_size((80, 24))
    cols = max(60, cols)
    rows = max(18, rows)
    inner_width = cols - 4

    max_ledger_count = max(1, rows - 15)

    sla, incidents, acks, blocks, ledger = get_telemetry(max_ledger_count)
    
    tv_24_mode = str(conf.get('tv_24', '1'))
    if tv_24_mode == "2":
        time_fmt = "%Y-%m-%d  %-I:%M %p"
    elif tv_24_mode == "3":
        time_fmt = "%Y-%m-%d  %-I:%M"
    else:
        time_fmt = "%Y-%m-%d  %H:%M"

    now_str = datetime.now().strftime(time_fmt)
    hostname = conf.get('alias')

    out = []

    # Frame: Header
    out.append(border_line("┌", "─", "┐", cols))
    
    title_plain = "N E M S   C E N T R A L   C O M M A N D   //   TACTICAL TTY NOC v1.8"
    title_vis = f"{C_BOLD}{C_WHITE}N E M S   C E N T R A L   C O M M A N D{C_RESET}   {C_CYAN}//   TACTICAL TTY NOC v1.8{C_RESET}"
    if len(title_plain) > inner_width:
        title_plain = "NEMS CENTRAL COMMAND // TTY NOC v1.8"
        title_vis = f"{C_BOLD}{C_WHITE}NEMS CENTRAL COMMAND{C_RESET} // {C_CYAN}TTY NOC v1.8{C_RESET}"
    out.append(box_line(title_vis, cols))

    node_part_plain = f"NODE: {hostname}"
    time_part_plain = f"TIME: {now_str}"
    pad_len = max(1, inner_width - len(node_part_plain) - len(time_part_plain) - 4)
    
    host_time_vis = f"  NODE: {C_GREEN}{hostname}{C_RESET}{' ' * pad_len}TIME: {C_BOLD}{now_str}{C_RESET}"
    out.append(box_line(host_time_vis, cols))
    out.append(border_line("├", "─", "┤", cols))

    # Frame: Metrics
    out.append(box_line(f"{C_BOLD}[SYSTEM METRICS]{C_RESET}", cols))

    sla_color = C_GREEN if sla >= 95 else (C_AMBER if sla >= 85 else C_RED)
    inc_color = C_RED if incidents > 0 else C_MUTED
    ack_color = C_CYAN if acks > 0 else C_MUTED

    bar_label = "  30-DAY HEALTH   : ["
    bar_suffix = f"] {sla}%"
    bar_avail = max(10, inner_width - len(bar_label) - len(bar_suffix))
    
    filled = int((sla / 100) * bar_avail)
    bar_str = "█" * filled + "░" * (bar_avail - filled)
    
    m_sla_vis = f"  30-DAY HEALTH   : [{sla_color}{bar_str}{C_RESET}] {sla_color}{sla}%{C_RESET}"
    out.append(box_line(m_sla_vis, cols))

    inc_vis = f"  INCIDENTS (30D) : [{inc_color}{incidents:02d}{C_RESET}] EVENTS DETECTED"
    out.append(box_line(inc_vis, cols))

    ack_vis = f"  ACKNOWLEDGED    : [{ack_color}{acks:02d}{C_RESET}] OPERATOR NOTES RECORDED"
    out.append(box_line(ack_vis, cols))
    out.append(border_line("├", "─", "┤", cols))

    # Frame: 24-Hour Ribbon
    out.append(box_line(f"{C_BOLD}[24-HOUR HEALTH]{C_RESET}", cols))

    block_repeat = max(1, (inner_width - 16) // 48)
    ribbon = ""
    for b in blocks[:48]:
        if b == 0:
            char = f"{C_GREEN}█{C_RESET}"
        elif b == 1:
            char = f"{C_RED}█{C_RESET}"
        elif b == 2:
            char = f"{C_CYAN}█{C_RESET}"
        else:
            char = f"{C_MUTED}░{C_RESET}"
        ribbon += char * block_repeat

    r_line_vis = f"  -24H [{ribbon}] NOW "
    out.append(box_line(r_line_vis, cols))
    out.append(border_line("├", "─", "┤", cols))

    # Frame: Audit Ledger
    out.append(box_line(f"{C_BOLD}[REAL-TIME AUDIT LEDGER]{C_RESET}", cols))

    if not ledger:
        nom_vis = f"  {C_GREEN}✓ SYSTEM STABILITY - NO EVENTS FOUND IN DATABASE{C_RESET}"
        out.append(box_line(nom_vis, cols))
        for _ in range(max_ledger_count - 1):
            out.append(box_line("", cols))
    else:
        for ev in ledger[:max_ledger_count]:
            color_line = format_ledger_line(ev[0], ev[1], ev[2], ev[3], ev[4], inner_width)
            out.append(box_line(color_line, cols))

        for _ in range(max_ledger_count - len(ledger[:max_ledger_count])):
            out.append(box_line("", cols))

    out.append(border_line("└", "─", "┘", cols))

    sys.stdout.write("\033[H")
    sys.stdout.write("\n".join(out))
    sys.stdout.flush()

def main():
    sys.stdout.write("\033[2J\033[?25l")
    conf = get_nems_config()
    try:
        while True:
            draw_noc(conf)
            time.sleep(2)
    except KeyboardInterrupt:
        pass
    finally:
        sys.stdout.write("\033[?25h\033[2J\033[H")

if __name__ == "__main__":
    main()
