"""
ErrorService - Centralized error capture and management.

Provides methods for:
- Capturing errors from frontend and backend
- Querying errors with filtering
- Generating statistics for dashboard
- Resolving errors
- Cleaning up old errors
"""

import logging
import traceback
from datetime import timedelta
from typing import Any
from uuid import UUID

from django.db.models import Count, Q
from django.db.models.functions import TruncDate
from django.http import HttpRequest
from django.utils import timezone

from apps.core.models import ApplicationError

logger = logging.getLogger(__name__)


class ErrorService:
    """Service for error capture and management."""

    @staticmethod
    def capture_error(
        error: Exception | str,
        source: str,
        request: HttpRequest | None = None,
        user: Any | None = None,
        severity: str = "medium",
        metadata: dict | None = None,
        environment: dict | None = None,
        error_id: UUID | None = None,
    ) -> ApplicationError:
        """
        Capture and store an error.

        Args:
            error: The exception or error message
            source: One of 'frontend', 'backend', 'api'
            request: Optional HTTP request for context
            user: Optional user who encountered the error
            severity: Error severity level
            metadata: Additional context data
            environment: Client environment info

        Returns:
            The created ApplicationError instance
        """
        # Extract error details
        if isinstance(error, Exception):
            error_type = type(error).__name__
            error_message = str(error)
            error_stack = traceback.format_exc()
            error_code = getattr(error, "error_code", "") or ""
        else:
            error_type = "Error"
            error_message = str(error)
            error_stack = ""
            error_code = ""

        # Extract request details
        endpoint = ""
        http_method = ""
        user_agent = ""
        ip_address_hash = ""
        http_status = None

        # Build request_context for additional debugging info
        request_context: dict[str, Any] = {}

        if request:
            endpoint = request.path
            http_method = request.method
            user_agent = request.META.get("HTTP_USER_AGENT", "")

            # Hash IP for privacy
            ip = ErrorService._get_client_ip(request)
            ip_address_hash = ApplicationError.hash_ip(ip)

            # Use request user if not provided
            if user is None and hasattr(request, "user") and request.user.is_authenticated:
                user = request.user

            # Capture additional request context for debugging
            request_context = {
                "content_type": request.content_type,
                "query_string": request.META.get("QUERY_STRING", "")[:500],
                "request_id": request.META.get("HTTP_X_REQUEST_ID", ""),
                "accept_language": request.META.get("HTTP_ACCEPT_LANGUAGE", ""),
                "referer": request.META.get("HTTP_REFERER", ""),
                "origin": request.META.get("HTTP_ORIGIN", ""),
            }

            # Capture request body snippet for POST/PUT/PATCH (sanitized)
            if http_method in ("POST", "PUT", "PATCH"):
                try:
                    body_snippet = request.body.decode("utf-8")[:1000] if request.body else ""
                    # Sanitize sensitive fields
                    for field in ("password", "token", "secret", "key", "auth"):
                        if field in body_snippet.lower():
                            body_snippet = "[REDACTED - contains sensitive data]"
                            break
                    request_context["body_snippet"] = body_snippet
                except Exception:
                    request_context["body_snippet"] = "[Could not decode body]"

        # Extract exception chain for comprehensive debugging
        if isinstance(error, Exception):
            cause_chain = []
            current_cause = error.__cause__
            depth = 0
            while current_cause and depth < 5:
                cause_chain.append({
                    "type": type(current_cause).__name__,
                    "message": str(current_cause)[:500],
                })
                current_cause = current_cause.__cause__
                depth += 1
            if cause_chain:
                request_context["exception_chain"] = cause_chain

        # Merge request_context into metadata
        merged_metadata = {**(metadata or {}), "request_context": request_context}

        # Build create kwargs, only including error_id if provided
        create_kwargs = {
            "error_type": error_type,
            "error_message": error_message[:5000],  # Limit message length
            "error_stack": error_stack[:10000],  # Limit stack length
            "error_code": error_code,
            "severity": severity,
            "source": source,
            "endpoint": endpoint,
            "http_method": http_method,
            "user_agent": user_agent[:1000],
            "user": user,
            "ip_address_hash": ip_address_hash,
            "metadata": merged_metadata,
            "environment": environment or {},
        }
        # Only include error_id if explicitly provided, else let model default
        if error_id is not None:
            create_kwargs["error_id"] = error_id

        # Create and save error
        app_error = ApplicationError.objects.create(**create_kwargs)

        logger.info(
            f"Captured error {app_error.short_error_id}: {error_type} from {source}"
        )

        return app_error

    @staticmethod
    def capture_frontend_error(
        error_data: dict,
        request: HttpRequest | None = None,
    ) -> ApplicationError:
        """
        Capture an error reported from the frontend.

        Args:
            error_data: Dictionary containing error details from frontend
            request: HTTP request for IP/user context

        Returns:
            The created ApplicationError instance
        """
        return ErrorService.capture_error(
            error=error_data.get("error_message", "Unknown error"),
            source="frontend",
            request=request,
            severity=error_data.get("severity", "medium"),
            metadata=error_data.get("metadata", {}),
            environment=error_data.get("environment", {}),
            error_id=error_data.get("error_id"),
        )

    @staticmethod
    def get_error_statistics(days: int = 7) -> dict:
        """
        Get error statistics for the dashboard.

        Args:
            days: Number of days to include in statistics

        Returns:
            Dictionary containing error statistics
        """
        cutoff = timezone.now() - timedelta(days=days)
        base_queryset = ApplicationError.objects.filter(created_at__gte=cutoff)

        # Total errors
        total_errors = base_queryset.count()

        # Errors by severity
        severity_counts = dict(
            base_queryset.values("severity")
            .annotate(count=Count("id"))
            .values_list("severity", "count")
        )

        # Errors by source
        source_counts = dict(
            base_queryset.values("source")
            .annotate(count=Count("id"))
            .values_list("source", "count")
        )

        # Top error types
        error_types = list(
            base_queryset.values("error_type")
            .annotate(count=Count("id"))
            .order_by("-count")[:10]
        )

        # Errors over time (daily)
        errors_over_time = list(
            base_queryset.annotate(date=TruncDate("created_at"))
            .values("date")
            .annotate(count=Count("id"))
            .order_by("date")
        )

        # Convert dates to strings for JSON serialization
        for item in errors_over_time:
            item["date"] = item["date"].isoformat() if item["date"] else None

        # Resolution rate
        resolved_count = base_queryset.filter(resolved=True).count()
        resolution_rate = (resolved_count / total_errors * 100) if total_errors > 0 else 0

        # Critical/unresolved counts for quick stats
        critical_count = base_queryset.filter(severity="critical").count()
        unresolved_count = base_queryset.filter(resolved=False).count()

        return {
            "total_errors": total_errors,
            "errors_by_severity": severity_counts,
            "errors_by_source": source_counts,
            "errors_by_type": error_types,
            "errors_over_time": errors_over_time,
            "resolution_rate": round(resolution_rate, 1),
            "critical_count": critical_count,
            "unresolved_count": unresolved_count,
            "period_days": days,
        }

    @staticmethod
    def resolve_error(
        error_id: UUID | int,
        user: Any,
        notes: str = "",
    ) -> ApplicationError:
        """
        Mark an error as resolved.

        Args:
            error_id: UUID or database ID of the error
            user: User marking the error as resolved
            notes: Optional resolution notes

        Returns:
            The updated ApplicationError instance

        Raises:
            ApplicationError.DoesNotExist: If error not found
        """
        if isinstance(error_id, int):
            error = ApplicationError.objects.get(id=error_id)
        else:
            error = ApplicationError.objects.get(error_id=error_id)

        error.resolved = True
        error.resolved_at = timezone.now()
        error.resolved_by = user
        error.resolution_notes = notes
        error.save(update_fields=["resolved", "resolved_at", "resolved_by", "resolution_notes"])

        logger.info(
            f"Error {error.short_error_id} resolved by {user}"
        )

        return error

    @staticmethod
    def cleanup_old_errors(days: int = 30) -> int:
        """
        Delete errors older than specified days.

        Args:
            days: Delete errors older than this many days

        Returns:
            Number of errors deleted
        """
        cutoff = timezone.now() - timedelta(days=days)

        # Only delete resolved errors by default, keep unresolved critical
        deleted_count, _ = ApplicationError.objects.filter(
            Q(created_at__lt=cutoff) & (Q(resolved=True) | ~Q(severity="critical"))
        ).delete()

        logger.info(
            f"Cleaned up {deleted_count} errors older than {days} days"
        )

        return deleted_count

    @staticmethod
    def _get_client_ip(request: HttpRequest) -> str:
        """Extract client IP from request, handling proxies."""
        x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
        if x_forwarded_for:
            return x_forwarded_for.split(",")[0].strip()
        return request.META.get("REMOTE_ADDR", "")
