from rest_framework.decorators import action
from rest_framework import status
from rest_framework.response import Response
from django.db.models import Sum, Count, Q
from apps.profiles.models import Profile
from .serializers import EmptySerializer
from apps.core.views.base import BaseViewSet
from apps.core.utils import create_dynamic_pagination
from apps.deposite.models import Deposit
from apps.bnb.models import Transaction
from apps.profiles.models import SubscriptionHistory, GlobalRank, ProfileRank


class DownlineViewSet(BaseViewSet):
    queryset = Profile.objects.all()
    serializer_class = EmptySerializer

    permission_app_name = 'downline'
    permission_model_name = 'downline'
    view_permissions = ["my_downline", "level", "stats", "earning_report"]

    filter_backends = []

    def get_queryset(self):
        if self.request.user.is_authenticated:
            return Profile.objects.filter(user=self.request.user)
        return Profile.objects.none()

    def _get_or_create_profile(self, user):
        if not hasattr(user, 'profile'):
            Profile.objects.create(user=user)
            user.refresh_from_db()
        return user.profile

    def _deduplicate_downline(self, downline_data):
        seen = {}
        for item in downline_data:
            uid = item['user_id']
            if uid not in seen or seen[uid]['level'] > item['level']:
                seen[uid] = item
        return list(seen.values())

    def _attach_deposit_info(self, unique_data):
        user_ids = [m['user_id'] for m in unique_data]

        manual_map = {}
        crypto_map = {}

        CONVERSION_RATE = 125.0 

        if user_ids:
            manual_rows = (
                Deposit.objects
                .filter(user_id__in=user_ids)
                .values('user_id')
                .annotate(
                    approved=Sum('amount', filter=Q(status='approved')),
                    pending=Sum('amount', filter=Q(status='pending')),
                    rejected=Sum('amount', filter=Q(status='rejected')),
                    approved_count=Count('id', filter=Q(status='approved')),
                    pending_count=Count('id', filter=Q(status='pending')),
                    rejected_count=Count('id', filter=Q(status='rejected')),
                )
            )
            for row in manual_rows:
                manual_map[str(row['user_id'])] = {
                    'approved': round(float(row['approved'] or 0) / CONVERSION_RATE, 2),
                    'pending': round(float(row['pending'] or 0) / CONVERSION_RATE, 2),
                    'rejected': round(float(row['rejected'] or 0) / CONVERSION_RATE, 2),
                    'approved_count': row['approved_count'],
                    'pending_count': row['pending_count'],
                    'rejected_count': row['rejected_count'],
                }

            # Crypto (Binance) — already in USD
            crypto_rows = (
                Transaction.objects
                .filter(user_id__in=user_ids)
                .values('user_id')
                .annotate(
                    approved=Sum('amount', filter=Q(status='COMPLETED')),
                    pending=Sum('amount', filter=Q(status='PENDING')),
                    failed=Sum('amount', filter=Q(status='FAILED')),
                    approved_count=Count('id', filter=Q(status='COMPLETED')),
                    pending_count=Count('id', filter=Q(status='PENDING')),
                    failed_count=Count('id', filter=Q(status='FAILED')),
                )
            )
            for row in crypto_rows:
                crypto_map[str(row['user_id'])] = {
                    'approved': float(row['approved'] or 0),
                    'pending': float(row['pending'] or 0),
                    'failed': float(row['failed'] or 0),
                    'approved_count': row['approved_count'],
                    'pending_count': row['pending_count'],
                    'failed_count': row['failed_count'],
                }

        empty_manual = {
            'approved': 0.0, 'pending': 0.0, 'rejected': 0.0,
            'approved_count': 0, 'pending_count': 0, 'rejected_count': 0,
        }
        empty_crypto = {
            'approved': 0.0, 'pending': 0.0, 'failed': 0.0,
            'approved_count': 0, 'pending_count': 0, 'failed_count': 0,
        }

        for member in unique_data:
            uid = str(member['user_id'])
            m = manual_map.get(uid, empty_manual)
            c = crypto_map.get(uid, empty_crypto)

            member['deposit_info'] = {
                'manual': m,   # USD converted
                'crypto': c,   # already USD
                'total': {
                    'approved': round(m['approved'] + c['approved'], 2),
                    'pending': round(m['pending'] + c['pending'], 2),
                    'total': round(
                        m['approved'] + m['pending'] + m['rejected'] +
                        c['approved'] + c['pending'] + c['failed'], 2
                    ),
                }
            }

        return unique_data

    def _subscription_and_nft_summary(self, unique_data):
        """
        দেওয়া মেম্বার লিস্টের (যেকোনো লেভেল/জেনারেশন ফিল্টার করা হোক বা না হোক) জন্য —
        Total Subscription (তারা মোট কত ডলারের প্যাকেজ কিনেছে) এবং
        Total NFT (তাদের মধ্যে কতজনের NFT ব্যাজ/র‍্যাংক আছে) বের করে।
        """
        user_ids = [m['user_id'] for m in unique_data]
        if not user_ids:
            return {'total_subscription': 0.0, 'total_nft': 0}

        total_subscription = SubscriptionHistory.objects.filter(
            user_id__in=user_ids
        ).aggregate(total=Sum('amount'))['total'] or 0

        # NFT badge = GlobalRank আছে, অথবা কোনো active ProfileRank (category rank) আছে
        nft_user_ids = set(
            str(uid) for uid in GlobalRank.objects.filter(
                profile__user_id__in=user_ids
            ).values_list('profile__user_id', flat=True)
        )
        nft_user_ids |= set(
            str(uid) for uid in ProfileRank.objects.filter(
                profile__user_id__in=user_ids, is_active=True
            ).values_list('profile__user_id', flat=True).distinct()
        )

        return {
            'total_subscription': round(float(total_subscription), 2),
            'total_nft': len(nft_user_ids),
        }

    @action(detail=False, methods=['get'])
    def my_downline(self, request):
        try:
            profile = self._get_or_create_profile(request.user)
            downline_data = profile.get_downline()
            unique_downline = self._deduplicate_downline(downline_data)
            unique_downline = self._attach_deposit_info(unique_downline)

            PaginationClass = create_dynamic_pagination(page_size_value=10)
            paginator = PaginationClass()
            paginated_data = paginator.paginate_queryset(unique_downline, request, view=self)

            if paginated_data is not None:
                response = paginator.get_paginated_response(paginated_data)
                response.data.update({
                    "success": True,
                    "message": "Downline retrieved successfully"
                })
                return response

            return Response({
                "success": True,
                "message": "Downline retrieved successfully",
                "count": len(unique_downline),
                "data": unique_downline
            }, status=status.HTTP_200_OK)

        except RecursionError:
            return Response({
                "success": False,
                "message": "Referral tree has a loop or is too deep",
                "error_code": "5006"
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        except Exception as e:
            return Response({
                "success": False,
                "message": "Internal Server Error",
                "error_code": "500"
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['get'])
    def level(self, request):
        try:
            level_str = request.query_params.get('level', '1')

            try:
                target_level = int(level_str)
            except ValueError:
                return Response({
                    "success": False,
                    "message": "Level must be a number",
                    "error_code": "4006"
                }, status=status.HTTP_400_BAD_REQUEST)

            if not (1 <= target_level <= 15):
                return Response({
                    "success": False,
                    "message": "Level must be between 1 and 15",
                    "error_code": "4005"
                }, status=status.HTTP_400_BAD_REQUEST)

            profile = self._get_or_create_profile(request.user)
            search = request.query_params.get('search', '').strip()

            data = profile.get_level_downline(
                target_level,
                search_term=search if search else None
            )
            unique_data = self._deduplicate_downline(data)
            unique_data = self._attach_deposit_info(unique_data)

            active_network = sum(
                1 for m in unique_data if m['deposit_info']['total']['approved'] > 0
            )
            direct_network = len({
                m.get('root_id') for m in unique_data if m.get('root_id')
            })

            level_summary = {
                "total_approved_deposit": round(
                    sum(m['deposit_info']['total']['approved'] for m in unique_data), 2
                ),
                "total_pending_deposit": round(
                    sum(m['deposit_info']['total']['pending'] for m in unique_data), 2
                ),
                "total_deposit": round(
                    sum(m['deposit_info']['total']['total'] for m in unique_data), 2
                ),
            }

            network_summary = {
                "total_network": len(unique_data),
                "direct_network": direct_network,
                "active_network": active_network,
                "total_balance": level_summary["total_approved_deposit"],
                **self._subscription_and_nft_summary(unique_data),
            }

            return Response({
                "success": True,
                "message": f"Level {target_level} downline retrieved successfully",
                "count": len(unique_data),
                "level_summary": level_summary,
                "network_summary": network_summary,
                "data": unique_data
            }, status=status.HTTP_200_OK)

        except Exception:
            return Response({
                "success": False,
                "message": "Internal Server Error",
                "error_code": "500"
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['get'])
    def stats(self, request):
        try:
            profile = self._get_or_create_profile(request.user)
            downline_data = profile.get_downline()
            unique_downline = self._deduplicate_downline(downline_data)
            unique_downline = self._attach_deposit_info(unique_downline)

            total = len(unique_downline)
            level_1 = sum(1 for m in unique_downline if m.get('level') == 1)
            # "Active" means the referred user has actually deposited, not just an active account
            active = sum(
                1 for m in unique_downline if m['deposit_info']['total']['approved'] > 0
            )
            total_balance = round(
                sum(m['deposit_info']['total']['approved'] for m in unique_downline), 2
            )
            sub_nft = self._subscription_and_nft_summary(unique_downline)

            return Response({
                "success": True,
                "message": "Downline stats retrieved successfully",
                "data": {
                    'total_downline': total,
                    'level_1_count': level_1,
                    'active_count': active,
                    'total_balance': total_balance,
                    'total_subscription': sub_nft['total_subscription'],
                    'total_nft': sub_nft['total_nft'],
                    'deposit_summary': {
                        'total_deposit': round(
                            sum(m['deposit_info']['total']['total'] for m in unique_downline), 2
                        ),
                        'approved_deposit': round(
                            sum(m['deposit_info']['total']['approved'] for m in unique_downline), 2
                        ),
                        'pending_deposit': round(
                            sum(m['deposit_info']['total']['pending'] for m in unique_downline), 2
                        ),
                    }
                }
            }, status=status.HTTP_200_OK)

        except Exception:
            return Response({
                "success": False,
                "message": "Stack overflow or loop detected in referral tree",
                "error_code": "5004"
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=False, methods=['get'])
    def earning_report(self, request):
        try:
            profile = self._get_or_create_profile(request.user)
            report = profile.get_downline_earning_stats()

            return Response({
                "success": True,
                "message": "Earning report retrieved successfully",
                "data": report
            }, status=status.HTTP_200_OK)

        except Exception:
            return Response({
                "success": False,
                "message": "Internal Server Error",
                "error_code": "5005"
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)