"""Tests for the Signature Extractor."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone

import pytest

from navil.honeypot.signature_extractor import SignatureExtractor


# ── Helpers ──────────────────────────────────────────────────


def _make_record(tool_name: str, source_ip: str = "1.2.3.4", arguments: dict = None, ts_offset_s: int = 0) -> dict:
    """Create a test honeypot record."""
    ts = datetime.now(timezone.utc) + timedelta(seconds=ts_offset_s)
    return {
        "timestamp": ts.isoformat(),
        "tool_name": tool_name,
        "arguments": arguments or {},
        "source_ip": source_ip,
        "request_headers": {},
        "method": "tools/call",
    }


# ── Signature Extraction ────────────────────────────────────


class TestSignatureExtractor:
    """Tests for the signature extractor."""

    def test_empty_records(self):
        extractor = SignatureExtractor()
        entries = extractor.analyze([])
        assert len(entries) == 0

    def test_extract_suspicious_tool_names(self):
        """Should detect tools with suspicious keywords."""
        records = [
            _make_record("inject_backdoor", ts_offset_s=i)
            for i in range(5)
        ]
        extractor = SignatureExtractor()
        entries = extractor.analyze(records)

        tool_entries = [e for e in entries if e.pattern_type == "tool_name"]
        assert len(tool_entries) >= 1
        assert any(e.value == "inject_backdoor" for e in tool_entries)

    def test_minimum_observations_enforced(self):
        """Should not create entries for tools seen fewer than MIN_OBSERVATIONS times."""
        records = [_make_record("inject_backdoor")]  # Only 1 observation
        extractor = SignatureExtractor()
        entries = extractor.analyze(records)

        tool_entries = [e for e in entries if e.value == "inject_backdoor"]
        assert len(tool_entries) == 0

    def test_extract_sequence_patterns(self):
        """Should detect suspicious tool call sequences."""
        records = []
        # Create 4 IPs each doing read_file -> exec_command
        for i in range(4):
            ip = f"10.0.0.{i}"
            records.append(_make_record("read_file", ip, ts_offset_s=i * 10))
            records.append(_make_record("exec_command", ip, ts_offset_s=i * 10 + 1))

        extractor = SignatureExtractor()
        entries = extractor.analyze(records)

        seq_entries = [e for e in entries if e.pattern_type == "tool_sequence"]
        assert len(seq_entries) >= 1

    def test_extract_argument_patterns(self):
        """Should detect sensitive argument patterns."""
        records = [
            _make_record("read_file", arguments={"path": "~/.ssh/id_rsa"}, ts_offset_s=i)
            for i in range(5)
        ]
        extractor = SignatureExtractor()
        entries = extractor.analyze(records)

        arg_entries = [e for e in entries if e.pattern_type == "argument_pattern"]
        assert len(arg_entries) >= 1

    def test_confidence_threshold(self):
        """All extracted entries should meet minimum confidence threshold."""
        records = [
            _make_record("inject_backdoor", ts_offset_s=i)
            for i in range(5)
        ]
        extractor = SignatureExtractor(min_confidence=0.7)
        entries = extractor.analyze(records)

        for entry in entries:
            assert entry.confidence >= 0.7, (
                f"Entry {entry.pattern_id} has confidence {entry.confidence} < 0.7"
            )

    def test_custom_confidence_threshold(self):
        """Should respect custom minimum confidence threshold."""
        records = [
            _make_record("inject_backdoor", ts_offset_s=i)
            for i in range(5)
        ]
        # Very high threshold
        extractor = SignatureExtractor(min_confidence=0.99)
        entries = extractor.analyze(records)
        # Some entries might still pass if they have very high confidence
        for entry in entries:
            assert entry.confidence >= 0.99

    def test_unique_pattern_ids(self):
        """All generated pattern IDs should be unique."""
        records = []
        for tool in ["inject_backdoor", "exfil_data", "keylogger"]:
            records.extend(_make_record(tool, ts_offset_s=i) for i in range(5))

        extractor = SignatureExtractor()
        entries = extractor.analyze(records)

        ids = [e.pattern_id for e in entries]
        assert len(ids) == len(set(ids)), "Duplicate pattern IDs found"

    def test_entries_are_blocklist_entries(self):
        """All results should be BlocklistEntry instances."""
        from navil.blocklist import BlocklistEntry

        records = [
            _make_record("inject_backdoor", ts_offset_s=i)
            for i in range(5)
        ]
        extractor = SignatureExtractor()
        entries = extractor.analyze(records)

        for entry in entries:
            assert isinstance(entry, BlocklistEntry)


# ── Timing Patterns ──────────────────────────────────────────


class TestTimingPatterns:
    """Tests for timing pattern analysis."""

    def test_periodic_pattern_detection(self):
        """Should detect periodic call patterns."""
        records = [
            _make_record("status", source_ip="10.0.0.1", ts_offset_s=i * 10)
            for i in range(10)
        ]
        extractor = SignatureExtractor()
        timing = extractor.extract_timing_patterns(records)

        assert "10.0.0.1" in timing
        info = timing["10.0.0.1"]
        assert info["call_count"] == 10
        assert info["is_periodic"] is True

    def test_non_periodic_pattern(self):
        """Should detect non-periodic patterns."""
        import random
        random.seed(42)
        records = [
            _make_record("tool", source_ip="10.0.0.2", ts_offset_s=random.randint(0, 1000))
            for _ in range(10)
        ]
        extractor = SignatureExtractor()
        timing = extractor.extract_timing_patterns(records)

        if "10.0.0.2" in timing:
            # Random intervals should not be periodic
            assert timing["10.0.0.2"]["is_periodic"] is False

    def test_insufficient_records(self):
        """Should skip IPs with fewer than 3 records."""
        records = [
            _make_record("tool", source_ip="10.0.0.3", ts_offset_s=0),
            _make_record("tool", source_ip="10.0.0.3", ts_offset_s=10),
        ]
        extractor = SignatureExtractor()
        timing = extractor.extract_timing_patterns(records)
        assert "10.0.0.3" not in timing
