from __future__ import annotations

import json
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional

from core.database import EventDatabase
from core.paths import config_path as default_config_path
from core.rules import RuleEngine


def utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="milliseconds")


@dataclass
class TelemetryRelayService:
    """Service-oriented runtime wrapper around the telemetry relay loop."""

    config_path: Optional[str | Path] = None
    output_path: Optional[str | Path] = None
    enabled: bool = True
    poll_interval: int = 2
    heartbeat_interval: int = 30
    db_path: Optional[str | Path] = None
    database: Optional[EventDatabase] = field(default=None, init=False)
    rule_engine: Optional[RuleEngine] = field(default=None, init=False)
    _output_path_override: bool = field(default=False, init=False, repr=False)

    def __post_init__(self):
        self.config_path = Path(self.config_path) if self.config_path is not None else default_config_path()
        self._output_path_override = self.output_path is not None
        self.output_path = Path(self.output_path) if self.output_path is not None else Path("C:/Logs/process_monitor_events.jsonl")
        self._load_runtime_settings()
        self.database = EventDatabase(self.db_path)
        self.rule_engine = RuleEngine(self.config_path)

    def _load_runtime_settings(self) -> None:
        try:
            payload = json.loads(self.config_path.read_text(encoding="utf-8"))
        except FileNotFoundError:
            payload = {}

        self.enabled = bool(payload.get("Enabled", self.enabled))
        telemetry = payload.get("Telemetry", {})
        self.poll_interval = max(1, int(telemetry.get("PollIntervalSeconds", self.poll_interval)))
        self.heartbeat_interval = max(5, int(telemetry.get("HeartbeatIntervalSeconds", self.heartbeat_interval)))
        configured_path = telemetry.get("Path")
        if configured_path and not self._output_path_override:
            self.output_path = Path(configured_path)
        configured_db_path = telemetry.get("DatabasePath")
        if configured_db_path and self.db_path is None:
            self.db_path = Path(configured_db_path)

    def append_jsonl(self, event: Dict[str, Any]) -> Dict[str, Any]:
        output_path = Path(self.output_path) if not isinstance(self.output_path, Path) else self.output_path
        self.output_path = output_path
        output_path.parent.mkdir(parents=True, exist_ok=True)
        if event.get("event") and not event.get("event_id"):
            event["event_id"] = f"relay-{uuid.uuid4().hex}"
        serialized = json.dumps({key: value for key, value in event.items() if key != "alert_record"}, separators=(",", ":"))
        with output_path.open("a", encoding="utf-8") as handle:
            handle.write(serialized + "\n")
        if event.get("event"):
            self.database.insert_event(
                {
                    "event_id": event["event_id"],
                    "timestamp": event.get("timestamp", utc_now_iso()),
                    "event_type": event.get("event", "relay_event"),
                    "source": event.get("source", "relay_service"),
                    "severity": event.get("severity", "info"),
                    "confidence": 0,
                    "hostname": None,
                    "user": None,
                    "pid": event.get("pid"),
                    "parent_pid": event.get("parent_pid"),
                    "process_name": event.get("image") or event.get("process_name"),
                    "path": event.get("path"),
                    "command_line": event.get("command_line"),
                    "sha256": event.get("sha256"),
                    "signer": event.get("signer"),
                    "signed": event.get("signed"),
                    "rule_id": event.get("rule_id"),
                    "risk_score": int(event.get("risk_score") or 0),
                    "tags": [],
                    "metadata": {k: v for k, v in event.items() if k not in {"event", "source", "timestamp", "severity", "pid", "image", "command_line", "risk_score", "rule_id", "alert_record"}},
                }
            )
            alert = event.get("alert_record")
            if isinstance(alert, dict):
                alert["event_id"] = event["event_id"]
                self.database.insert_alert(alert)
        return event

    @staticmethod
    def _process_context(pid: int, value: Any) -> Dict[str, Any]:
        if isinstance(value, dict):
            context = dict(value)
            context.setdefault("pid", pid)
            context.setdefault("process_name", context.get("image") or "Unknown")
            return context
        return {"pid": pid, "process_name": str(value or "Unknown"), "path": None, "parent_pid": None,
                "parent_process_name": None, "command_line": None, "signer": None, "signed": None,
                "signature_status": "Unknown", "sha256": None, "creation_time": None}

    def run_cycle(self, previous: Dict[int, Any], current: Dict[int, Any]) -> List[Dict[str, Any]]:
        events: List[Dict[str, Any]] = []

        for pid, process in current.items():
            if pid not in previous:
                context = self._process_context(pid, process)
                timestamp = utc_now_iso()
                event = {
                    "timestamp": timestamp,
                    "source": "relay_service",
                    "event": "process_started",
                    "pid": pid,
                    "image": context.get("process_name") or "Unknown",
                    "path": context.get("path"),
                    "parent_pid": context.get("parent_pid"),
                    "parent_process_name": context.get("parent_process_name"),
                    "command_line": context.get("command_line"),
                    "signer": context.get("signer"),
                    "signed": context.get("signed"),
                    "signature_status": context.get("signature_status") or "Unknown",
                    "signature_message": context.get("signature_message"),
                    "sha256": context.get("sha256"),
                    "first_seen": context.get("creation_time") or timestamp,
                    "last_seen": timestamp,
                    "severity": "info",
                    "risk_score": 0,
                    "triggered_rule": False,
                }
                decision = self.rule_engine.classify(event)
                event.update({key: decision[key] for key in ("classification", "classification_reason", "severity", "risk_score", "rule_id", "matched_rules")})
                event["triggered_rule"] = bool(decision["alert"])
                alert = self.rule_engine.build_alert(event, decision)
                if alert is not None:
                    event["alert_record"] = alert
                self.append_jsonl(event)
                event.pop("alert_record", None)
                events.append(event)

        for pid, process in previous.items():
            if pid not in current:
                context = self._process_context(pid, process)
                event = {
                    "timestamp": utc_now_iso(),
                    "source": "relay_service",
                    "event": "process_exited",
                    "pid": pid,
                    "image": context.get("process_name") or "Unknown",
                    "path": context.get("path"),
                    "parent_pid": context.get("parent_pid"),
                    "parent_process_name": context.get("parent_process_name"),
                    "command_line": context.get("command_line"),
                    "signer": context.get("signer"),
                    "signed": context.get("signed"),
                    "signature_status": context.get("signature_status") or "Unknown",
                    "sha256": context.get("sha256"),
                    "first_seen": context.get("creation_time"),
                    "last_seen": utc_now_iso(),
                    "severity": "info",
                    "risk_score": 0,
                    "triggered_rule": False,
                    "classification": "NORMAL",
                    "classification_reason": "Process lifecycle exit telemetry",
                    "rule_id": None,
                    "matched_rules": [],
                }
                self.append_jsonl(event)
                events.append(event)

        return events

    def close(self) -> None:
        if self.database is not None:
            self.database.close()
