"""
Robust Upload Error Logging System

Comprehensive logging for file uploads during beta testing.
Captures detailed error context, file metadata, and user actions.
"""

import json
import traceback
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional, List
from enum import Enum
import hashlib

from loguru import logger


class UploadStage(str, Enum):
    """Upload process stages for tracking"""
    VALIDATION = "validation"
    PARSING = "parsing"
    GPS_VALIDATION = "gps_validation"
    STORAGE = "storage"
    MATCHING = "matching"
    DATABASE = "database"
    COMPLETE = "complete"


class ErrorSeverity(str, Enum):
    """Error severity levels"""
    DEBUG = "debug"
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


class UploadLogger:
    """
    Centralized upload logging with detailed context tracking
    """

    def __init__(self, log_dir: str = "logs/uploads"):
        self.log_dir = Path(log_dir)
        self.log_dir.mkdir(parents=True, exist_ok=True)

        # Create separate log files for different purposes
        self.setup_loggers()

    def setup_loggers(self):
        """Setup specialized loggers for different aspects"""

        # Main upload log (all uploads)
        logger.add(
            self.log_dir / "uploads_{time:YYYY-MM-DD}.log",
            rotation="00:00",  # New file daily
            retention="90 days",
            level="DEBUG",
            format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} | {message}",
            enqueue=True  # Thread-safe
        )

        # Error-only log (failures)
        logger.add(
            self.log_dir / "upload_errors_{time:YYYY-MM-DD}.log",
            rotation="00:00",
            retention="180 days",  # Keep errors longer
            level="ERROR",
            format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[upload_id]} | {message}",
            enqueue=True
        )

        # JSON structured log (for analysis)
        logger.add(
            self.log_dir / "uploads_structured_{time:YYYY-MM-DD}.jsonl",
            rotation="00:00",
            retention="90 days",
            level="INFO",
            format="{message}",
            serialize=True,  # JSON output
            enqueue=True
        )

        # Performance metrics log
        logger.add(
            self.log_dir / "upload_performance_{time:YYYY-MM-DD}.log",
            rotation="00:00",
            retention="30 days",
            level="INFO",
            filter=lambda record: "performance" in record["extra"],
            enqueue=True
        )

    def log_upload_attempt(
        self,
        upload_id: str,
        user_id: Optional[int],
        session_id: Optional[str],
        file_count: int,
        total_size: int,
        client_info: Dict[str, Any]
    ):
        """
        Log the start of an upload attempt

        Args:
            upload_id: Unique upload identifier
            user_id: User ID (if authenticated)
            session_id: Session UUID
            file_count: Number of files in upload
            total_size: Total bytes
            client_info: Browser/device info
        """
        log_entry = {
            "event": "upload_attempt",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "user_id": user_id,
            "session_id": session_id,
            "file_count": file_count,
            "total_size_bytes": total_size,
            "total_size_mb": round(total_size / 1024 / 1024, 2),
            "client_info": client_info
        }

        logger.bind(upload_id=upload_id).info(
            f"Upload attempt started: {file_count} files, {log_entry['total_size_mb']} MB",
            **log_entry
        )

    def log_file_processing(
        self,
        upload_id: str,
        filename: str,
        file_size: int,
        file_hash: str,
        stage: UploadStage,
        success: bool,
        duration_ms: float,
        metadata: Optional[Dict[str, Any]] = None,
        error: Optional[str] = None
    ):
        """
        Log individual file processing

        Args:
            upload_id: Upload batch identifier
            filename: Original filename
            file_size: File size in bytes
            file_hash: SHA256 hash
            stage: Processing stage
            success: Whether stage succeeded
            duration_ms: Processing time in milliseconds
            metadata: Extracted signal metadata
            error: Error message if failed
        """
        log_entry = {
            "event": "file_processing",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "filename": filename,
            "file_size": file_size,
            "file_hash": file_hash,
            "stage": stage.value,
            "success": success,
            "duration_ms": round(duration_ms, 2),
            "metadata": metadata or {},
            "error": error
        }

        log_level = "INFO" if success else "ERROR"
        message = f"File {filename} - {stage.value}: {'✓' if success else '✗'} ({duration_ms:.0f}ms)"

        if error:
            message += f" - {error}"

        logger.bind(upload_id=upload_id).log(log_level, message, **log_entry)

    def log_parse_error(
        self,
        upload_id: str,
        filename: str,
        file_content_sample: str,
        error_type: str,
        error_message: str,
        traceback_str: str,
        file_hash: Optional[str] = None
    ):
        """
        Log detailed parse error for debugging

        Args:
            upload_id: Upload identifier
            filename: File that failed to parse
            file_content_sample: First 500 chars of file
            error_type: Exception type
            error_message: Error description
            traceback_str: Full traceback
            file_hash: File hash if available
        """
        log_entry = {
            "event": "parse_error",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "filename": filename,
            "file_hash": file_hash,
            "error_type": error_type,
            "error_message": error_message,
            "file_content_sample": file_content_sample[:500],  # First 500 chars
            "traceback": traceback_str
        }

        logger.bind(upload_id=upload_id).error(
            f"Parse failed: {filename} - {error_type}: {error_message}",
            **log_entry
        )

        # Also save problematic file sample to disk for analysis
        self._save_problem_file_sample(upload_id, filename, file_content_sample)

    def log_gps_validation_error(
        self,
        upload_id: str,
        filename: str,
        latitude: float,
        longitude: float,
        accuracy: Optional[float],
        reason: str
    ):
        """Log GPS validation failures"""
        log_entry = {
            "event": "gps_validation_error",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "filename": filename,
            "latitude": latitude,
            "longitude": longitude,
            "accuracy": accuracy,
            "reason": reason
        }

        logger.bind(upload_id=upload_id).warning(
            f"GPS validation failed: {filename} - {reason}",
            **log_entry
        )

    def log_matching_results(
        self,
        upload_id: str,
        filename: str,
        file_hash: str,
        matches: List[Dict[str, Any]],
        duration_ms: float,
        matcher_stats: Dict[str, Any]
    ):
        """
        Log device matching results

        Args:
            upload_id: Upload identifier
            filename: File being matched
            file_hash: File hash
            matches: List of match results with confidence scores
            duration_ms: Matching duration
            matcher_stats: Statistics from matcher (strategies used, etc.)
        """
        log_entry = {
            "event": "matching_results",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "filename": filename,
            "file_hash": file_hash,
            "match_count": len(matches),
            "top_match": matches[0] if matches else None,
            "top_confidence": matches[0]["confidence"] if matches else 0.0,
            "all_matches": matches,
            "duration_ms": round(duration_ms, 2),
            "matcher_stats": matcher_stats
        }

        if matches:
            top = matches[0]
            logger.bind(upload_id=upload_id).info(
                f"Matched {filename}: {top.get('manufacturer', 'Unknown')} {top.get('model', 'Unknown')} "
                f"({top['confidence']*100:.0f}% confidence)",
                **log_entry
            )
        else:
            logger.bind(upload_id=upload_id).warning(
                f"No matches found for {filename}",
                **log_entry
            )

    def log_manual_label(
        self,
        upload_id: str,
        filename: str,
        file_hash: str,
        label_type: str,
        device_id: Optional[int],
        device_info: Dict[str, Any],
        user_id: Optional[int]
    ):
        """Log manual device labeling"""
        log_entry = {
            "event": "manual_label",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "filename": filename,
            "file_hash": file_hash,
            "label_type": label_type,
            "device_id": device_id,
            "device_info": device_info,
            "user_id": user_id
        }

        logger.bind(upload_id=upload_id).info(
            f"Manual label: {filename} → {device_info.get('manufacturer', 'Unknown')} "
            f"{device_info.get('model', 'Unknown')} ({label_type})",
            **log_entry
        )

    def log_upload_complete(
        self,
        upload_id: str,
        total_duration_ms: float,
        successful_count: int,
        failed_count: int,
        duplicate_count: int,
        summary: Dict[str, Any]
    ):
        """Log upload batch completion"""
        log_entry = {
            "event": "upload_complete",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "total_duration_ms": round(total_duration_ms, 2),
            "total_duration_sec": round(total_duration_ms / 1000, 2),
            "successful_count": successful_count,
            "failed_count": failed_count,
            "duplicate_count": duplicate_count,
            "success_rate": round(successful_count / (successful_count + failed_count) * 100, 1) if (successful_count + failed_count) > 0 else 0,
            "summary": summary
        }

        logger.bind(upload_id=upload_id, performance=True).info(
            f"Upload complete: {successful_count} success, {failed_count} failed, "
            f"{duplicate_count} duplicates ({log_entry['total_duration_sec']}s)",
            **log_entry
        )

    def log_exception(
        self,
        upload_id: str,
        stage: UploadStage,
        exception: Exception,
        context: Dict[str, Any]
    ):
        """
        Log unhandled exceptions with full context

        Args:
            upload_id: Upload identifier
            stage: Stage where exception occurred
            exception: The exception object
            context: Additional context (filename, user, etc.)
        """
        log_entry = {
            "event": "exception",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "stage": stage.value,
            "exception_type": type(exception).__name__,
            "exception_message": str(exception),
            "traceback": traceback.format_exc(),
            "context": context
        }

        logger.bind(upload_id=upload_id).error(
            f"Exception in {stage.value}: {type(exception).__name__}: {str(exception)}",
            **log_entry
        )

    def log_performance_metrics(
        self,
        upload_id: str,
        metrics: Dict[str, float]
    ):
        """
        Log performance metrics for analysis

        Args:
            upload_id: Upload identifier
            metrics: Dictionary of timing metrics
                - parse_time_ms
                - matching_time_ms
                - storage_time_ms
                - db_time_ms
                - total_time_ms
        """
        log_entry = {
            "event": "performance_metrics",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            **{k: round(v, 2) for k, v in metrics.items()}
        }

        logger.bind(upload_id=upload_id, performance=True).info(
            f"Performance: parse={metrics.get('parse_time_ms', 0):.0f}ms, "
            f"match={metrics.get('matching_time_ms', 0):.0f}ms, "
            f"total={metrics.get('total_time_ms', 0):.0f}ms",
            **log_entry
        )

    def log_client_error(
        self,
        upload_id: str,
        error_type: str,
        error_message: str,
        client_info: Dict[str, Any],
        stack_trace: Optional[str] = None
    ):
        """
        Log client-side errors reported from browser

        Args:
            upload_id: Upload identifier
            error_type: JS error type
            error_message: Error message
            client_info: Browser/device info
            stack_trace: JS stack trace
        """
        log_entry = {
            "event": "client_error",
            "upload_id": upload_id,
            "timestamp": datetime.utcnow().isoformat(),
            "error_type": error_type,
            "error_message": error_message,
            "client_info": client_info,
            "stack_trace": stack_trace
        }

        logger.bind(upload_id=upload_id).error(
            f"Client error: {error_type}: {error_message}",
            **log_entry
        )

    def _save_problem_file_sample(
        self,
        upload_id: str,
        filename: str,
        content_sample: str
    ):
        """Save problematic file samples for debugging"""
        problem_dir = self.log_dir / "problem_files" / upload_id
        problem_dir.mkdir(parents=True, exist_ok=True)

        # Save sample content
        sample_file = problem_dir / f"{filename}.txt"
        sample_file.write_text(content_sample[:1000])  # First 1000 chars

        logger.debug(f"Saved problem file sample: {sample_file}")


# Global logger instance
upload_logger = UploadLogger()


# Convenience functions
def log_upload_attempt(*args, **kwargs):
    return upload_logger.log_upload_attempt(*args, **kwargs)


def log_file_processing(*args, **kwargs):
    return upload_logger.log_file_processing(*args, **kwargs)


def log_parse_error(*args, **kwargs):
    return upload_logger.log_parse_error(*args, **kwargs)


def log_gps_validation_error(*args, **kwargs):
    return upload_logger.log_gps_validation_error(*args, **kwargs)


def log_matching_results(*args, **kwargs):
    return upload_logger.log_matching_results(*args, **kwargs)


def log_manual_label(*args, **kwargs):
    return upload_logger.log_manual_label(*args, **kwargs)


def log_upload_complete(*args, **kwargs):
    return upload_logger.log_upload_complete(*args, **kwargs)


def log_exception(*args, **kwargs):
    return upload_logger.log_exception(*args, **kwargs)


def log_performance_metrics(*args, **kwargs):
    return upload_logger.log_performance_metrics(*args, **kwargs)


def log_client_error(*args, **kwargs):
    return upload_logger.log_client_error(*args, **kwargs)
