import json
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest import mock

from core.process_enrichment import ProcessEnricher
from core.service_runtime import TelemetryRelayService
from gui.adapters import DataAdapter


class Completed:
    def __init__(self, stdout): self.stdout = stdout


class TestProcessEnricher(unittest.TestCase):
    def test_snapshot_enriches_processes_and_correlates_parent(self):
        rows = [
            {"ProcessId": 10, "Name": "parent.exe", "ExecutablePath": "C:/parent.exe", "ParentProcessId": 4,
             "CommandLine": "parent.exe --service", "CreationDate": "20260814010000.000000-240"},
            {"ProcessId": 20, "Name": "child.exe", "ExecutablePath": "C:/child.exe", "ParentProcessId": 10,
             "CommandLine": "child.exe -enc payload", "CreationDate": "20260814010100.000000-240"},
        ]
        enricher = ProcessEnricher(runner=lambda *args, **kwargs: Completed(json.dumps(rows)))
        snapshot = enricher.snapshot()
        self.assertEqual("child.exe -enc payload", snapshot[20]["command_line"])
        self.assertEqual("C:/child.exe", snapshot[20]["path"])
        self.assertEqual(10, snapshot[20]["parent_pid"])
        self.assertEqual("parent.exe", snapshot[20]["parent_process_name"])

    def test_inaccessible_snapshot_returns_empty_without_raising(self):
        def denied(*args, **kwargs): raise subprocess.CalledProcessError(1, "powershell")
        self.assertEqual({}, ProcessEnricher(runner=denied).snapshot())

    def test_signature_status_is_cached_by_file_identity(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            executable = Path(tmpdir) / "signed.exe"; executable.write_bytes(b"binary")
            calls = []
            def runner(*args, **kwargs):
                calls.append(args)
                return Completed(json.dumps({"Status": "Valid", "StatusMessage": "Signature verified", "Signer": "CN=Example"}))
            enricher = ProcessEnricher(runner=runner)
            process = {"path": str(executable), "signer": None, "signed": None, "signature_status": "Unknown"}
            first = enricher.enrich_signature(process); second = enricher.enrich_signature(process)
            self.assertTrue(first["signed"]); self.assertEqual("CN=Example", first["signer"])
            self.assertEqual("Valid", second["signature_status"]); self.assertEqual(1, len(calls))

    def test_signature_failure_is_unknown_not_suspicious(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            executable = Path(tmpdir) / "blocked.exe"; executable.write_bytes(b"binary")
            def denied(*args, **kwargs): raise subprocess.CalledProcessError(1, "powershell")
            result = ProcessEnricher(runner=denied).enrich_signature({"path": str(executable)})
            self.assertIsNone(result["signed"]); self.assertEqual("Unavailable", result["signature_status"])

    def test_sha256_is_cached(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            executable = Path(tmpdir) / "sample.exe"; executable.write_bytes(b"sample executable")
            enricher = ProcessEnricher(); original = Path.open
            with mock.patch("pathlib.Path.open", autospec=True, side_effect=original) as opened:
                first = enricher.sha256(str(executable)); second = enricher.sha256(str(executable))
            self.assertEqual(first, second); self.assertEqual(1, opened.call_count)


class TestEnrichedRelay(unittest.TestCase):
    def setUp(self):
        self.tempdir = tempfile.TemporaryDirectory(); self.root = Path(self.tempdir.name)
        self.config = self.root / "config.json"
        self.config.write_text(json.dumps({"Enabled": True, "AllowList": [], "WatchList": [],
            "AlertRules": {"HighRiskKeywords": ["-enc"]}, "Telemetry": {"Path": str(self.root / "events.jsonl"),
            "DatabasePath": str(self.root / "events.db"), "PollIntervalSeconds": 1, "HeartbeatIntervalSeconds": 5,
            "RetentionDays": 14}}), encoding="utf-8")

    def tearDown(self): self.tempdir.cleanup()

    def test_enriched_command_line_drives_rule_and_persists_context(self):
        service = TelemetryRelayService(config_path=self.config)
        context = {"pid": 50, "process_name": "tool.exe", "path": "C:/Tools/tool.exe", "parent_pid": 10,
                   "parent_process_name": "parent.exe", "command_line": "tool.exe -enc payload", "signer": "CN=Vendor",
                   "signed": True, "signature_status": "Valid", "sha256": None, "creation_time": "2026-08-14T01:00:00Z"}
        event = service.run_cycle({}, {50: context})[0]
        self.assertEqual("HIGH_RISK", event["classification"]); self.assertEqual("high", event["severity"])
        stored = service.database.list_events(1)[0]
        self.assertEqual("C:/Tools/tool.exe", stored["path"]); self.assertEqual(10, stored["parent_pid"])
        self.assertEqual("tool.exe -enc payload", stored["command_line"]); self.assertEqual("CN=Vendor", stored["signer"])
        active = DataAdapter(self.root / "events.db", self.config).active_processes()[0]
        self.assertEqual("parent.exe", active["parent_process_name"])
        self.assertEqual([event["rule_id"]], [finding["rule_id"] for finding in active["matched_rules"]])

    def test_missing_metadata_remains_normal_and_zero_risk(self):
        service = TelemetryRelayService(config_path=self.config)
        event = service.run_cycle({}, {60: {"pid": 60, "process_name": "plain.exe"}})[0]
        self.assertEqual("NORMAL", event["classification"]); self.assertEqual("info", event["severity"])
        self.assertEqual(0, event["risk_score"]); self.assertIsNone(event["command_line"])


if __name__ == "__main__": unittest.main()
