#!/usr/bin/env python3
from __future__ import annotations

import json
import logging
import socket
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from urllib import error, request

import yaml

LOG = logging.getLogger("aw.dlp.syslog_forwarder")


def setup_logging() -> None:
    logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")


def load_yaml(path: Path) -> dict[str, Any]:
    if not path.exists():
        return {}
    data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
    return data if isinstance(data, dict) else {}


def load_json(path: Path) -> dict[str, Any]:
    if not path.exists():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return data if isinstance(data, dict) else {}


def save_json(path: Path, payload: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")


def http_json(url: str, timeout: int = 15) -> Any:
    req = request.Request(url, method="GET")
    with request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode("utf-8", errors="ignore"))


def iter_new_incidents(aw_base: str, state: dict[str, Any], per_bucket_limit: int) -> tuple[list[dict[str, Any]], dict[str, int]]:
    buckets = http_json(f"{aw_base}/buckets/")
    bucket_ids = sorted([bid for bid in buckets.keys() if bid.startswith("aw-dlp-incidents_")])
    last_ids = state.get("last_ids", {})
    if not isinstance(last_ids, dict):
        last_ids = {}
    max_ids: dict[str, int] = {}
    out: list[dict[str, Any]] = []
    for bid in bucket_ids:
        try:
            events = http_json(f"{aw_base}/buckets/{bid}/events?limit={int(per_bucket_limit)}")
        except (TimeoutError, OSError, error.URLError, error.HTTPError) as exc:
            LOG.warning("skip bucket %s: %s", bid, exc)
            continue
        prev = int(last_ids.get(bid, 0))
        bucket_max = prev
        for ev in events:
            eid = int(ev.get("id") or 0)
            if eid <= prev:
                continue
            out.append(ev)
            if eid > bucket_max:
                bucket_max = eid
        max_ids[bid] = bucket_max
    out.sort(key=lambda x: int(x.get("id") or 0))
    return out, max_ids


def build_message(event: dict[str, Any], app_name: str, facility: int) -> str:
    pri = facility * 8 + 6
    ts = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    data = event.get("data") or {}
    host = str(data.get("hostname") or "unknown")
    payload = json.dumps(
        {
            "event_id": event.get("id"),
            "timestamp": event.get("timestamp"),
            "host": host,
            "severity": data.get("severity"),
            "signalType": data.get("signalType"),
            "username": data.get("username"),
            "action": data.get("action"),
            "message": data.get("message"),
            "data": data,
        },
        ensure_ascii=False,
        separators=(",", ":"),
    )
    return f"<{pri}>1 {ts} {host} {app_name} - - - {payload}"


def send_syslog(line: str, host: str, port: int, proto: str, timeout: int = 10) -> None:
    if proto.lower() == "tcp":
        sock = socket.create_connection((host, port), timeout=timeout)
        try:
            sock.sendall((line + "\n").encode("utf-8", errors="ignore"))
        finally:
            sock.close()
        return
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        sock.sendto(line.encode("utf-8", errors="ignore"), (host, port))
    finally:
        sock.close()


def main() -> None:
    setup_logging()
    cfg_path = Path("/opt/activitywatch/dlp-integrations/syslog-forwarder-config.yaml")
    cfg = load_yaml(cfg_path)
    aw_base = str(cfg.get("aw_api_base", "http://127.0.0.1:5600/api/0")).rstrip("/")
    state_path = Path(str(cfg.get("state_path", "/var/lib/activitywatch/dlp-integrations/syslog-forwarder-state.json")))
    per_bucket_limit = int(cfg.get("per_bucket_limit", 300))
    syslog_host = str(cfg.get("syslog_host", "127.0.0.1"))
    syslog_port = int(cfg.get("syslog_port", 514))
    syslog_proto = str(cfg.get("syslog_proto", "udp"))
    facility = int(cfg.get("facility", 16))
    app_name = str(cfg.get("app_name", "aw-dlp"))

    state = load_json(state_path)
    try:
        incidents, max_ids = iter_new_incidents(aw_base=aw_base, state=state, per_bucket_limit=per_bucket_limit)
    except (TimeoutError, OSError, error.URLError, error.HTTPError) as exc:
        LOG.warning("skip syslog forwarder run: AW API unavailable: %s", exc)
        return

    sent = 0
    for event in incidents:
        line = build_message(event, app_name=app_name, facility=facility)
        send_syslog(line=line, host=syslog_host, port=syslog_port, proto=syslog_proto)
        sent += 1

    save_json(state_path, {"last_ids": max_ids})
    LOG.info("syslog forwarder sent=%d buckets=%d", sent, len(max_ids))


if __name__ == "__main__":
    main()
