#!/usr/bin/env python3
"""
Unit Tests for Preamble Detection and Frequency Fingerprinting
"""

import pytest
from src.matcher.preamble_detector import PreambleDetector, PreamblePattern
from src.matcher.frequency_fingerprint import FrequencyFingerprinter


class TestPreambleDetection:
    """Test preamble detection algorithms"""

    def test_long_burst_detection(self):
        """Test 1: Detect long HIGH burst preamble (Princeton style)"""
        detector = PreambleDetector()

        # Long burst: 4000us followed by normal pulses (need at least 10 pulses)
        pulses = [4000, -500, 500, -500, 1000, -500, 500, -500, 1000, -500, 500, -500]

        pattern = detector.detect(pulses, timing_short=500, timing_long=1000)

        assert pattern is not None, "Should detect long burst"
        assert pattern.type == 'long_burst', f"Expected long_burst, got {pattern.type}"
        assert pattern.confidence > 0.7, f"Expected confidence >0.7, got {pattern.confidence}"

    def test_alternating_detection(self):
        """Test 2: Detect alternating 01010101 pattern (Manchester/Oregon)"""
        detector = PreambleDetector()

        # Alternating pattern: 500us/1000us pulses
        # Pattern: 01010101 (8 alternations)
        pulses = [
            500, -500,   # 0
            1000, -500,  # 1
            500, -500,   # 0
            1000, -500,  # 1
            500, -500,   # 0
            1000, -500,  # 1
            500, -500,   # 0
            1000, -500,  # 1
        ] * 2  # Repeat for emphasis

        pattern = detector.detect(pulses, timing_short=500, timing_long=1000)

        assert pattern is not None, "Should detect alternating pattern"
        assert pattern.type == 'alternating', f"Expected alternating, got {pattern.type}"
        assert pattern.repetitions >= 4, f"Expected >=4 repetitions, got {pattern.repetitions}"

    def test_sync_word_detection(self):
        """Test 3: Detect known sync word (e.g., '1000' for Oregon Scientific)"""
        detector = PreambleDetector()

        # Sync word "1000" (need at least 10 pulses)
        pulses = [
            1000, -500,  # 1
            500, -500,   # 0
            500, -500,   # 0
            500, -500,   # 0
            # Followed by data (add more to meet 10 pulse minimum)
            1000, -500,
            500, -500,
            1000, -500,
            500, -500,
        ]

        pattern = detector.detect(pulses, timing_short=500, timing_long=1000)

        assert pattern is not None, "Should detect sync word"
        assert pattern.type == 'sync_word', f"Expected sync_word, got {pattern.type}"
        assert '1000' in pattern.pattern, "Pattern should contain '1000'"

    def test_no_preamble(self):
        """Test 4: Handle signals without clear preamble"""
        detector = PreambleDetector()

        # Random-looking pattern
        pulses = [600, -400, 800, -600, 550, -450, 900, -500]

        pattern = detector.detect(pulses, timing_short=500, timing_long=1000)

        # May or may not detect - should not crash
        assert True, "Should handle no preamble gracefully"

    def test_preamble_matching_exact(self):
        """Test 5: Exact preamble match scoring"""
        detector = PreambleDetector()

        # Detected alternating pattern
        detected = PreamblePattern(
            type='alternating',
            pattern='10101010',
            length=8,
            repetitions=4,
            confidence=0.9,
            details={}
        )

        # Protocol expects alternating
        match = detector.match_against_protocol(
            detected,
            protocol_preamble='"10" * 4',  # "10101010"
            protocol_sync=None
        )

        assert match.match_type == 'exact', f"Expected exact match, got {match.match_type}"
        assert match.similarity >= 0.9, f"Expected similarity >=0.9, got {match.similarity}"

    def test_preamble_matching_partial(self):
        """Test 6: Partial preamble match (type similarity)"""
        detector = PreambleDetector()

        detected = PreamblePattern(
            type='alternating',
            pattern='0101',
            length=4,
            repetitions=2,
            confidence=0.8,
            details={}
        )

        # Protocol also uses alternating (but different exact pattern)
        match = detector.match_against_protocol(
            detected,
            protocol_preamble='"01" * 8',
            protocol_sync=None
        )

        # Should match type even if exact pattern differs
        assert match.similarity >= 0.5, f"Expected partial match, got {match.similarity}"

    def test_preamble_mismatch(self):
        """Test 7: Preamble mismatch penalty"""
        detector = PreambleDetector()

        detected = PreamblePattern(
            type='long_burst',
            pattern='1111',
            length=4,
            repetitions=1,
            confidence=0.8,
            details={}
        )

        # Protocol expects alternating (mismatch)
        match = detector.match_against_protocol(
            detected,
            protocol_preamble='"10" * 4',
            protocol_sync=None
        )

        assert match.similarity < 0.5, f"Expected low similarity for mismatch, got {match.similarity}"


class TestFrequencyFingerprinting:
    """Test frequency-based filtering and scoring"""

    def test_ism_band_detection_433mhz(self):
        """Test 8: Detect 433 MHz ISM band"""
        fingerprinter = FrequencyFingerprinter()

        freq = 433920000  # 433.92 MHz

        fingerprint = fingerprinter.analyze(freq)

        assert fingerprint.ism_band == '433MHz', f"Expected 433MHz band, got {fingerprint.ism_band}"
        assert fingerprint.center_freq == freq
        assert fingerprint.confidence > 0.8

    def test_ism_band_detection_315mhz(self):
        """Test 9: Detect 315 MHz ISM band"""
        fingerprinter = FrequencyFingerprinter()

        freq = 315000000  # 315 MHz

        fingerprint = fingerprinter.analyze(freq)

        assert fingerprint.ism_band == '315MHz', f"Expected 315MHz band, got {fingerprint.ism_band}"

    def test_frequency_exact_match(self):
        """Test 10: Perfect frequency match scoring"""
        fingerprinter = FrequencyFingerprinter()

        match = fingerprinter.score_frequency_match(
            signal_frequency=433920000,
            protocol_frequency=433920000,
            protocol_tolerance=100000
        )

        assert match.score == 1.0, f"Expected score 1.0 for exact match, got {match.score}"
        assert match.within_tolerance is True
        assert match.frequency_error == 0

    def test_frequency_within_tolerance(self):
        """Test 11: Frequency match within tolerance"""
        fingerprinter = FrequencyFingerprinter()

        # 50 kHz offset (within ±100 kHz tolerance)
        match = fingerprinter.score_frequency_match(
            signal_frequency=433970000,
            protocol_frequency=433920000,
            protocol_tolerance=100000
        )

        assert match.within_tolerance is True
        assert match.score >= 0.8, f"Expected score >=0.8 within tolerance, got {match.score}"

    def test_frequency_outside_tolerance(self):
        """Test 12: Frequency mismatch outside tolerance"""
        fingerprinter = FrequencyFingerprinter()

        # 500 kHz offset (outside ±100 kHz tolerance)
        match = fingerprinter.score_frequency_match(
            signal_frequency=434420000,
            protocol_frequency=433920000,
            protocol_tolerance=100000
        )

        assert match.within_tolerance is False
        assert match.score < 0.6, f"Expected low score outside tolerance, got {match.score}"

    def test_frequency_filtering(self):
        """Test 13: Filter protocols by frequency"""
        fingerprinter = FrequencyFingerprinter()

        # Mock protocols
        class MockProtocol:
            def __init__(self, name, freq):
                self.name = name
                self.frequency = freq
                self.frequency_tolerance = 100000

        protocols = [
            MockProtocol('433MHz Device', 433920000),
            MockProtocol('315MHz Device', 315000000),
            MockProtocol('868MHz Device', 868000000),
            MockProtocol('433MHz Device 2', 433850000),  # Close to 433.92
        ]

        # Filter for 433 MHz signal
        filtered = fingerprinter.filter_protocols_by_frequency(
            protocols,
            signal_frequency=433920000,
            tolerance_hz=200000
        )

        # Should get both 433 MHz devices
        assert len(filtered) == 2, f"Expected 2 protocols, got {len(filtered)}"
        assert all('433MHz' in p.name for p in filtered)

    def test_protocol_grouping_by_band(self):
        """Test 14: Group protocols by ISM band"""
        fingerprinter = FrequencyFingerprinter()

        class MockProtocol:
            def __init__(self, name, freq):
                self.name = name
                self.frequency = freq

        protocols = [
            MockProtocol('Dev1', 433920000),
            MockProtocol('Dev2', 315000000),
            MockProtocol('Dev3', 433850000),
            MockProtocol('Dev4', 868000000),
        ]

        grouped = fingerprinter.group_protocols_by_band(protocols)

        assert '433MHz' in grouped
        assert '315MHz' in grouped
        assert '868MHz' in grouped
        assert len(grouped['433MHz']) == 2, "Should have 2 protocols in 433MHz band"

    def test_band_statistics(self):
        """Test 15: Get protocol count per band"""
        fingerprinter = FrequencyFingerprinter()

        class MockProtocol:
            def __init__(self, freq):
                self.frequency = freq

        protocols = [
            MockProtocol(433920000),
            MockProtocol(433850000),
            MockProtocol(315000000),
        ]

        stats = fingerprinter.get_band_statistics(protocols)

        assert stats.get('433MHz', 0) == 2
        assert stats.get('315MHz', 0) == 1


if __name__ == '__main__':
    pytest.main([__file__, '-v'])
