import os
import requests
import datetime
import paramiko
import time
import psycopg2

# ==== CONFIG ====
MASTODON_INSTANCE = "https://chatwithus.live"
MASTODON_TOKEN = ""
MASTODON_USER_ID = ""
HEALTHCHECK_HTML = "/var/www/html/healthcheck.html"

DISK_WARN_THRESHOLD = 10
INODE_WARN_THRESHOLD = 10
LOG_FILES = ["/var/log/syslog", "/var/log/nginx/error.log"]
LOG_PATTERNS = ["ERROR", "FATAL", "disk full", "out of memory"]
SUPPRESSED_PATTERNS = ["SomeKnownHarmlessMastodonError"]

NODES = [
    {"name": "shredder", "host": "38.102.127.171", "ssh_user": "doc", "services": ["minio.service"], "disks": ["/", "/mnt/raid5"], "db": False, "raid": True},
    {"name": "mastodon", "host": "chatwithus.live", "ssh_user": "root", "services": ["nginx", "mastodon-web"], "disks": ["/"], "db": False, "raid": False},
    {"name": "db1", "host": "cluster.db1.genesishostingtechnologies.com", "ssh_user": "doc", "services": ["postgresql@16-main.service"], "disks": ["/", "/var/lib/postgresql"], "db": True, "raid": False},
    {"name": "db2", "host": "cluster.db2.genesishostingtechnologies.com", "ssh_user": "doc", "services": ["postgresql@16-main.service"], "disks": ["/", "/var/lib/postgresql"], "db": True, "raid": False}
]

# ==== Mastodon DM ====
def mastodon_dm(message, retries=3):
    url = f"{MASTODON_INSTANCE}/api/v1/statuses"
    headers = {"Authorization": f"Bearer {MASTODON_TOKEN}"}
    payload = {"status": message, "visibility": "direct", "in_reply_to_account_id": MASTODON_USER_ID}
    for attempt in range(retries):
        resp = requests.post(url, headers=headers, data=payload)
        if resp.status_code == 200:
            return
        print(f"Failed to send Mastodon DM (attempt {attempt+1}): {resp.text}")
        time.sleep(5)

# ==== SSH Helper ====
def ssh_command(host, user, cmd):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=host, username=user, timeout=10)
    stdin, stdout, stderr = ssh.exec_command(cmd)
    out = stdout.read().decode().strip()
    ssh.close()
    return out

# ==== Health Check Helpers ====
def choose_emoji(line):
    if "RAID" in line:
        return "🧨"
    if "disk" in line.lower():
        return "📈"
    if "rclone" in line.lower():
        return "🐢"
    if "Service" in line:
        return "🛑"
    if "Replication" in line:
        return "💥"
    return "⚠️"

def check_remote_disk(host, user, path, node_name):
    try:
        cmd = f"df --output=pcent {path} | tail -1 | tr -dc '0-9'"
        out = ssh_command(host, user, cmd)
        if not out:
            return f"[{node_name}] ERROR: Disk {path} not found or could not check disk usage."
        percent = int(out)
        if percent > (100 - DISK_WARN_THRESHOLD):
            return f"[{node_name}] WARNING: Only {100 - percent}% disk free on {path}."
    except Exception as e:
        return f"[{node_name}] ERROR: Disk check failed: {e}"
    return None

def check_remote_service(host, user, service, node_name):
    try:
        cmd = f"systemctl is-active {service}"
        out = ssh_command(host, user, cmd)
        if out.strip() != "active":
            return f"[{node_name}] CRITICAL: Service {service} not running!"
    except Exception as e:
        return f"[{node_name}] ERROR: Service check failed: {e}"
    return None

def check_replication(host, node_name):
    try:
        conn = psycopg2.connect(host=host, dbname="postgres", user="postgres", connect_timeout=5)
        cur = conn.cursor()
        cur.execute("SELECT pg_is_in_recovery();")
        is_replica = cur.fetchone()[0]
        if is_replica:
            cur.execute("SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::INT;")
            lag = cur.fetchone()[0]
            if lag is None:
                return f"[{node_name}] CRITICAL: Standby not streaming! Replication down."
            elif lag >= 60:
                return f"[{node_name}] WARNING: Replication lag is {lag} seconds."
        cur.close()
        conn.close()
    except Exception as e:
        return f"[{node_name}] ERROR: Replication check failed: {e}"
    return None

def check_remote_raid_md0(host, user, node_name):
    try:
        mdstat = ssh_command(host, user, "cat /proc/mdstat")
        lines = mdstat.splitlines()
        status = None
        inside_md0 = False
        for line in lines:
            if line.startswith("md0"):
                inside_md0 = True
            elif inside_md0:
                if "[" in line and "]" in line:
                    status = line[line.index("["):line.index("]")+1]
                    break
                if line.strip() == "" or ":" in line:
                    break
        if status is None:
            return f"[{node_name}] CRITICAL: /dev/md0 RAID status string not found!"
        if "_" in status:
            return f"[{node_name}] WARNING: /dev/md0 RAID degraded! Status: {status}"
    except Exception as e:
        return f"[{node_name}] ERROR: RAID check failed: {e}"
    return None

def check_remote_logs(host, user, node_name):
    alerts = []
    for log in LOG_FILES:
        cmd = f"tail -500 {log}"
        try:
            out = ssh_command(host, user, cmd)
            lines = out.split("\n")
            for pattern in LOG_PATTERNS:
                for line in lines:
                    if pattern in line and not any(suppress in line for suppress in SUPPRESSED_PATTERNS):
                        alerts.append(f"[{node_name}] WARNING: Pattern '{pattern}' in {log}")
        except Exception as e:
            alerts.append(f"[{node_name}] ERROR: Could not read log {log}: {e}")
    return alerts

# ==== Main Routine ====
def main():
    critical_problems = []
    warning_problems = []
    node_status = {}

    for node in NODES:
        status = " Healthy"

        for disk in node["disks"]:
            res = check_remote_disk(node["host"], node["ssh_user"], disk, node["name"])
            if res:
                if "CRITICAL" in res:
                    critical_problems.append(res)
                    status = "🚨 Critical"
                elif "WARNING" in res and status != "🚨 Critical":
                    warning_problems.append(res)
                    status = "Warning"

        for svc in node["services"]:
            res = check_remote_service(node["host"], node["ssh_user"], svc, node["name"])
            if res:
                if "CRITICAL" in res:
                    critical_problems.append(res)
                    status = "🚨 Critical"
                elif "WARNING" in res and status != "🚨 Critical":
                    warning_problems.append(res)
                    status = "⚠️ Warning"

        if node.get("db"):
            res = check_replication(node["host"], node["name"])
            if res:
                if "CRITICAL" in res:
                    critical_problems.append(res)
                    status = " Critical"
                else:
                    warning_problems.append(res)
                    if status != " Critical":
                        status = " Warning"

        if node.get("raid", False):
            res = check_remote_raid_md0(node["host"], node["ssh_user"], node["name"])
            if res:
                if "CRITICAL" in res:
                    critical_problems.append(res)
                    status = "Critical"
                else:
                    warning_problems.append(res)
                    if status != "Critical":
                        status = "Warning"

        logs = check_remote_logs(node["host"], node["ssh_user"], node["name"])
        for log_alert in logs:
            warning_problems.append(log_alert)
            if status != "Critical":
                status = "Warning"

        node_status[node["name"]] = status

    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    if critical_problems:
        formatted = "\n".join(f"- {choose_emoji(p)} {p}" for p in critical_problems)
        msg = f"🚨 Genesis Radio Critical Healthcheck {now} 🚨\n⚡ {len(critical_problems)} critical issues found:\n{formatted}"
        print(msg)
        mastodon_dm(msg)

    if warning_problems:
        formatted = "\n".join(f"- {choose_emoji(p)} {p}" for p in warning_problems)
        msg = f"⚠️ Genesis Radio Warning Healthcheck {now} ⚠️\n⚡ {len(warning_problems)} warnings found:\n{formatted}"
        print(msg)
        mastodon_dm(msg)

    if not critical_problems and not warning_problems:
        msg = f"✅ Genesis Radio Healthcheck {now}: All systems normal."
        print(msg)
        mastodon_dm(msg)

    # Write dashboard
    with open(HEALTHCHECK_HTML, "w") as f:
        f.write("<html><head><title>Genesis Radio Healthcheck</title><meta http-equiv='refresh' content='60'></head><body>")
        f.write(f"<h1>Genesis Radio System Health</h1>")
        f.write(f"<p>Last Checked: {now}</p>")
        f.write("<table border='1' cellpadding='5' style='border-collapse: collapse;'><tr><th>System</th><th>Status</th></tr>")
        for node, status in node_status.items():
            color = 'green' if 'Healthy' in status else ('orange' if 'Warning' in status else 'red')
            f.write(f"<tr><td>{node}</td><td style='color:{color};'>{status}</td></tr>")
        f.write("</table></body></html>")

if __name__ == "__main__":
    main()
