from api.property_api.property.models.property import Property
from service.models.services import Services
from jobs_company.models.jobs import Jobs
from django.apps import apps

import sys
import os
import json
from django.db.models import Model
from django.db.models.fields.files import FieldFile

def get_object_size_in_mb(obj):
    """
    Calculate approximate size (in MB) of a Django model instance or dict.
    Handles:
        - Model instances (uses _meta)
        - Dicts/lists (serializes to JSON)
        - File fields (uses .size)
    """

    total_size = 0

    # 🔹 CASE 1: Django model instance
    if isinstance(obj, Model):
        for field in obj._meta.get_fields():
            # Skip reverse relations (e.g., related_name)
            if field.many_to_one or field.one_to_one:
                continue

            value = getattr(obj, field.name, None)
            if value is None:
                continue

            # File/Image fields. Empty FieldFile objects raise when `.size` is accessed.
            if isinstance(value, FieldFile):
                if value and value.name:
                    total_size += value.size

            # Strings, numbers, dicts, lists
            elif isinstance(value, (str, int, float, dict, list)):
                total_size += sys.getsizeof(value)

    # 🔹 CASE 2: Dict or list
    elif isinstance(obj, (dict, list)):
        try:
            json_str = json.dumps(obj)
            total_size = sys.getsizeof(json_str)
        except (TypeError, ValueError):
            # If object cannot be JSON serialized
            total_size = sys.getsizeof(str(obj))

    # 🔹 Convert to MB
    total_mb = round(total_size / (1024 * 1024), 4)
    return total_mb


def get_user_total_space_used(user):
    """
    Dynamically calculates the total storage used by a user
    across all models (Properties, Services, Jobs, etc.).
    """
    total_size = 0

    # Define your model mappings here
    model_mappings = [
        ("properties", "Property", ["user", "agent", "created_by"]),
        ("services", "Service", ["user", "agent", "created_by"]),
        ("jobs", "Job", ["user", "agent", "created_by"]),
        ("property_reviews", "PropertyReviews", ["created_by"]),
        ("service", "ServicesReview", ["created_by"]),
        ("company_review", "CompanyReview", ["created_by", "user"]),
        ("job_company_reviews", "JobCompanyReview", ["created_by"]),
        ("jobs", "JobReview", ["created_by"]),
        ("producers_products", "ProductsReview", ["created_by"]),
        ("property_media", "PropertyMedia", ["property_ID__created_by", "property_ID__user", "property_ID__agent"]),
        ("service_media", "ServiceMedia", ["user", "service__created_by"]),
        ("job_companies", "JobCompanies", ["created_by"]),
        ("company_services", "Companies", ["created_by"]),
        ("company_gallery", "CompanyGallery", ["company__created_by"]),
        ("job_company_team_members", "CompanyTeam", ["company__created_by"]),
        ("producers_products_images", "ProductsImages", ["product__created_by"]),
        ("company_portfolio_business", "CompanyPortfolioBusiness", ["company_id__created_by"]),
        ("service_ownership", "ServiceOwnership", ["user", "created_by", "service__created_by"]),
        ("service_report", "ServiceReport", ["reported_by", "created_by"]),
    ]

    for app_label, model_name, possible_fields in model_mappings:
        try:
            Model = apps.get_model(app_label, model_name)
        except LookupError:
            continue  # Model doesn't exist, skip

        # Support both direct user fields and nested relation lookups.
        queryset = None
        for lookup in possible_fields:
            try:
                candidate_queryset = Model.objects.filter(**{lookup: user})
                candidate_queryset.query
                queryset = candidate_queryset
                break
            except Exception:
                continue

        if queryset is None:
            continue

        for obj in queryset.distinct():
            total_size += get_object_size_in_mb(obj)

    return round(total_size, 4)  # Total size in MB


def ensure_user_has_storage_capacity(user, incoming_file=None, field_name="media"):
    import logging
    from rest_framework.exceptions import ValidationError
    from user_ads_package.models.userpackage import UserPackage

    logger = logging.getLogger('storage_capacity')
    
    # Generate request ID for tracking (use thread-local storage if available)
    import threading
    request_id = getattr(threading.current_thread(), 'request_id', 'unknown')
    user_id = getattr(user, 'id', 'unknown')
    
    logger.info(
        f"Storage capacity check started for user {user_id}",
        extra={
            'user_id': user_id,
            'request_id': request_id,
            'field_name': field_name,
            'has_incoming_file': incoming_file is not None
        }
    )

    # Check user package
    user_package = (
        UserPackage.objects.select_related("package")
        .filter(user=user)
        .order_by("-end_date", "-id")
        .first()
    )

    if not user_package:
        logger.error(
            f"No active package found for user {user_id}",
            extra={
                'user_id': user_id,
                'request_id': request_id,
                'error_type': 'no_active_package'
            }
        )
        raise ValidationError(
            {"error": "You do not have an active package to upload review media."}
        )

    # Log package details
    package_name = getattr(user_package.package, 'name', 'Unknown')
    limit_mb = float(user_package.total_space or user_package.package.space or 5.0)
    
    logger.info(
        f"User package found: {package_name} (Limit: {limit_mb}MB)",
        extra={
            'user_id': user_id,
            'request_id': request_id,
            'package_name': package_name,
            'package_id': getattr(user_package.package, 'id', None),
            'limit_mb': limit_mb,
            'package_end_date': str(user_package.end_date) if user_package.end_date else None
        }
    )

    # Calculate current usage
    logger.debug(f"Calculating current storage usage for user {user_id}")
    used_space = get_user_total_space_used(user)
    
    logger.info(
        f"Current storage usage: {used_space}MB",
        extra={
            'user_id': user_id,
            'request_id': request_id,
            'used_space_mb': used_space,
            'limit_mb': limit_mb,
            'usage_percentage': round((used_space / limit_mb) * 100, 2) if limit_mb > 0 else 0
        }
    )

    # Calculate incoming file size
    incoming_size_mb = 0.0
    if incoming_file is not None and hasattr(incoming_file, "size"):
        incoming_size_mb = round(incoming_file.size / (1024 * 1024), 4)
        file_name = getattr(incoming_file, 'name', 'unknown')
        content_type = getattr(incoming_file, 'content_type', 'unknown')
        
        logger.info(
            f"Incoming file: {file_name} ({incoming_size_mb}MB, {content_type})",
            extra={
                'user_id': user_id,
                'request_id': request_id,
                'file_name': file_name,
                'file_size_mb': incoming_size_mb,
                'file_size_bytes': incoming_file.size,
                'content_type': content_type,
                'field_name': field_name
            }
        )

    # Calculate projected usage
    projected_usage = round(used_space + incoming_size_mb, 4)
    available_space = round(limit_mb - used_space, 2)
    
    logger.info(
        f"Storage calculation: Used {used_space}MB + Incoming {incoming_size_mb}MB = {projected_usage}MB (Limit: {limit_mb}MB)",
        extra={
            'user_id': user_id,
            'request_id': request_id,
            'used_space_mb': used_space,
            'incoming_size_mb': incoming_size_mb,
            'projected_usage_mb': projected_usage,
            'limit_mb': limit_mb,
            'available_space_mb': available_space,
            'will_exceed': projected_usage > limit_mb
        }
    )

    # Check if capacity will be exceeded
    if projected_usage > limit_mb:
        logger.warning(
            f"Storage capacity exceeded: {projected_usage}MB > {limit_mb}MB (Available: {available_space}MB, Required: {incoming_size_mb}MB)",
            extra={
                'user_id': user_id,
                'request_id': request_id,
                'error_type': 'storage_capacity_exceeded',
                'used_space_mb': used_space,
                'incoming_size_mb': incoming_size_mb,
                'projected_usage_mb': projected_usage,
                'limit_mb': limit_mb,
                'available_space_mb': available_space,
                'excess_mb': round(projected_usage - limit_mb, 2)
            }
        )
        
        raise ValidationError({
            field_name: f"Insufficient storage space. Used {used_space:.2f}MB of {limit_mb:.2f}MB. This upload requires {incoming_size_mb:.2f}MB.",
            "storage_info": {
                "used_mb": used_space,
                "limit_mb": limit_mb,
                "required_mb": incoming_size_mb,
                "available_mb": available_space
            }
        })

    logger.info(
        f"Storage capacity check passed: {projected_usage}MB <= {limit_mb}MB",
        extra={
            'user_id': user_id,
            'request_id': request_id,
            'result': 'capacity_check_passed',
            'used_space_mb': used_space,
            'incoming_size_mb': incoming_size_mb,
            'projected_usage_mb': projected_usage,
            'limit_mb': limit_mb,
            'remaining_space_mb': round(limit_mb - projected_usage, 2)
        }
    )

    return {
        "used_space_mb": used_space,
        "incoming_size_mb": incoming_size_mb,
        "limit_mb": limit_mb,
        "projected_usage_mb": projected_usage,
    }
