#!/usr/bin/env python3
"""
Import RF Device Signatures into Database

Imports device signatures from downloaded datasets:
1. Flipper Zero .sub file collections (with automatic labeling from file paths)
2. RTL_433 protocol definitions
3. Creates devices and signatures records for matching engine

Usage:
    python3 scripts/import_signatures_to_db.py --flipper --rtl433 --limit 10000
"""

import sys
import os
import json
import re
import hashlib
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass

# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from sqlalchemy.orm import Session
from loguru import logger

from src.database.connection import get_session
from src.database.models import Device, Signature, FlipperSignature
from src.parser.sub_parser import parse_sub_file
from src.parser.metadata import SignalMetadata

# Compatibility alias
SessionLocal = get_session

# =============================================================================
# CONFIGURATION
# =============================================================================

DATA_DIR = Path(__file__).parent.parent / "data"

FLIPPER_DATASETS = [
    DATA_DIR / "rf_test_datasets" / "UberGuidoZ_Flipper",
    DATA_DIR / "test_known_devices",
]

RTL433_REPO = DATA_DIR / "test_rtl433_real"

# Device type mapping from file paths
DEVICE_TYPE_PATTERNS = {
    r"weather[_\s]?station": "Weather Sensor",
    r"garage[_\s]?door": "Garage Door Opener",
    r"gate[_\s]?opener": "Gate Opener",
    r"doorbell": "Doorbell",
    r"door[_\s]?sensor": "Door Sensor",
    r"motion[_\s]?sensor": "Motion Sensor",
    r"smoke[_\s]?detector": "Smoke Detector",
    r"remote[_\s]?control": "Remote Control",
    r"car[_\s]?key": "Car Key Fob",
    r"tpms": "TPMS",
    r"tire[_\s]?pressure": "TPMS",
    r"security": "Security System",
    r"alarm": "Alarm System",
}

MANUFACTURER_PATTERNS = {
    r"chamberlain": "Chamberlain",
    r"liftmaster": "LiftMaster",
    r"genie": "Genie",
    r"linear": "Linear",
    r"oregon[_\s]?scientific": "Oregon Scientific",
    r"acurite": "AcuRite",
    r"lacrosse": "LaCrosse",
    r"ambient[_\s]?weather": "Ambient Weather",
    r"honeywell": "Honeywell",
    r"nest": "Nest",
    r"ring": "Ring",
    r"yale": "Yale",
    r"toyota": "Toyota",
    r"schrader": "Schrader",
    r"nice": "NICE",
    r"came": "CAME",
    r"bft": "BFT",
}


# =============================================================================
# DATA CLASSES
# =============================================================================

@dataclass
class ExtractedDeviceInfo:
    """Device information extracted from file path and content"""
    device_type: Optional[str] = None
    manufacturer: Optional[str] = None
    model: Optional[str] = None
    confidence: float = 0.5  # Confidence in automatic extraction


# =============================================================================
# FILE PATH ANALYSIS
# =============================================================================

def extract_device_info_from_path(file_path: Path) -> ExtractedDeviceInfo:
    """
    Extract device information from file path structure

    Example paths:
        - Weather_stations/Oregon_Scientific/THGN123N.sub
        - Garage_Door_Openers/Chamberlain/model_891LM.sub
        - UberGuidoZ/Sub-GHz/Vehicles/Tesla/Model_3_Charge_Port.sub
    """
    info = ExtractedDeviceInfo()

    path_str = str(file_path).lower()
    parts = file_path.parts

    # Extract device type
    for pattern, device_type in DEVICE_TYPE_PATTERNS.items():
        if re.search(pattern, path_str, re.IGNORECASE):
            info.device_type = device_type
            info.confidence = 0.7
            break

    # Extract manufacturer
    for pattern, manufacturer in MANUFACTURER_PATTERNS.items():
        if re.search(pattern, path_str, re.IGNORECASE):
            info.manufacturer = manufacturer
            info.confidence = 0.8
            break

    # Extract model from filename (heuristic: uppercase letters + numbers)
    filename = file_path.stem  # Without extension
    model_match = re.search(r'([A-Z0-9]{3,}[-_]?[A-Z0-9]*)', filename)
    if model_match:
        info.model = model_match.group(1).replace('_', ' ')
        info.confidence = max(info.confidence, 0.6)

    return info


# =============================================================================
# FLIPPER ZERO SIGNATURE IMPORT
# =============================================================================

def import_flipper_signatures(
    db: Session,
    limit: Optional[int] = None,
    skip_existing: bool = True
) -> Tuple[int, int, int]:
    """
    Import Flipper Zero .sub files as device signatures

    Returns:
        (devices_created, signatures_created, files_skipped)
    """
    devices_created = 0
    signatures_created = 0
    files_skipped = 0

    # Collect all .sub files
    sub_files: List[Path] = []
    for dataset_dir in FLIPPER_DATASETS:
        if not dataset_dir.exists():
            logger.warning(f"Dataset not found: {dataset_dir}")
            continue

        logger.info(f"Scanning: {dataset_dir}")
        sub_files.extend(dataset_dir.rglob("*.sub"))

    logger.info(f"Found {len(sub_files)} .sub files across all datasets")

    if limit:
        sub_files = sub_files[:limit]
        logger.info(f"Limited to {limit} files")

    # Process each file
    for idx, sub_file in enumerate(sub_files, 1):
        if idx % 100 == 0:
            logger.info(f"Progress: {idx}/{len(sub_files)} ({idx/len(sub_files)*100:.1f}%)")

        try:
            # Compute file hash for deduplication
            file_hash = hashlib.sha256(sub_file.read_bytes()).hexdigest()

            # Check if already imported
            if skip_existing:
                existing = db.query(FlipperSignature).filter_by(
                    file_hash=file_hash
                ).first()
                if existing:
                    files_skipped += 1
                    continue

            # Parse .sub file
            try:
                metadata = parse_sub_file(str(sub_file))
            except Exception as e:
                logger.debug(f"Parse failed: {sub_file.name} - {e}")
                files_skipped += 1
                continue

            # Extract device info from path
            device_info = extract_device_info_from_path(sub_file)

            # Skip if no useful information extracted
            if not device_info.device_type and not metadata.protocol:
                files_skipped += 1
                continue

            # Create or find device
            device_query = db.query(Device)

            if device_info.manufacturer and device_info.model:
                device_query = device_query.filter_by(
                    manufacturer=device_info.manufacturer,
                    model=device_info.model
                )
            elif metadata.protocol:
                # Use protocol as fallback identifier
                device_query = device_query.filter_by(
                    protocol=metadata.protocol,
                    typical_frequency=metadata.frequency
                )
            else:
                files_skipped += 1
                continue

            device = device_query.first()

            if not device:
                # Create new device
                device = Device(
                    manufacturer=device_info.manufacturer or "Unknown",
                    model=device_info.model or metadata.protocol or "Unknown",
                    device_type=device_info.device_type or "Unknown",
                    typical_frequency=metadata.frequency,
                    protocol=metadata.protocol,
                    bit_length=metadata.bit_length,
                    encoding=metadata.file_format if metadata.file_format else None,
                    source="flipper",
                    verified=False  # Needs community verification
                )
                db.add(device)
                db.flush()  # Get device ID
                devices_created += 1

            # Extract timing statistics for signature matching
            timing_min = None
            timing_max = None

            if metadata.timing_element:
                # KEY format: use timing_element with tolerance
                timing_min = int(metadata.timing_element * 0.8)
                timing_max = int(metadata.timing_element * 1.2)
            elif metadata.raw_data and len(metadata.raw_data) > 0:
                # RAW format: calculate from pulse statistics
                pulse_widths = [abs(p) for p in metadata.raw_data]
                if pulse_widths:
                    timing_min = int(min(pulse_widths) * 0.9)  # 10% tolerance
                    timing_max = int(max(pulse_widths) * 1.1)

            # Generate bit_mask for KEY format (all bits significant by default)
            bit_mask = None
            if metadata.key_data:
                # Create mask with all bits set to 1 (all bits are significant)
                bit_mask = bytes([0xFF] * len(metadata.key_data))

            # Create signature with enhanced metadata
            signature = Signature(
                device_id=device.id,
                protocol=metadata.protocol,
                frequency=metadata.frequency,
                modulation=str(metadata.modulation).replace("Modulation.", "") if metadata.modulation else None,
                bit_pattern=metadata.key_data if metadata.key_data else None,
                bit_mask=bit_mask,
                timing_min=timing_min,
                timing_max=timing_max,
                weight=device_info.confidence,
                source="flipper"
            )
            db.add(signature)
            signatures_created += 1

            # Create Flipper-specific record
            flipper_sig = FlipperSignature(
                signature_id=None,  # Will be set after signature is committed
                file_hash=file_hash,
                file_path=str(sub_file.relative_to(DATA_DIR)),
                preset=metadata.preset if hasattr(metadata, 'preset') else None,
                raw_format=metadata.file_format
            )
            db.add(flipper_sig)

            # Commit in batches
            if idx % 50 == 0:
                try:
                    db.commit()
                except Exception as e:
                    logger.debug(f"Batch commit error: {e}")
                    db.rollback()

        except Exception as e:
            logger.error(f"Error processing {sub_file}: {e}")
            db.rollback()  # Rollback after error to continue with next file
            files_skipped += 1
            continue

    # Final commit
    try:
        db.commit()
    except Exception as e:
        logger.debug(f"Final commit error: {e}")
        db.rollback()

    logger.info(f"Import complete:")
    logger.info(f"  - Devices created: {devices_created}")
    logger.info(f"  - Signatures created: {signatures_created}")
    logger.info(f"  - Files skipped: {files_skipped}")

    return devices_created, signatures_created, files_skipped


# =============================================================================
# RTL_433 PROTOCOL IMPORT
# =============================================================================

def import_rtl433_protocols(db: Session) -> Tuple[int, int]:
    """
    Import RTL_433 protocol definitions from test files

    RTL_433 test files include .json metadata with:
    - Protocol name and number
    - Expected output fields
    - Device information

    Returns:
        (devices_created, signatures_created)
    """
    devices_created = 0
    signatures_created = 0

    if not RTL433_REPO.exists():
        logger.warning(f"RTL_433 test repo not found: {RTL433_REPO}")
        return 0, 0

    # Find all .json files (contain protocol metadata)
    json_files = list(RTL433_REPO.rglob("*.json"))
    logger.info(f"Found {len(json_files)} RTL_433 test .json files")

    protocol_map = {}  # protocol_name -> metadata

    for json_file in json_files:
        try:
            with open(json_file, 'r') as f:
                data = json.load(f)

            # RTL_433 .json files contain decoded device information
            if isinstance(data, dict) and 'model' in data:
                model = data.get('model')
                protocol_id = data.get('protocol', None)

                # Extract device info
                manufacturer = None
                if '-' in model:
                    parts = model.split('-', 1)
                    manufacturer = parts[0].strip()
                    model_name = parts[1].strip()
                else:
                    model_name = model

                # Frequency from file path or metadata
                freq_match = re.search(r'(\d+)M', str(json_file))
                frequency = int(freq_match.group(1)) * 1000000 if freq_match else 433920000

                # Store protocol info
                key = (manufacturer or "Unknown", model_name)
                if key not in protocol_map:
                    protocol_map[key] = {
                        'manufacturer': manufacturer or "Unknown",
                        'model': model_name,
                        'frequency': frequency,
                        'protocol': model,
                        'protocol_id': protocol_id,
                        'device_type': 'Weather Sensor',  # Most RTL_433 devices
                        'example_data': data
                    }

        except Exception as e:
            logger.debug(f"Error parsing {json_file}: {e}")
            continue

    logger.info(f"Extracted {len(protocol_map)} unique RTL_433 protocols")

    # Create devices and signatures
    for (manufacturer, model_name), proto_info in protocol_map.items():
        # Check if device exists
        device = db.query(Device).filter_by(
            manufacturer=manufacturer,
            model=model_name
        ).first()

        if not device:
            device = Device(
                manufacturer=manufacturer,
                model=model_name,
                device_type=proto_info['device_type'],
                typical_frequency=proto_info['frequency'],
                protocol=proto_info['protocol'],
                source="rtl433",
                verified=True  # RTL_433 protocols are trusted
            )
            db.add(device)
            db.flush()
            devices_created += 1

        # Create signature
        signature = Signature(
            device_id=device.id,
            protocol=proto_info['protocol'],
            frequency=proto_info['frequency'],
            weight=0.95  # High confidence for RTL_433
        )
        db.add(signature)
        signatures_created += 1

    db.commit()

    logger.info(f"RTL_433 import complete:")
    logger.info(f"  - Devices created: {devices_created}")
    logger.info(f"  - Signatures created: {signatures_created}")

    return devices_created, signatures_created


# =============================================================================
# MAIN
# =============================================================================

def main():
    import argparse

    parser = argparse.ArgumentParser(description="Import RF device signatures into database")
    parser.add_argument("--flipper", action="store_true", help="Import Flipper Zero signatures")
    parser.add_argument("--rtl433", action="store_true", help="Import RTL_433 protocols")
    parser.add_argument("--limit", type=int, default=None, help="Limit number of Flipper files to process")
    parser.add_argument("--skip-existing", action="store_true", default=True, help="Skip already imported files")
    parser.add_argument("--all", action="store_true", help="Import all sources")

    args = parser.parse_args()

    if not any([args.flipper, args.rtl433, args.all]):
        parser.print_help()
        print("\nError: Must specify at least one import source (--flipper, --rtl433, or --all)")
        sys.exit(1)

    # Setup logging
    logger.remove()
    logger.add(sys.stderr, level="INFO")
    logger.add("logs/import_signatures.log", rotation="10 MB", level="DEBUG")

    logger.info("=" * 80)
    logger.info("RF Device Signature Import")
    logger.info("=" * 80)

    # Connect to database
    db = SessionLocal()

    try:
        # Import Flipper signatures
        if args.flipper or args.all:
            logger.info("\n[1/2] Importing Flipper Zero signatures...")
            flipper_stats = import_flipper_signatures(
                db,
                limit=args.limit,
                skip_existing=args.skip_existing
            )
            logger.info(f"Flipper import: {flipper_stats[0]} devices, {flipper_stats[1]} signatures")

        # Import RTL_433 protocols
        if args.rtl433 or args.all:
            logger.info("\n[2/2] Importing RTL_433 protocols...")
            rtl_stats = import_rtl433_protocols(db)
            logger.info(f"RTL_433 import: {rtl_stats[0]} devices, {rtl_stats[1]} signatures")

        logger.info("\n" + "=" * 80)
        logger.info("Import complete!")
        logger.info("=" * 80)

        # Summary
        total_devices = db.query(Device).count()
        total_signatures = db.query(Signature).count()

        logger.info(f"\nDatabase totals:")
        logger.info(f"  - Total devices: {total_devices}")
        logger.info(f"  - Total signatures: {total_signatures}")

    finally:
        db.close()


if __name__ == "__main__":
    main()
