"""Tests for crowdstrike/aggregator.py -- end-to-end aggregation."""

from astrix_openclaw_scanner.crowdstrike.aggregator import aggregate_events
from tests.conftest import (
    CRWD_CHROME_NOISE_EVENT,
    CRWD_MAC_EVENT,
    CRWD_OPENCLAW_EVENT,
    CRWD_SPARSE_EVENT,
    to_json_list,
)


class TestCrowdStrikeNormalization:
    """Verify CrowdStrike fields map to the common output format."""

    def test_field_mapping(self):
        result = aggregate_events(to_json_list(CRWD_OPENCLAW_EVENT))
        assert len(result) == 1
        row = result[0]
        assert row["endpoint"] == "WS-PC01"
        assert row["username"] == "jsmith"
        assert row["os"] == "Windows"  # "Win" mapped
        assert "Feb 04" in row["last_seen"]
        # Commandline coalesced: CommandLine is first non-empty
        assert "openclaw-agent.exe" in row["commandline"]

    def test_mac_os_mapping(self):
        result = aggregate_events(to_json_list(CRWD_MAC_EVENT))
        assert result[0]["os"] == "macOS"

    def test_epoch_ms_timestamp_parsed(self):
        result = aggregate_events(to_json_list(CRWD_OPENCLAW_EVENT))
        # 1738684800000 == 2025-02-04T16:00:00Z
        assert "2025" in result[0]["last_seen"]


class TestCrowdStrikeFiltering:
    """Verify noise removal and the skip_filter flag."""

    def test_noise_removed(self):
        events = to_json_list(CRWD_OPENCLAW_EVENT, CRWD_CHROME_NOISE_EVENT)
        result = aggregate_events(events)
        assert len(result) == 1
        assert result[0]["endpoint"] == "WS-PC01"
        # Chrome noise is gone -- only the openclaw event's commandline remains
        assert "chrome" not in result[0]["commandline"].lower()

    def test_skip_filter_keeps_noise(self):
        events = to_json_list(CRWD_OPENCLAW_EVENT, CRWD_CHROME_NOISE_EVENT)
        result = aggregate_events(events, skip_filter=True)
        # Both events are from same (endpoint, username) so they merge into 1 row,
        # but the key test is that we don't lose events -- the chrome event has a
        # later timestamp so its commandline would win the merge.
        assert len(result) == 1
        assert "chrome" in result[0]["commandline"].lower()

    def test_safe_fallback_on_bad_data(self):
        """An event that causes extraction errors should NOT be filtered out."""
        bad_event = {
            **CRWD_OPENCLAW_EVENT,
            # Use a numeric value that will cause shlex/pathlib to fail
            "ImageFileName": 12345,
            "FilePath": 12345,
            "CommandLine": 12345,
            "ParentBaseFileName": 12345,
        }
        result = aggregate_events(to_json_list(bad_event))
        # Event should still appear (safe fallback keeps it)
        assert len(result) == 1


class TestCrowdStrikeMissingFields:
    """Verify graceful handling of missing/absent fields."""

    def test_sparse_event_defaults_to_unknown(self):
        result = aggregate_events(to_json_list(CRWD_SPARSE_EVENT))
        assert len(result) == 1
        row = result[0]
        assert row["endpoint"] == "WS-PC99"
        assert row["username"] == "Unknown"  # UserName absent from event
        assert row["os"] == "Windows"  # event_platform "Win" mapped


class TestCrowdStrikeGrouping:
    """Verify deduplication across multiple events."""

    def test_same_endpoint_username_grouped(self):
        # Two events from same (WS-PC01, jsmith) with different timestamps
        early = {**CRWD_OPENCLAW_EVENT, "@timestamp": 1738684800000}
        late = {**CRWD_OPENCLAW_EVENT, "@timestamp": 1738771200000, "CommandLine": "agent --v2"}
        result = aggregate_events(to_json_list(early, late))
        assert len(result) == 1
        # Latest timestamp's commandline wins
        assert result[0]["commandline"] == "agent --v2"

    def test_two_users_same_endpoint(self):
        """Two users on the same machine should produce two separate rows."""
        user_a = {**CRWD_OPENCLAW_EVENT, "UserName": "alice"}
        user_b = {**CRWD_OPENCLAW_EVENT, "UserName": "bob", "@timestamp": 1738684900000}
        result = aggregate_events(to_json_list(user_a, user_b))
        assert len(result) == 2
        usernames = {r["username"] for r in result}
        assert usernames == {"alice", "bob"}
        # Both rows share the same endpoint
        assert all(r["endpoint"] == "WS-PC01" for r in result)

    def test_different_endpoints_not_grouped(self):
        events = to_json_list(CRWD_OPENCLAW_EVENT, CRWD_MAC_EVENT)
        result = aggregate_events(events)
        assert len(result) == 2
