#!/usr/bin/env python3
"""
Import RTL_433 Protocol Database

Expands GigLez protocol database from 18 → 200+ by importing RTL_433's open-source
protocol definitions.

Source: data/rtl_433_protocols.json (286 devices)
Output: src/matcher/rtl433_protocols_imported.py
"""

import json
from pathlib import Path
from typing import Dict, List
from collections import defaultdict


def load_rtl433_json() -> Dict:
    """Load RTL_433 protocol database JSON"""
    json_path = Path(__file__).parent.parent / "data" / "rtl_433_protocols.json"

    with open(json_path) as f:
        data = json.load(f)

    print(f"✓ Loaded {data['total_devices']} devices from RTL_433 database")
    return data


def estimate_frequency(device: Dict) -> int:
    """
    Estimate frequency for devices with null frequency field

    Based on:
      - Category (weather = 433MHz, automotive = 315MHz)
      - Manufacturer patterns
      - Default ISM bands
    """
    freq = device.get('frequency')
    if freq:
        return freq

    # Heuristics based on category and manufacturer
    category = (device.get('category') or '').lower()
    manufacturer = (device.get('manufacturer') or '').lower()
    name = (device.get('name') or '').lower()

    # Automotive TPMS → 315 MHz (North America) or 433 MHz (Europe)
    if category == 'automotive' or 'tpms' in name:
        return 315000000  # Default to NA frequency

    # Security systems → 433 MHz (most common)
    if category == 'security' or 'alarm' in name or 'security' in name:
        return 433920000

    # Doorbells → 433 MHz
    if 'doorbell' in name or 'bell' in name:
        return 433920000

    # Garage door openers → 315 MHz (NA) or 433 MHz
    if 'garage' in name or 'door' in name:
        # Chamberlain/LiftMaster = 315 MHz
        if 'chamberlain' in manufacturer or 'liftmaster' in manufacturer:
            return 315000000
        return 433920000

    # Weather sensors → 433 MHz (most common globally)
    if category == 'weather' or any(kw in name for kw in ['temperature', 'humidity', 'rain', 'wind', 'sensor']):
        # Some exceptions: Acurite (915 MHz for 5n1)
        if 'acurite' in manufacturer and '5n1' in name:
            return 915000000
        return 433920000

    # Default: 433.92 MHz (most popular ISM band)
    return 433920000


def estimate_encoding(device: Dict) -> str:
    """
    Estimate encoding scheme from modulation and name patterns

    Common mappings:
      - OOK → PWM (most common)
      - FSK → Manchester or custom
      - ASK → PWM
    """
    modulation = (device.get('modulation') or 'OOK').upper()
    name = (device.get('name') or '').lower()

    # Manchester encoding indicators
    if 'manchester' in name or 'oregon' in name:
        return 'Encoding.MANCHESTER'

    # Differential Manchester (rare)
    if 'diff' in name and 'manchester' in name:
        return 'Encoding.DIFFERENTIAL_MANCHESTER'

    # Default based on modulation
    if modulation in ['OOK', 'ASK']:
        return 'Encoding.PWM'
    elif modulation == 'FSK':
        return 'Encoding.MANCHESTER'  # FSK often uses Manchester

    return 'Encoding.PWM'  # Safe default


def estimate_bit_counts(device: Dict) -> tuple:
    """
    Estimate min/max bit counts from device characteristics

    Based on:
      - Typical payload sizes for device categories
      - Manufacturer patterns
    """
    category = (device.get('category') or '').lower()
    name = (device.get('name') or '').lower()

    # Weather sensors: typically 32-72 bits
    if category == 'weather':
        if 'oregon' in name:
            return (64, 128)  # Oregon Scientific uses longer messages
        return (32, 72)

    # Remotes/garage openers: typically 24-40 bits
    if 'remote' in name or 'garage' in name:
        return (24, 40)

    # TPMS: typically 64-80 bits
    if 'tpms' in name:
        return (64, 80)

    # Security sensors: 32-64 bits
    if category == 'security':
        return (32, 64)

    # Default
    return (24, 64)


def estimate_pulse_count(short_width: int, long_width: int, avg_bits: int) -> int:
    """
    Estimate typical pulse count for a transmission

    Formula: pulses ≈ 2 * bits (for PWM: each bit = HIGH + LOW pulse)
              + preamble overhead (~20-40 pulses)
    """
    base_pulses = avg_bits * 2  # Each bit = 2 transitions
    preamble_overhead = 30  # Typical preamble length

    return base_pulses + preamble_overhead


def convert_to_protocol_signature(device: Dict) -> Dict:
    """
    Convert RTL_433 device format to GigLez ProtocolSignature

    Input (RTL_433):
      {
        "device_id": "acurite_th",
        "name": "Acurite 609TXC Temperature and Humidity Sensor",
        "modulation": "OOK",
        "short_width": 1000,
        "long_width": 2000,
        "gap_limit": 3000,
        "reset_limit": 10000,
        "frequency": null,
        "category": "weather",
        "manufacturer": "Acurite"
      }

    Output (GigLez):
      ProtocolSignature(
        name="Acurite 609TXC Temperature and Humidity Sensor",
        category="Weather Sensor",
        manufacturer="Acurite",
        modulation=Modulation.OOK,
        encoding=Encoding.PWM,
        short_pulse_us=1000,
        long_pulse_us=2000,
        frequency=433920000,
        ...
      )
    """
    # Extract basic fields
    name = device.get('name', 'Unknown Device')
    manufacturer = device.get('manufacturer', 'Unknown')
    category = device.get('category', 'unknown').title()

    # Map modulation
    modulation_map = {
        'OOK': 'Modulation.OOK',
        'FSK': 'Modulation.FSK',
        'ASK': 'Modulation.ASK'
    }
    modulation = modulation_map.get(device.get('modulation', 'OOK'), 'Modulation.OOK')

    # Timing parameters
    short_width = device.get('short_width') or 500
    long_width = device.get('long_width') or 1000

    # Frequency estimation
    frequency = estimate_frequency(device)

    # Encoding estimation
    encoding = estimate_encoding(device)

    # Bit count estimation
    min_bits, max_bits = estimate_bit_counts(device)
    avg_bits = (min_bits + max_bits) // 2

    # Pulse count estimation
    typical_pulse_count = estimate_pulse_count(short_width, long_width, avg_bits)

    # Preamble/sync patterns (if detectable from name)
    preamble_pattern = None
    sync_pattern = None

    if 'oregon' in name.lower():
        preamble_pattern = '"1010" * 12'  # Oregon Scientific preamble
        sync_pattern = '"1000"'
    elif 'princeton' in name.lower() or 'pt2262' in name.lower():
        preamble_pattern = '"1" * 4'
        sync_pattern = '"10"'

    return {
        'name': name,
        'category': category,
        'manufacturer': manufacturer,
        'modulation': modulation,
        'encoding': encoding,
        'short_pulse_us': short_width,
        'long_pulse_us': long_width,
        'frequency': frequency,
        'frequency_tolerance': 100000,  # ±100 kHz
        'min_bits': min_bits,
        'max_bits': max_bits,
        'typical_pulse_count': typical_pulse_count,
        'preamble_pattern': preamble_pattern,
        'sync_pattern': sync_pattern,
        'timing_tolerance': 0.25,  # ±25% (more lenient than hand-curated)
        'min_confidence': 0.5,  # Lower threshold for imported protocols
        'source': f"RTL_433 (device_id: {device.get('device_id', 'unknown')})"
    }


def filter_valid_protocols(protocols: List[Dict]) -> List[Dict]:
    """
    Filter out invalid/incomplete protocol definitions

    Requirements:
      - Must have valid timing (short_width and long_width > 0)
      - Must have reasonable values (not extreme outliers)
      - Prefer unique entries (deduplicate by name)
    """
    valid = []
    seen_names = set()

    for proto in protocols:
        # Check timing validity
        if proto['short_pulse_us'] <= 0 or proto['long_pulse_us'] <= 0:
            continue

        # Check reasonable ranges (10μs to 10ms)
        if not (10 <= proto['short_pulse_us'] <= 10000):
            continue
        if not (10 <= proto['long_pulse_us'] <= 10000):
            continue

        # Check long > short (PWM assumption)
        if proto['long_pulse_us'] <= proto['short_pulse_us']:
            # Swap if reversed
            proto['short_pulse_us'], proto['long_pulse_us'] = \
                proto['long_pulse_us'], proto['short_pulse_us']

        # Deduplicate by name
        if proto['name'] in seen_names:
            continue

        seen_names.add(proto['name'])
        valid.append(proto)

    return valid


def generate_python_code(protocols: List[Dict]) -> str:
    """
    Generate Python code for rtl433_protocols_imported.py

    Creates a list of ProtocolSignature objects that can be imported
    into protocol_database.py
    """
    # Group by category for organization
    by_category = defaultdict(list)
    for proto in protocols:
        by_category[proto['category']].append(proto)

    code = '''#!/usr/bin/env python3
"""
RTL_433 Imported Protocol Signatures

Auto-generated from RTL_433 open-source protocol database.
Source: data/rtl_433_protocols.json ({total} devices)

DO NOT EDIT MANUALLY - run scripts/import_rtl433_protocols.py to regenerate.

Generated: {date}
"""

from src.matcher.protocol_database import ProtocolSignature, Modulation, Encoding

# Imported RTL_433 Protocols ({count} total)
RTL433_PROTOCOLS = [
'''.format(
        total=len(protocols),
        count=len(protocols),
        date=__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    )

    # Generate entries by category
    for category in sorted(by_category.keys()):
        protos = by_category[category]
        code += f'\n    # {category} ({len(protos)} devices)\n'

        for proto in protos:
            code += f'''    ProtocolSignature(
        name="{proto['name']}",
        category="{proto['category']}",
        manufacturer="{proto['manufacturer']}",
        modulation={proto['modulation']},
        encoding={proto['encoding']},
        short_pulse_us={proto['short_pulse_us']},
        long_pulse_us={proto['long_pulse_us']},
        frequency={proto['frequency']},
        frequency_tolerance={proto['frequency_tolerance']},
        min_bits={proto['min_bits']},
        max_bits={proto['max_bits']},
        typical_pulse_count={proto['typical_pulse_count']},
'''

            if proto['preamble_pattern']:
                code += f"        preamble_pattern={proto['preamble_pattern']},\n"
            if proto['sync_pattern']:
                code += f"        sync_pattern={proto['sync_pattern']},\n"

            code += f'''        timing_tolerance={proto['timing_tolerance']},
        min_confidence={proto['min_confidence']},
    ),
'''

    code += ''']

# Export for use in protocol_database.py
__all__ = ['RTL433_PROTOCOLS']
'''

    return code


def update_protocol_database():
    """
    Update protocol_database.py to include RTL433_PROTOCOLS
    """
    db_path = Path(__file__).parent.parent / "src" / "matcher" / "protocol_database.py"

    with open(db_path, 'r') as f:
        original_code = f.read()

    # Check if already updated
    if 'RTL433_PROTOCOLS' in original_code:
        print("⚠ protocol_database.py already includes RTL433_PROTOCOLS")
        return

    # Find the ALL_PROTOCOLS definition and update it
    old_all_protocols = '''# Compile all protocols into single list
ALL_PROTOCOLS = (
    WEATHER_SENSORS +
    GARAGE_DOOR_OPENERS +
    DOORBELLS +
    TIRE_PRESSURE +
    SECURITY_SENSORS +
    REMOTE_CONTROLS
)'''

    new_all_protocols = '''# Import RTL_433 protocols
try:
    from src.matcher.rtl433_protocols_imported import RTL433_PROTOCOLS
except ImportError:
    print("Warning: RTL_433 protocols not imported yet. Run scripts/import_rtl433_protocols.py")
    RTL433_PROTOCOLS = []

# Compile all protocols into single list
ALL_PROTOCOLS = (
    WEATHER_SENSORS +
    GARAGE_DOOR_OPENERS +
    DOORBELLS +
    TIRE_PRESSURE +
    SECURITY_SENSORS +
    REMOTE_CONTROLS +
    RTL433_PROTOCOLS  # Imported from RTL_433 database
)'''

    updated_code = original_code.replace(old_all_protocols, new_all_protocols)

    with open(db_path, 'w') as f:
        f.write(updated_code)

    print(f"✓ Updated {db_path} to include RTL433_PROTOCOLS")


def main():
    """Main import workflow"""
    print("=== RTL_433 Protocol Database Import ===\n")

    # Step 1: Load RTL_433 JSON
    data = load_rtl433_json()
    devices = data['devices']

    # Step 2: Convert to GigLez format
    print(f"\n📝 Converting {len(devices)} devices to ProtocolSignature format...")
    protocols = [convert_to_protocol_signature(dev) for dev in devices]

    # Step 3: Filter valid entries
    print(f"🔍 Filtering valid protocols...")
    valid_protocols = filter_valid_protocols(protocols)
    print(f"✓ Kept {len(valid_protocols)} valid protocols (removed {len(protocols) - len(valid_protocols)} invalid)")

    # Step 4: Generate Python code
    print(f"\n📄 Generating Python code...")
    code = generate_python_code(valid_protocols)

    # Step 5: Write to file
    output_path = Path(__file__).parent.parent / "src" / "matcher" / "rtl433_protocols_imported.py"
    with open(output_path, 'w') as f:
        f.write(code)

    print(f"✓ Written to {output_path}")

    # Step 6: Update protocol_database.py
    print(f"\n🔧 Updating protocol_database.py...")
    update_protocol_database()

    # Step 7: Summary statistics
    print(f"\n=== Import Summary ===")
    print(f"Total RTL_433 devices: {len(devices)}")
    print(f"Valid protocols imported: {len(valid_protocols)}")

    # By category
    from collections import Counter
    categories = Counter(p['category'] for p in valid_protocols)
    print(f"\nProtocols by category:")
    for cat, count in categories.most_common():
        print(f"  {cat}: {count}")

    # By frequency
    frequencies = Counter(p['frequency'] for p in valid_protocols)
    print(f"\nProtocols by frequency:")
    for freq, count in sorted(frequencies.items()):
        freq_mhz = freq / 1_000_000
        print(f"  {freq_mhz:.2f} MHz: {count}")

    print(f"\n✅ Import complete!")
    print(f"   Original protocols: 18")
    print(f"   Imported protocols: {len(valid_protocols)}")
    print(f"   Total protocols: {18 + len(valid_protocols)}")

    # Verify import
    print(f"\n🧪 Verifying import...")
    try:
        from src.matcher.protocol_database import get_protocol_database
        db = get_protocol_database()
        stats = db.get_statistics()
        print(f"✓ Database loaded successfully")
        print(f"  Total protocols in database: {stats['total_protocols']}")
    except Exception as e:
        print(f"❌ Error loading database: {e}")


if __name__ == '__main__':
    main()
