"""
View Tracking Middleware for automatic PageView recording.

This middleware automatically tracks page views for park and ride pages,
implementing IP-based deduplication to prevent spam and provide accurate
analytics for the trending algorithm.
"""

import logging
import re
from datetime import timedelta

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.http import HttpRequest, HttpResponse
from django.utils import timezone

from apps.core.analytics import PageView
from apps.core.utils import capture_and_log
from apps.parks.models import Park
from apps.rides.models import Ride

# Type alias for content objects
ContentObject = Park | Ride

logger = logging.getLogger(__name__)


class ViewTrackingMiddleware:
    """
    Middleware for tracking page views with IP deduplication.

    Automatically creates PageView records when users visit park or ride pages.
    Implements 24-hour IP deduplication window to prevent view inflation.
    """

    def __init__(self, get_response):
        self.get_response = get_response
        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")

        # URL patterns for tracking - matches park and ride detail pages
        self.tracked_patterns = [
            (r"^/parks/(?P<slug>[\w-]+)/$", "park"),
            (r"^/rides/(?P<slug>[\w-]+)/$", "ride"),
            # Add API patterns if needed
            (r"^/api/v1/parks/(?P<slug>[\w-]+)/$", "park"),
            (r"^/api/v1/rides/(?P<slug>[\w-]+)/$", "ride"),
        ]

        # Compile patterns for performance
        self.compiled_patterns = [
            (re.compile(pattern), content_type) for pattern, content_type in self.tracked_patterns
        ]

        # Cache configuration
        self.cache_timeout = 60 * 15  # 15 minutes
        self.dedup_window_hours = 24

    def __call__(self, request: HttpRequest) -> HttpResponse:
        """Process the request and track views if applicable."""
        response = self.get_response(request)

        # Only track successful GET requests
        if request.method == "GET" and 200 <= response.status_code < 300 and not self._should_skip_tracking(request):
            try:
                self._track_view_if_applicable(request)
            except Exception as e:
                # Capture error but don't break the request
                capture_and_log(e, f'Track view for {request.path}', source='middleware', severity='low')

        return response

    def _should_skip_tracking(self, request: HttpRequest) -> bool:
        """Check if this request should be skipped for tracking."""
        # Skip if disabled in settings
        if not getattr(settings, "ENABLE_VIEW_TRACKING", True):
            return True

        # Skip requests from bots/crawlers
        user_agent = request.META.get("HTTP_USER_AGENT", "").lower()
        bot_indicators = [
            "bot",
            "crawler",
            "spider",
            "scraper",
            "facebook",
            "twitter",
            "linkedin",
            "google",
            "bing",
            "yahoo",
            "duckduckgo",
            "slurp",
        ]
        if any(indicator in user_agent for indicator in bot_indicators):
            return True

        # Skip requests without real IP
        if not self._get_client_ip(request):
            return True

        # Skip AJAX requests (optional - depending on requirements)
        return request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"

    def _track_view_if_applicable(self, request: HttpRequest) -> None:
        """Track view if the URL matches tracked patterns."""
        path = request.path

        for pattern, content_type in self.compiled_patterns:
            match = pattern.match(path)
            if match:
                slug = match.group("slug")
                self._record_page_view(request, content_type, slug)
                break

    def _record_page_view(self, request: HttpRequest, content_type: str, slug: str) -> None:
        """Record a page view for the specified content."""
        client_ip = self._get_client_ip(request)
        if not client_ip:
            return

        try:
            # Get the content object
            content_obj = self._get_content_object(content_type, slug)
            if not content_obj:
                self.logger.warning(f"Content not found: {content_type} with slug '{slug}'")
                return

            # Check deduplication
            if self._is_duplicate_view(content_obj, client_ip):
                self.logger.debug(f"Duplicate view skipped for {content_type} {slug} from {client_ip}")
                return

            # Create PageView record
            self._create_page_view(content_obj, client_ip, request)

            self.logger.debug(f"Recorded view for {content_type} {slug} from {client_ip}")

        except Exception as e:
            capture_and_log(e, f'Record page view for {content_type} {slug}', source='middleware', severity='low')

    def _get_content_object(self, content_type: str, slug: str) -> ContentObject | None:
        """Get the content object by type and slug."""
        try:
            if content_type == "park":
                # Use get_by_slug method to handle historical slugs
                park, _ = Park.get_by_slug(slug)
                return park
            elif content_type == "ride":
                # For rides, we need to search by slug within parks
                return Ride.objects.filter(slug=slug).first()
            else:
                self.logger.warning(f"Unknown content type: {content_type}")
                return None

        except Park.DoesNotExist:
            return None
        except Exception as e:
            capture_and_log(e, f'Get {content_type} with slug {slug}', source='middleware', severity='low')
            return None

    def _is_duplicate_view(self, content_obj: ContentObject, client_ip: str) -> bool:
        """Check if this view is a duplicate within the deduplication window."""
        # Use cache for performance
        cache_key = self._get_dedup_cache_key(content_obj, client_ip)

        if cache.get(cache_key):
            return True

        # Check database as fallback
        content_type = ContentType.objects.get_for_model(content_obj)
        cutoff_time = timezone.now() - timedelta(hours=self.dedup_window_hours)

        existing_view = PageView.objects.filter(
            content_type=content_type,
            object_id=content_obj.pk,
            ip_address=client_ip,
            timestamp__gte=cutoff_time,
        ).exists()

        if not existing_view:
            # Set cache to prevent future duplicates
            cache.set(cache_key, True, timeout=self.dedup_window_hours * 3600)

        return existing_view

    def _create_page_view(self, content_obj: ContentObject, client_ip: str, request: HttpRequest) -> None:
        """Create a new PageView record."""
        content_type = ContentType.objects.get_for_model(content_obj)

        # Extract additional metadata
        user_agent = request.META.get("HTTP_USER_AGENT", "")[:500]  # Truncate long user agents
        referer = request.META.get("HTTP_REFERER", "")[:500]

        PageView.objects.create(
            content_type=content_type,
            object_id=content_obj.pk,
            ip_address=client_ip,
            user_agent=user_agent,
            referer=referer,
            path=request.path[:500],
        )

        # Update cache for deduplication
        cache_key = self._get_dedup_cache_key(content_obj, client_ip)
        cache.set(cache_key, True, timeout=self.dedup_window_hours * 3600)

    def _get_dedup_cache_key(self, content_obj: ContentObject, client_ip: str) -> str:
        """Generate cache key for deduplication."""
        content_type = ContentType.objects.get_for_model(content_obj)
        return f"pageview_dedup:{content_type.id}:{content_obj.pk}:{client_ip}"

    def _get_client_ip(self, request: HttpRequest) -> str | None:
        """Extract client IP address from request."""
        # Check for forwarded IP (common in production with load balancers)
        x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
        if x_forwarded_for:
            # Take the first IP in the chain (client IP)
            ip = x_forwarded_for.split(",")[0].strip()
            if self._is_valid_ip(ip):
                return ip

        # Check for real IP header (some proxy configurations)
        x_real_ip = request.META.get("HTTP_X_REAL_IP")
        if x_real_ip and self._is_valid_ip(x_real_ip):
            return x_real_ip

        # Fall back to remote address
        remote_addr = request.META.get("REMOTE_ADDR")
        if remote_addr and self._is_valid_ip(remote_addr):
            return remote_addr

        return None

    def _is_valid_ip(self, ip: str) -> bool:
        """Validate IP address format."""
        try:
            # Basic validation - check if it looks like an IP
            parts = ip.split(".")
            if len(parts) != 4:
                return False

            for part in parts:
                if not part.isdigit() or not 0 <= int(part) <= 255:
                    return False

            # Skip localhost and private IPs in production
            if getattr(settings, "SKIP_LOCAL_IPS", not settings.DEBUG):  # noqa: SIM102
                if (ip.startswith(("127.", "192.168.", "10.")) or ip.startswith("172.")) and any(
                    16 <= int(ip.split(".")[1]) <= 31 for _ in [ip] if ip.startswith("172.")
                ):
                    return False

            return True

        except (ValueError, IndexError):
            return False


def get_view_stats_for_content(content_obj: ContentObject, hours: int = 24) -> dict:
    """
    Helper function to get view statistics for content.

    Args:
        content_obj: The content object (Park or Ride)
        hours: Time window in hours for stats

    Returns:
        Dictionary with view statistics
    """
    try:
        content_type = ContentType.objects.get_for_model(content_obj)
        cutoff_time = timezone.now() - timedelta(hours=hours)

        total_views = PageView.objects.filter(
            content_type=content_type,
            object_id=content_obj.pk,
            timestamp__gte=cutoff_time,
        ).count()

        unique_views = (
            PageView.objects.filter(
                content_type=content_type,
                object_id=content_obj.pk,
                timestamp__gte=cutoff_time,
            )
            .values("ip_address")
            .distinct()
            .count()
        )

        return {
            "total_views": total_views,
            "unique_views": unique_views,
            "hours": hours,
            "content_type": content_type.model,
            "content_id": content_obj.pk,
        }

    except Exception as e:
        capture_and_log(e, f'Get view stats for content', source='service', severity='low')
        return {"total_views": 0, "unique_views": 0, "hours": hours, "error": str(e)}
