#!/usr/bin/env python3
"""
==============================================================================
Multi-Server Certificate Exchange Command Proposer & Wrapper (Python)
==============================================================================
Runs on the Elasticsearch host. Parses local elasticsearch.yml and fetches
remote kibana.yml over SSH from the Kibana + Elastic Agent server.
Proposes the exact SCP + SSH certificate exchange command.
==============================================================================
"""

import os
import sys
import re
import argparse
import socket
import subprocess
import json

# Terminal ANSI Color Codes
CYAN = "\033[0;36m"
BOLD_CYAN = "\033[1;36m"
BLUE = "\033[0;34m"
BOLD_BLUE = "\033[1;34m"
GREEN = "\033[0;32m"
BOLD_GREEN = "\033[1;32m"
YELLOW = "\033[0;33m"
BOLD_YELLOW = "\033[1;33m"
RED = "\033[0;31m"
BOLD_RED = "\033[1;31m"
DIM = "\033[2m"
BOLD = "\033[1m"
RESET = "\033[0m"


def parse_simple_yaml_str(yaml_content):
    config = {}
    for line in yaml_content.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if ":" in line:
            parts = line.split(":", 1)
            k = parts[0].strip()
            v = parts[1].strip().strip("\"'")
            if v:
                config[k] = v
    return config


def parse_file_yaml(file_path):
    if not os.path.exists(file_path):
        return {}
    try:
        with open(file_path, "r", encoding="utf-8") as f:
            return parse_simple_yaml_str(f.read())
    except Exception:
        return {}


def discover_local_es_network():
    dns_names = set(["localhost"])
    ip_addresses = set(["127.0.0.1"])

    try:
        hostname = socket.gethostname()
        if hostname:
            dns_names.add(hostname)
            fqdn = socket.getfqdn()
            if fqdn:
                dns_names.add(fqdn)
    except Exception:
        pass

    try:
        res = subprocess.run(["hostname", "-I"], capture_output=True, text=True, timeout=2)
        if res.returncode == 0:
            for ip in res.stdout.strip().split():
                if ":" not in ip:
                    ip_addresses.add(ip)
    except Exception:
        pass

    return sorted(list(dns_names)), sorted(list(ip_addresses))


def fetch_remote_kibana_config(kibana_host, kibana_user="root", kibana_port="22", ssh_key=None, remote_config_path="/etc/kibana/kibana.yml"):
    ssh_cmd = ["ssh", "-o", "StrictHostKeyChecking=no", "-p", str(kibana_port)]
    if ssh_key:
        ssh_cmd.extend(["-i", ssh_key])
    target = f"{kibana_user}@{kibana_host}" if kibana_user else kibana_host
    ssh_cmd.extend([target, f"cat {remote_config_path} 2>/dev/null || true"])

    try:
        res = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=5)
        if res.returncode == 0 and res.stdout.strip():
            return parse_simple_yaml_str(res.stdout), True
    except Exception:
        pass
    return {}, False


def propose_remote_command(kibana_host, kibana_user, kibana_port, ssh_key, es_info, kib_info, es_dns, es_ips, kib_dns, kib_ips, update_script_path):
    env_vars = {}
    env_vars["KIBANA_HOST"] = kibana_host
    if kibana_user and kibana_user != "root":
        env_vars["KIBANA_SSH_USER"] = kibana_user
    if kibana_port and str(kibana_port) != "22":
        env_vars["KIBANA_SSH_PORT"] = str(kibana_port)
    if ssh_key:
        env_vars["KIBANA_SSH_KEY"] = ssh_key

    if es_info["http_p12"] != "/etc/elasticsearch/certs/http.p12":
        env_vars["ES_HTTP_P12_TARGET"] = es_info["http_p12"]
    if es_info["transport_p12"] != "/etc/elasticsearch/certs/transport.p12":
        env_vars["ES_TRANSPORT_P12_TARGET"] = es_info["transport_p12"]

    if kib_info.get("server.ssl.certificate") and kib_info["server.ssl.certificate"] != "/etc/kibana/certs/kibana.crt":
        env_vars["REMOTE_KIBANA_CRT_TARGET"] = kib_info["server.ssl.certificate"]
    if kib_info.get("server.ssl.key") and kib_info["server.ssl.key"] != "/etc/kibana/certs/kibana.key":
        env_vars["REMOTE_KIBANA_KEY_TARGET"] = kib_info["server.ssl.key"]

    es_dns_str = " ".join(es_dns)
    es_ip_str = " ".join(es_ips)
    kib_dns_str = " ".join(kib_dns)
    kib_ip_str = " ".join(kib_ips)

    env_vars["ES_SAN_DNS"] = f'"{es_dns_str}"'
    env_vars["ES_SAN_IPS"] = f'"{es_ip_str}"'
    env_vars["KIBANA_SAN_DNS"] = f'"{kib_dns_str}"'
    env_vars["KIBANA_SAN_IPS"] = f'"{kib_ip_str}"'

    env_part = " ".join([f"{k}={v}" for k, v in env_vars.items()])
    cmd = f"sudo {env_part} {update_script_path}"
    return cmd, env_vars


def main():
    parser = argparse.ArgumentParser(description="Propose Multi-Server Certificate Exchange Command (Run on ES Host)")
    parser.add_argument("--kibana-host", default="10.1.10.4", help="Remote Kibana + Agent host IP/domain")
    parser.add_argument("--kibana-user", default="root", help="SSH user for Kibana host")
    parser.add_argument("--kibana-port", default="22", help="SSH port for Kibana host")
    parser.add_argument("--ssh-key", default="", help="Path to SSH key file")
    parser.add_argument("--es-config", default="/etc/elasticsearch/elasticsearch.yml", help="Path to local elasticsearch.yml")
    parser.add_argument("--kibana-config", default="/etc/kibana/kibana.yml", help="Path to remote kibana.yml")
    parser.add_argument("--execute", "-e", action="store_true", help="Execute the proposed command immediately")
    parser.add_argument("--json", action="store_true", help="Output raw configuration in JSON format")

    args = parser.parse_args()

    script_dir = os.path.dirname(os.path.abspath(__file__))
    py_script = os.path.join(script_dir, "update-certs-remote.py")
    sh_script = os.path.join(script_dir, "update-certs-remote.sh")
    update_script = py_script if os.path.exists(py_script) else sh_script

    # Local ES parsing & network discovery
    es_data = parse_file_yaml(args.es_config)
    es_info = {
        "http_p12": es_data.get("xpack.security.http.ssl.keystore.path", "/etc/elasticsearch/certs/http.p12"),
        "transport_p12": es_data.get("xpack.security.transport.ssl.keystore.path", "/etc/elasticsearch/certs/transport.p12"),
        "http_ca": es_data.get("xpack.security.http.ssl.certificate_authorities", "/etc/elasticsearch/certs/http_ca.crt"),
    }
    es_dns, es_ips = discover_local_es_network()

    # Remote Kibana SSH fetch
    kib_data, kib_ssh_success = fetch_remote_kibana_config(args.kibana_host, args.kibana_user, args.kibana_port, args.ssh_key, args.kibana_config)
    kib_dns = ["localhost", "kibana-server", args.kibana_host]
    kib_ips = ["127.0.0.1", args.kibana_host]

    proposed_cmd, env_vars = propose_remote_command(
        args.kibana_host, args.kibana_user, args.kibana_port, args.ssh_key,
        es_info, kib_data, es_dns, es_ips, kib_dns, kib_ips, update_script
    )

    if args.json:
        out = {
            "local_elasticsearch": es_info,
            "remote_kibana_host": args.kibana_host,
            "remote_kibana_ssh_connected": kib_ssh_success,
            "remote_kibana_config": kib_data,
            "proposed_command": proposed_cmd,
            "env_vars": env_vars,
        }
        print(json.dumps(out, indent=2))
        return

    print(f"\n{BOLD_CYAN}===================================================={RESET}")
    print(f" {BOLD_BLUE}Multi-Server Certificate Exchange Proposer (ES ➜ Kibana){RESET}")
    print(f"{BOLD_CYAN}===================================================={RESET}\n")

    print(f"{BOLD_CYAN}[1] Host Topology:{RESET}")
    print(f"    {CYAN}↳{RESET} Local Elasticsearch Server: {BOLD}Local Host{RESET} ({args.es_config})")
    kib_status_str = f"{BOLD_GREEN}Connected over SSH{RESET}" if kib_ssh_success else f"{YELLOW}Using standard remote defaults (SSH test pending){RESET}"
    print(f"    {CYAN}↳{RESET} Remote Kibana + Agent Server: {BOLD}{args.kibana_user}@{args.kibana_host}:{args.kibana_port}{RESET} [{kib_status_str}]")

    print(f"\n{BOLD_CYAN}[2] Discovered SSL Targets & Transport Plan:{RESET}")
    print(f"    {CYAN}↳{RESET} ES Local Keystore:    {BOLD}{es_info['http_p12']}{RESET}")
    print(f"    {CYAN}↳{RESET} Kibana Remote Cert:   {BOLD}{kib_data.get('server.ssl.certificate', '/etc/kibana/certs/kibana.crt')}{RESET}")
    print(f"    {CYAN}↳{RESET} Kibana Remote Key:    {BOLD}{kib_data.get('server.ssl.key', '/etc/kibana/certs/kibana.key')}{RESET}")
    print(f"    {CYAN}↳{RESET} SCP Destination Host: {BOLD}{args.kibana_host}{RESET}")

    print(f"\n{BOLD_CYAN}[3] Discovered Subject Alternative Names (SANs):{RESET}")
    print(f"    {CYAN}↳{RESET} ES Node DNS/IPs:     {GREEN}{' '.join(es_dns)}{RESET} | {GREEN}{' '.join(es_ips)}{RESET}")
    print(f"    {CYAN}↳{RESET} Kibana Node DNS/IPs: {GREEN}{' '.join(kib_dns)}{RESET} | {GREEN}{' '.join(kib_ips)}{RESET}")

    print(f"\n{BOLD_CYAN}===================================================={RESET}")
    print(f" {BOLD_GREEN}PROPOSED MULTI-SERVER CERTIFICATE EXCHANGE COMMAND:{RESET}")
    print(f"{BOLD_CYAN}===================================================={RESET}")
    print(f"\n  {BOLD_YELLOW}{proposed_cmd}{RESET}\n")

    if args.execute:
        print(f"{BOLD_GREEN}Executing proposed multi-server cert update script...{RESET}\n")
        env = os.environ.copy()
        for k, v in env_vars.items():
            env[k] = v.strip('"')
        cmd_list = ["sudo", "-E", sys.executable, update_script] if update_script.endswith(".py") else ["sudo", "-E", update_script]
        subprocess.run(cmd_list, env=env)


if __name__ == "__main__":
    main()
