#!/usr/bin/env python3
"""
Import Flipper Zero Captures with Random African GPS Coordinates

Imports all downloaded .sub files with random GPS coordinates across Africa
to visualize device distribution on the map.

Africa Coverage:
- Latitude: -35° to 37° (South Africa to Mediterranean)
- Longitude: -17° to 51° (West coast to East coast)

Cities included as hotspots:
- Cairo, Egypt
- Lagos, Nigeria
- Nairobi, Kenya
- Johannesburg, South Africa
- Casablanca, Morocco
- Addis Ababa, Ethiopia
- Dar es Salaam, Tanzania
- Khartoum, Sudan
- Accra, Ghana
- Kampala, Uganda
"""

import sys
import os
import random
import hashlib
from pathlib import Path
from typing import Tuple, List
from datetime import datetime, timedelta

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 Capture, Device, CaptureMatch
from src.parser.sub_parser import parse_sub_file
from src.matcher.engine import SignatureMatcher

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

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

FLIPPER_DATASETS = [
    DATA_DIR / "FlipperZero-Subghz-DB",
    DATA_DIR / "UberGuidoZ_Flipper",
    DATA_DIR / "Full_Flipper_Database",
]

# Africa bounding box
AFRICA_LAT_MIN = -35.0  # South Africa
AFRICA_LAT_MAX = 37.0   # Mediterranean coast
AFRICA_LON_MIN = -17.0  # West coast (Senegal)
AFRICA_LON_MAX = 51.0   # East coast (Somalia)

# Major African cities (hotspots with higher concentration)
AFRICAN_CITIES = [
    {"name": "Cairo", "lat": 30.0444, "lon": 31.2357, "weight": 10},
    {"name": "Lagos", "lat": 6.5244, "lon": 3.3792, "weight": 8},
    {"name": "Nairobi", "lat": -1.2921, "lon": 36.8219, "weight": 7},
    {"name": "Johannesburg", "lat": -26.2041, "lon": 28.0473, "weight": 8},
    {"name": "Casablanca", "lat": 33.5731, "lon": -7.5898, "weight": 6},
    {"name": "Addis Ababa", "lat": 9.0320, "lon": 38.7469, "weight": 6},
    {"name": "Dar es Salaam", "lat": -6.7924, "lon": 39.2083, "weight": 5},
    {"name": "Khartoum", "lat": 15.5007, "lon": 32.5599, "weight": 5},
    {"name": "Accra", "lat": 5.6037, "lon": -0.1870, "weight": 5},
    {"name": "Kampala", "lat": 0.3476, "lon": 32.5825, "weight": 5},
    {"name": "Kinshasa", "lat": -4.4419, "lon": 15.2663, "weight": 6},
    {"name": "Luanda", "lat": -8.8368, "lon": 13.2343, "weight": 5},
    {"name": "Dakar", "lat": 14.7167, "lon": -17.4677, "weight": 4},
    {"name": "Cape Town", "lat": -33.9249, "lon": 18.4241, "weight": 6},
    {"name": "Abidjan", "lat": 5.3600, "lon": -4.0083, "weight": 5},
]


# =============================================================================
# GPS GENERATION
# =============================================================================

def generate_random_african_gps(use_hotspots: bool = True) -> Tuple[float, float, float]:
    """
    Generate random GPS coordinates in Africa

    Returns:
        (latitude, longitude, accuracy_meters)
    """
    if use_hotspots and random.random() < 0.6:  # 60% chance of city hotspot
        # Weight by city population/importance
        weights = [city["weight"] for city in AFRICAN_CITIES]
        city = random.choices(AFRICAN_CITIES, weights=weights, k=1)[0]

        # Add random offset within ~10km of city center
        lat_offset = random.gauss(0, 0.05)  # ~5.5 km std dev
        lon_offset = random.gauss(0, 0.05)

        latitude = city["lat"] + lat_offset
        longitude = city["lon"] + lon_offset
        accuracy = random.uniform(5.0, 15.0)  # Urban GPS accuracy

    else:
        # Random location anywhere in Africa
        latitude = random.uniform(AFRICA_LAT_MIN, AFRICA_LAT_MAX)
        longitude = random.uniform(AFRICA_LON_MIN, AFRICA_LON_MAX)
        accuracy = random.uniform(10.0, 50.0)  # Rural GPS accuracy

    return latitude, longitude, accuracy


def generate_random_timestamp(days_back: int = 90) -> datetime:
    """Generate random timestamp within last N days"""
    now = datetime.utcnow()
    delta = timedelta(days=random.randint(0, days_back))
    return now - delta


# =============================================================================
# IMPORT WITH GPS
# =============================================================================

def import_captures_with_african_gps(
    db: Session,
    limit: int = None,
    skip_existing: bool = True,
    run_matching: bool = True
) -> dict:
    """
    Import all .sub files with random African GPS coordinates

    Returns:
        Statistics dict
    """
    stats = {
        "files_processed": 0,
        "captures_created": 0,
        "duplicates_skipped": 0,
        "parse_errors": 0,
        "matches_created": 0,
        "cities_covered": set()
    }

    # 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 total")

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

    # Shuffle for geographic randomness
    random.shuffle(sub_files)

    # Initialize matcher if enabled
    matcher = SignatureMatcher(db) if run_matching else None

    # Process files
    for idx, sub_file in enumerate(sub_files, 1):
        try:
            if idx % 100 == 0:
                logger.info(f"Progress: {idx}/{len(sub_files)} ({idx/len(sub_files)*100:.1f}%)")
                db.commit()  # Commit in batches

            stats["files_processed"] += 1

            # Read file
            content = sub_file.read_bytes()
            file_hash = hashlib.sha256(content).hexdigest()

            # Check for duplicates
            if skip_existing:
                existing = db.query(Capture).filter_by(file_hash=file_hash).first()
                if existing:
                    stats["duplicates_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}")
                stats["parse_errors"] += 1
                continue

            # Generate random African GPS
            latitude, longitude, accuracy = generate_random_african_gps()

            # Track which city region this is near
            for city in AFRICAN_CITIES:
                dist_lat = abs(latitude - city["lat"])
                dist_lon = abs(longitude - city["lon"])
                if dist_lat < 1.0 and dist_lon < 1.0:  # Within ~100km
                    stats["cities_covered"].add(city["name"])
                    break

            # Generate random timestamp (last 90 days)
            captured_at = generate_random_timestamp(days_back=90)

            # Store file (simplified - just use relative path)
            storage_path = f"data/uploads/{file_hash[:2]}/{file_hash}.sub"

            # Ensure directory exists
            os.makedirs(os.path.dirname(storage_path), exist_ok=True)

            # Write file
            with open(storage_path, 'wb') as f:
                f.write(content)

            # Create Capture record
            capture = Capture(
                file_hash=file_hash,
                session_id=None,
                user_id=None,
                latitude=latitude,
                longitude=longitude,
                altitude=random.uniform(0, 1500),  # 0-1500m elevation
                gps_accuracy=accuracy,
                captured_at=captured_at,
                frequency=metadata.frequency,
                modulation=metadata.modulation,
                preset=metadata.preset,
                protocol=metadata.protocol,
                bit_length=metadata.bit_length,
                key_data=metadata.key_data,
                timing_element=metadata.timing_element,
                raw_data=str(metadata.raw_data) if metadata.raw_data else None,
                raw_format=metadata.file_format,
                file_path=storage_path,
                file_size=len(content)
            )

            db.add(capture)
            db.flush()  # Get capture ID
            stats["captures_created"] += 1

            # Run matching engine
            if matcher and run_matching:
                try:
                    match_results = matcher.match(metadata)

                    if match_results:
                        # Store top 5 matches
                        for match_result in match_results[:5]:
                            device = db.query(Device).filter_by(id=match_result.device_id).first()
                            if not device:
                                continue

                            capture_match = CaptureMatch(
                                capture_id=capture.id,
                                device_id=device.id,
                                confidence=match_result.confidence,
                                match_method=match_result.method,
                                match_details=match_result.details or {}
                            )
                            db.add(capture_match)
                            stats["matches_created"] += 1

                        # Set best match
                        best_match = match_results[0]
                        capture.device_id = best_match.device_id
                        capture.match_confidence = best_match.confidence
                        capture.match_method = best_match.method

                except Exception as e:
                    logger.debug(f"Matching failed for {sub_file.name}: {e}")

        except Exception as e:
            logger.error(f"Failed to process {sub_file}: {e}")
            continue

    # Final commit
    db.commit()

    return stats


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

def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="Import .sub files with random African GPS coordinates"
    )
    parser.add_argument("--limit", type=int, default=None, help="Limit number of files")
    parser.add_argument("--no-matching", action="store_true", help="Skip device matching")
    parser.add_argument("--skip-existing", action="store_true", default=True)

    args = parser.parse_args()

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

    logger.info("=" * 80)
    logger.info("Importing Captures with African GPS Coordinates")
    logger.info("=" * 80)

    # Setup
    db = get_session()

    # Set random seed for reproducibility (optional)
    random.seed(42)

    try:
        stats = import_captures_with_african_gps(
            db=db,
            limit=args.limit,
            skip_existing=args.skip_existing,
            run_matching=not args.no_matching
        )

        logger.info("\n" + "=" * 80)
        logger.info("Import Complete!")
        logger.info("=" * 80)
        logger.info(f"Files processed:     {stats['files_processed']}")
        logger.info(f"Captures created:    {stats['captures_created']}")
        logger.info(f"Duplicates skipped:  {stats['duplicates_skipped']}")
        logger.info(f"Parse errors:        {stats['parse_errors']}")
        logger.info(f"Matches created:     {stats['matches_created']}")
        logger.info(f"Cities covered:      {len(stats['cities_covered'])}")
        logger.info(f"  → {', '.join(sorted(stats['cities_covered']))}")

        # Database totals
        try:
            total_captures = db.query(Capture).count()
            total_matches = db.query(CaptureMatch).count()

            logger.info(f"\nDatabase totals:")
            logger.info(f"  Total captures:    {total_captures}")
            logger.info(f"  Total matches:     {total_matches}")
        except Exception as e:
            logger.warning(f"Could not query database totals: {e}")

        # Geographic distribution
        logger.info(f"\nGeographic distribution:")
        result = db.execute("""
            SELECT
                COUNT(*) as count,
                AVG(latitude) as avg_lat,
                AVG(longitude) as avg_lon
            FROM captures
            WHERE latitude BETWEEN -35 AND 37
                AND longitude BETWEEN -17 AND 51
        """).fetchone()

        if result:
            logger.info(f"  Captures in Africa: {result[0]}")
            logger.info(f"  Center point:       {result[1]:.2f}°, {result[2]:.2f}°")

    finally:
        db.close()


if __name__ == "__main__":
    main()
