#!/usr/bin/env python3
"""Synthetic syslog-style lines to Core's TCP source, plus TCP listener for Core's sink.

Default layout matches padas-registry-quickstart.json:
  - Core syslog source listens on 0.0.0.0:8080; this script connects to 127.0.0.1:8080 and sends one line per interval.
  - Core syslog sink connects to 127.0.0.1:8081; this script listens on 0.0.0.0:8081 and prints each received line as [SINK].

Environment overrides:
  PADAS_TCP_SOURCE_HOST   (default 127.0.0.1)
  PADAS_TCP_SOURCE_PORT   (default 8080)
  PADAS_TCP_SINK_BIND     (default 0.0.0.0)
  PADAS_TCP_SINK_PORT     (default 8081)
  PADAS_QUICKSTART_EVENT_INTERVAL_SEC (default 1)
"""

from __future__ import annotations

import os
import random
import socket
import threading
import time
from datetime import datetime

SOURCE_TARGET_HOST = os.environ.get("PADAS_TCP_SOURCE_HOST", "127.0.0.1")
SOURCE_TARGET_PORT = int(os.environ.get("PADAS_TCP_SOURCE_PORT", "8080"))

SINK_HOST = os.environ.get("PADAS_TCP_SINK_BIND", "0.0.0.0")
SINK_PORT = int(os.environ.get("PADAS_TCP_SINK_PORT", "8081"))

EVENT_INTERVAL_SECONDS = float(os.environ.get("PADAS_QUICKSTART_EVENT_INTERVAL_SEC", "1"))

TEMPLATES = [
    'date={date} time={time} logid="0004000017" type="traffic" subtype="sniffer" level="notice" vd="root" eventtime={eventtime} srcip={srcip} srcport={srcport} dstip={dstip} dstport=443 sessionid={sessionid} proto=6 action="accept" service="HTTPS" duration={duration} sentbyte={sentbyte} rcvdbyte={rcvdbyte}',
    'date={date} time={time} logid="0000000013" type="traffic" subtype="forward" level="notice" vd="vdom1" eventtime={eventtime} srcip={srcip} srcport={srcport} dstip={dstip} dstport=80 sessionid={sessionid} proto=6 action="close" service="HTTP" app="HTTP.BROWSER_Firefox" duration={duration} sentbyte={sentbyte} rcvdbyte={rcvdbyte}',
    'date={date} time={time} logid="0100032001" type="event" subtype="system" level="information" vd="vdom1" eventtime={eventtime} logdesc="Admin login successful" user="admin" method="ssh" srcip={srcip} dstip=172.16.200.2 action="login" status="success" msg="Administrator admin logged in successfully from ssh({srcip})"',
]


def random_ip(private: bool = False) -> str:
    if private:
        return f"10.{random.randint(1, 254)}.{random.randint(1, 254)}.{random.randint(1, 254)}"
    return f"{random.randint(20, 223)}.{random.randint(1, 254)}.{random.randint(1, 254)}.{random.randint(1, 254)}"


def create_event() -> str:
    now = datetime.now()
    return random.choice(TEMPLATES).format(
        date=now.strftime("%Y-%m-%d"),
        time=now.strftime("%H:%M:%S"),
        eventtime=int(now.timestamp() * 1_000_000_000),
        srcip=random.choice([random_ip(private=True), random_ip()]),
        dstip=random.choice([random_ip(private=True), random_ip()]),
        srcport=random.randint(1024, 65535),
        sessionid=random.randint(100000, 9999999),
        duration=random.randint(1, 300),
        sentbyte=random.randint(0, 50000),
        rcvdbyte=random.randint(0, 50000),
    )


def send_source_events() -> None:
    while True:
        try:
            print(f"[SOURCE] Connecting to Padas TCP source at {SOURCE_TARGET_HOST}:{SOURCE_TARGET_PORT}")

            with socket.create_connection((SOURCE_TARGET_HOST, SOURCE_TARGET_PORT), timeout=5) as sock:
                print("[SOURCE] Connected")

                while True:
                    event = create_event()
                    sock.sendall((event + "\n").encode("utf-8"))
                    time.sleep(EVENT_INTERVAL_SECONDS)

        except Exception as e:
            print(f"[SOURCE ERROR] {e}")
            time.sleep(3)


def handle_sink_client(conn: socket.socket, addr) -> None:
    print(f"[SINK CONNECTED] {addr[0]}:{addr[1]}")
    buffer = b""

    try:
        while True:
            data = conn.recv(4096)

            if not data:
                break

            buffer += data

            while b"\n" in buffer:
                line, buffer = buffer.split(b"\n", 1)
                event = line.decode("utf-8", errors="replace").strip()

                if event:
                    print(f"[SINK] {event}")

        if buffer:
            event = buffer.decode("utf-8", errors="replace").strip()
            if event:
                print(f"[SINK] {event}")

    except Exception as e:
        print(f"[SINK ERROR] {addr}: {e}")

    finally:
        conn.close()
        print(f"[SINK DISCONNECTED] {addr[0]}:{addr[1]}")


def start_sink_server() -> None:
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((SINK_HOST, SINK_PORT))
    server.listen()

    print(f"[SINK] Listening on {SINK_HOST}:{SINK_PORT}")

    while True:
        conn, addr = server.accept()
        threading.Thread(
            target=handle_sink_client,
            args=(conn, addr),
            daemon=True,
        ).start()


if __name__ == "__main__":
    print("Padas TCP quickstart helper")
    print()
    print("Padas source connector (syslog TCP) should listen on:")
    print(f"  Host: 0.0.0.0 (or reachable bind)  Port: {SOURCE_TARGET_PORT}")
    print()
    print("This script sends synthetic lines to:")
    print(f"  {SOURCE_TARGET_HOST}:{SOURCE_TARGET_PORT}")
    print()
    print("Padas sink connector should connect to:")
    print(f"  address 127.0.0.1  port {SINK_PORT}  (Core process → loopback)")
    print()
    print("This script listens for sink TCP on:")
    print(f"  {SINK_HOST}:{SINK_PORT}")
    print()

    threading.Thread(target=start_sink_server, daemon=True).start()
    send_source_events()
