"""
Enhanced Capture Upload Endpoints

Features:
- Automatic device matching on upload
- Manual device labeling support
- Photo upload for unknown devices
- Training data collection
"""

from typing import List, Optional
from fastapi import APIRouter, UploadFile, File, Form, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.orm import Session
from loguru import logger
import hashlib
import json
from datetime import datetime
import tempfile
import os

from src.api.dependencies import get_db, optional_auth, get_storage
from src.database.models import (
    User, Capture, Session as CaptureSession,
    CaptureMatch, Device, ManualIdentification
)
from src.parser.sub_parser import parse_sub_file
from src.gps.validator import validate_gps_coordinates
from src.matcher.engine import SignatureMatcher
from config.settings import settings


router = APIRouter()


# =============================================================================
# ENHANCED UPLOAD ENDPOINT
# =============================================================================

@router.post("/captures/upload/enhanced")
async def upload_captures_enhanced(
    manifest: str = Form(..., description="JSON manifest with GPS data and manual labels"),
    files: List[UploadFile] = File(..., description=".sub files to upload"),
    background_tasks: BackgroundTasks = None,
    db: Session = Depends(get_db),
    user: User = Depends(optional_auth),
    storage = Depends(get_storage)
):
    """
    Enhanced upload with automatic matching and manual labeling

    **Manifest Format**:
    ```json
    {
        "session_uuid": "550e8400-e29b-41d4-a716-446655440000",
        "captures": [
            {
                "filename": "capture_001.sub",
                "latitude": 40.7128,
                "longitude": -74.0060,
                "accuracy": 5.0,
                "altitude": 10.5,
                "timestamp": "2026-01-12T10:00:00Z"
            }
        ],
        "manual_labels": [
            {
                "filename": "capture_001.sub",
                "device_id": 123,
                "source": "manual_existing"
            },
            {
                "filename": "capture_002.sub",
                "device_type": "Garage Door Opener",
                "manufacturer": "Chamberlain",
                "model": "891LM",
                "notes": "Captured from my garage",
                "source": "manual_new"
            }
        ]
    }
    ```

    **Process**:
    1. Parse and validate manifest
    2. For each file:
        - Deduplicate by hash
        - Validate GPS
        - Parse .sub file
        - Store file
        - **Run automatic matching (NEW)**
        - **Apply manual labels if provided (NEW)**
        - Save capture with matches
    3. Return results with device identifications
    """

    try:
        # Parse manifest
        manifest_data = json.loads(manifest)
        session_uuid = manifest_data.get("session_uuid")
        captures_manifest = manifest_data.get("captures", [])
        manual_labels_list = manifest_data.get("manual_labels", [])

        logger.info(f"Enhanced upload: {len(files)} files, {len(manual_labels_list)} manual labels")

        # Create manual labels lookup
        manual_labels_map = {label['filename']: label for label in manual_labels_list}

        # Get or create session
        capture_session = None
        if session_uuid:
            capture_session = db.query(CaptureSession).filter_by(
                session_uuid=session_uuid
            ).first()

            if not capture_session:
                capture_session = CaptureSession(
                    session_uuid=session_uuid,
                    user_id=user.id if user else None,
                    started_at=datetime.utcnow()
                )
                db.add(capture_session)
                db.flush()

        # Create filename to manifest mapping
        manifest_map = {c['filename']: c for c in captures_manifest}

        # Initialize matcher
        matcher = SignatureMatcher(db)

        # Process files
        results = {"successful": [], "failed": []}

        for file in files:
            try:
                # Read file content
                content = await file.read()

                # Compute SHA256 hash
                file_hash = hashlib.sha256(content).hexdigest()

                # Check if already exists
                existing = db.query(Capture).filter_by(file_hash=file_hash).first()
                if existing:
                    results["successful"].append({
                        "filename": file.filename,
                        "status": "duplicate",
                        "file_hash": file_hash,
                        "matches": []  # Could retrieve existing matches
                    })
                    continue

                # Get manifest entry
                manifest_entry = manifest_map.get(file.filename)
                if not manifest_entry:
                    results["failed"].append({
                        "filename": file.filename,
                        "error": "Missing manifest entry"
                    })
                    continue

                # Validate GPS
                latitude = float(manifest_entry['latitude'])
                longitude = float(manifest_entry['longitude'])
                accuracy = manifest_entry.get('accuracy')

                if not validate_gps_coordinates(latitude, longitude, accuracy):
                    results["failed"].append({
                        "filename": file.filename,
                        "error": f"Invalid GPS coordinates: ({latitude}, {longitude})"
                    })
                    continue

                # Parse .sub file
                with tempfile.NamedTemporaryFile(delete=False, suffix='.sub') as tmp:
                    tmp.write(content)
                    tmp_path = tmp.name

                try:
                    metadata = parse_sub_file(tmp_path)
                except Exception as e:
                    results["failed"].append({
                        "filename": file.filename,
                        "error": f"Failed to parse .sub file: {e}"
                    })
                    continue
                finally:
                    os.unlink(tmp_path)

                # Store file
                storage_path = storage.save_file(
                    file_hash=file_hash,
                    content=content,
                    filename=file.filename
                )

                # Create capture record
                capture = Capture(
                    file_hash=file_hash,
                    session_id=capture_session.id if capture_session else None,
                    user_id=user.id if user else None,
                    latitude=latitude,
                    longitude=longitude,
                    altitude=manifest_entry.get('altitude'),
                    gps_accuracy=accuracy,
                    captured_at=datetime.fromisoformat(
                        manifest_entry['timestamp'].replace('Z', '+00:00')
                    ),
                    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

                # =====================================================================
                # NEW: Automatic Device Matching
                # =====================================================================
                matches = []
                try:
                    # Run matching engine
                    match_results = matcher.match(metadata)

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

                        # Create CaptureMatch record
                        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)

                        matches.append({
                            "device_id": device.id,
                            "manufacturer": device.manufacturer,
                            "model": device.model,
                            "device_type": device.device_type,
                            "confidence": match_result.confidence,
                            "method": match_result.method
                        })

                    # Set best match as capture's device_id
                    if matches:
                        capture.device_id = matches[0]["device_id"]
                        capture.match_confidence = matches[0]["confidence"]
                        capture.match_method = matches[0]["method"]

                except Exception as e:
                    logger.error(f"Matching failed for {file.filename}: {e}")
                    # Continue without matches

                # =====================================================================
                # NEW: Manual Device Labeling
                # =====================================================================
                manual_label = manual_labels_map.get(file.filename)
                if manual_label:
                    await handle_manual_label(
                        db=db,
                        capture=capture,
                        manual_label=manual_label,
                        user=user
                    )

                # Commit this capture
                db.commit()

                results["successful"].append({
                    "filename": file.filename,
                    "status": "uploaded",
                    "file_hash": file_hash,
                    "frequency": metadata.frequency,
                    "protocol": metadata.protocol,
                    "matches": matches,
                    "manual_label": manual_label is not None
                })

                logger.debug(f"Capture uploaded with {len(matches)} matches: {file.filename}")

            except Exception as e:
                logger.error(f"Failed to process file {file.filename}: {e}")
                results["failed"].append({
                    "filename": file.filename,
                    "error": str(e)
                })

        logger.success(f"Enhanced upload complete: {len(results['successful'])} successful, {len(results['failed'])} failed")

        return results

    except json.JSONDecodeError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid JSON manifest: {e}"
        )
    except Exception as e:
        logger.error(f"Enhanced upload failed: {e}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Upload failed: {e}"
        )


# =============================================================================
# MANUAL LABEL HANDLING
# =============================================================================

async def handle_manual_label(
    db: Session,
    capture: Capture,
    manual_label: dict,
    user: Optional[User]
):
    """
    Process manual device label from user

    Two types:
    1. manual_existing: User selected from existing device database
    2. manual_new: User created new device entry
    """

    source = manual_label.get("source", "manual")

    if source == "manual_existing":
        # User selected existing device
        device_id = manual_label.get("device_id")
        if not device_id:
            return

        device = db.query(Device).filter_by(id=device_id).first()
        if not device:
            logger.warning(f"Manual label references non-existent device: {device_id}")
            return

        # Override automatic match with manual selection
        capture.device_id = device_id
        capture.match_confidence = 1.0  # User-confirmed
        capture.match_method = "manual_selection"

        # Create ManualIdentification record
        manual_id = ManualIdentification(
            capture_id=capture.id,
            device_id=device_id,
            user_id=user.id if user else None,
            confidence=1.0,
            notes=manual_label.get("notes"),
            verified=False,  # Needs community verification
            verification_count=1  # Initial submission counts as 1
        )
        db.add(manual_id)

        logger.info(f"Manual label applied: capture {capture.id} -> device {device_id}")

    elif source == "manual_new":
        # User created new device
        device_type = manual_label.get("device_type")
        manufacturer = manual_label.get("manufacturer")
        model = manual_label.get("model")

        if not all([device_type, manufacturer, model]):
            logger.warning("Incomplete manual_new label data")
            return

        # Check if device already exists
        existing_device = db.query(Device).filter_by(
            manufacturer=manufacturer,
            model=model
        ).first()

        if existing_device:
            device = existing_device
        else:
            # Create new device
            device = Device(
                manufacturer=manufacturer,
                model=model,
                device_type=device_type,
                typical_frequency=capture.frequency,
                protocol=capture.protocol,
                source="community",
                verified=False,  # Needs verification
                metadata_json={"submitter_user_id": user.id if user else None}
            )
            db.add(device)
            db.flush()  # Get device ID

        # Link capture to device
        capture.device_id = device.id
        capture.match_confidence = 1.0
        capture.match_method = "manual_new_device"

        # Create ManualIdentification record
        manual_id = ManualIdentification(
            capture_id=capture.id,
            device_id=device.id,
            user_id=user.id if user else None,
            confidence=1.0,
            notes=manual_label.get("notes"),
            verified=False,
            verification_count=1
        )
        db.add(manual_id)

        logger.info(f"New device created from manual label: {manufacturer} {model}")


# =============================================================================
# BATCH MATCHING ENDPOINT (For existing captures)
# =============================================================================

@router.post("/captures/match_batch")
async def match_captures_batch(
    limit: int = 100,
    background_tasks: BackgroundTasks = None,
    db: Session = Depends(get_db),
    user: User = Depends(optional_auth)
):
    """
    Run matching engine on existing captures that don't have device identifications

    Use this to backfill matches for captures uploaded before automatic matching was implemented.
    """

    # Find captures without device_id
    unmatched_captures = db.query(Capture).filter(
        Capture.device_id.is_(None)
    ).limit(limit).all()

    if not unmatched_captures:
        return {
            "message": "No unmatched captures found",
            "processed": 0
        }

    logger.info(f"Batch matching {len(unmatched_captures)} captures")

    matcher = SignatureMatcher(db)
    matched_count = 0

    for capture in unmatched_captures:
        try:
            # Reconstruct SignalMetadata from capture
            from src.parser.metadata import SignalMetadata

            metadata = SignalMetadata()
            metadata.frequency = capture.frequency
            metadata.protocol = capture.protocol
            metadata.modulation = capture.modulation
            metadata.bit_length = capture.bit_length
            metadata.key_data = capture.key_data
            metadata.timing_element = capture.timing_element

            if capture.raw_data:
                # Parse raw_data string back to list
                metadata.raw_data = eval(capture.raw_data) if capture.raw_data.startswith('[') else None

            # Run matching
            match_results = matcher.match(metadata)

            if match_results:
                # Store top 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

                # Store all matches in CaptureMatch table
                for match_result in match_results[:10]:
                    capture_match = CaptureMatch(
                        capture_id=capture.id,
                        device_id=match_result.device_id,
                        confidence=match_result.confidence,
                        match_method=match_result.method,
                        match_details=match_result.details or {}
                    )
                    db.add(capture_match)

                matched_count += 1

        except Exception as e:
            logger.error(f"Batch matching failed for capture {capture.id}: {e}")
            continue

    db.commit()

    logger.success(f"Batch matching complete: {matched_count}/{len(unmatched_captures)} matched")

    return {
        "message": f"Processed {len(unmatched_captures)} captures",
        "matched": matched_count,
        "unmatched": len(unmatched_captures) - matched_count
    }
