from rest_framework import viewsets, serializers
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly, IsAuthenticated
from .models import Category, Teacher, TeachingCenter, Project, Coupon, Banner, Student, StudentCoupon, StudentProject, StudentHonor, StudentShowcase, Notification, NotificationBatch
from .serializers import CategorySerializer, TeacherSerializer, TeachingCenterSerializer, ProjectSerializer, CouponSerializer, BannerSerializer, StudentSerializer, StudentCouponSerializer, StudentProjectSerializer, StudentHonorSerializer, StudentShowcaseSerializer, NotificationSerializer, NotificationBatchSerializer
import requests
import json
from django.db.models import Count, F, Sum
from django.utils import timezone
from datetime import timedelta, datetime

class LoginView(APIView):
    permission_classes = [AllowAny]
    authentication_classes = []
    
    def post(self, request):
        code = request.data.get('code')
        if not code:
            return Response({'error': 'Code is required'}, status=status.HTTP_400_BAD_REQUEST)
            
        # Try to get real OpenID from WeChat API
        from django.conf import settings
        app_id = getattr(settings, 'WECHAT_APP_ID', None)
        app_secret = getattr(settings, 'WECHAT_APP_SECRET', None)
        
        openid = None
        
        if app_id and app_secret:
            try:
                # Exchange code for session_key and openid
                url = f"https://api.weixin.qq.com/sns/jscode2session?appid={app_id}&secret={app_secret}&js_code={code}&grant_type=authorization_code"
                res = requests.get(url, timeout=5)
                data = res.json()
                
                if 'openid' in data:
                    openid = data['openid']
                    # We could also store session_key if needed
                else:
                    print(f"WeChat Login Error: {data}")
            except Exception as e:
                print(f"WeChat Login Exception: {e}")
        
        # Fallback to mock if real login failed or not configured
        if not openid:
            # Mock OpenID based on code (or random if code is generic)
            # Use code as a seed for consistent mock OpenID if possible, or just use it directly
            # In a real scenario, code is one-time use, so we can't rely on it being consistent for the same user across logins 
            # unless we actually call WeChat API.
            # For dev/mock purposes, let's assume 'mock_devtools' or similar fixed code means same user.
            if code == 'mock_devtools':
                 openid = "mock_openid_devtools_user"
            else:
                 # If we have AppID but failed to get OpenID, it might be an invalid code.
                 # But to keep development smooth if API fails, we fallback.
                 # To fix "new user every time" in dev without API access, we use a fixed mock ID
                 # IF we are in DEBUG mode.
                 if settings.DEBUG and not (app_id and app_secret):
                     openid = "mock_openid_fixed_dev_user" 
                 else:
                     # If we have credentials but failed, or if we want distinct users per code:
                     openid = f"mock_openid_{code}"
        
        # Check if we already have a student with this openid
        student, created = Student.objects.get_or_create(
            openid=openid,
            defaults={
                'name': '微信用户',
                'wechat_nickname': '微信用户',
                'avatar': 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
            }
        )
        
        # Ensure openid is saved if it was missing (e.g. if user was created via other means or if we just want to be sure)
        if not student.openid:
            student.openid = openid
            student.save(update_fields=['openid'])
        
        serializer = StudentSerializer(student)
        return Response({
            'token': f"mock_token_{student.id}", # Mock token
            'user': serializer.data,
            'is_new_user': created
        })

class UserPhoneView(APIView):
    permission_classes = [AllowAny]
    authentication_classes = []
    
    def post(self, request):
        code = request.data.get('code')
        if not code:
            return Response({'error': 'Code is required'}, status=status.HTTP_400_BAD_REQUEST)
            
        # Default mock phone number for dev
        phone = "13800138000"

        # Try real implementation if configured in settings
        # Add WECHAT_APP_ID and WECHAT_APP_SECRET to your settings.py or local_settings.py
        from django.conf import settings
        app_id = getattr(settings, 'WECHAT_APP_ID', None)
        app_secret = getattr(settings, 'WECHAT_APP_SECRET', None)

        if app_id and app_secret:
            try:
                # 1. Get Access Token
                token_url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={app_id}&secret={app_secret}"
                token_res = requests.get(token_url, timeout=5).json()
                access_token = token_res.get('access_token')
                
                if access_token:
                    # 2. Get Phone Number
                    # Note: The code from getPhoneNumber is valid for 5 mins and one-time use
                    phone_url = f"https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}"
                    phone_res = requests.post(phone_url, json={"code": code}, timeout=5).json()
                    
                    if phone_res.get('errcode') == 0:
                        phone_info = phone_res.get('phone_info')
                        phone = phone_info.get('phoneNumber')
                    else:
                        print(f"WeChat Phone API Error: {phone_res}")
                else:
                    print(f"WeChat Token API Error: {token_res}")
            except Exception as e:
                print(f"WeChat API Exception: {e}")
        
        # Identify student
        student = None
        auth = request.headers.get('Authorization')
        if auth and 'Bearer' in auth:
            try:
                # Extract ID from mock_token_{id}
                token_str = auth.split(' ')[1]
                if token_str.startswith('mock_token_'):
                    student_id = int(token_str.split('_')[-1])
                    student = Student.objects.get(id=student_id)
            except Exception as e:
                print(f"Token parse error: {e}")
                pass
        
        if not student:
            student = Student.objects.first()
            
        if student:
            student.phone = phone
            # Also ensure openid is set if not present (mock behavior)
            if not student.openid:
                student.openid = f"mock_openid_phone_{phone}"
            student.save()

            # 自动激活/发放注册优惠券
            
        return Response({'phone': phone})

class DashboardStatsView(APIView):
    """
    API for Dashboard Statistics
    """
    permission_classes = [AllowAny]

    def get(self, request):
        # Query params
        try:
            range_days = int(request.query_params.get('range', '14'))
            if range_days < 7:
                range_days = 7
            if range_days > 30:
                range_days = 30
        except Exception:
            range_days = 14
        dimension = request.query_params.get('dimension', 'organization')
        # 1. Panel Counts
        student_count = Student.objects.count()
        organizations_count = Teacher.objects.count()
        project_count = Project.objects.count()
        # Count assigned coupons (StudentCoupon)
        issued_coupon_count = StudentCoupon.objects.count()
        # Active resources
        active_banner_count = Banner.objects.filter(is_active=True).count()
        active_showcase_count = StudentShowcase.objects.filter(is_active=True).count()
        active_project_count = Project.objects.filter(is_active=True).count()

        # 2. Pie Chart: Project Category Proportion
        # Aggregate student counts by project type
        projects_data = Project.objects.filter(is_active=True).values('project_type').annotate(total_students=Sum('students')).order_by('-total_students')
        
        # Define colors for each project type
        type_colors = {
            'training': '#36cfc9',    # Cyan
            'competition': '#b37feb', # Purple
            'grading': '#409EFF',     # Blue
        }

        # Map type codes to display names for legend
        type_mapping = dict(Project.PROJECT_TYPE_CHOICES)
        
        # Construct Legend Data
        pie_chart_legend = []
        for type_code, color in type_colors.items():
            pie_chart_legend.append({
                'name': type_mapping.get(type_code, type_code),
                'color': color
            })

        pie_chart_data = []
        for item in projects_data:
            type_code = item['project_type']
            # Use default color if type not found
            color = type_colors.get(type_code, '#909399')
            pie_chart_data.append({
                'type': type_code,
                'name': type_mapping.get(type_code, type_code),
                'value': item['total_students'] or 0,
                'itemStyle': { 'color': color }
            })
        
        # If too many, maybe limit? For now, let's keep all active ones as user requested "specific names".
        # But if we have 0 students, maybe skip?
        # pie_chart_data = [d for d in pie_chart_data if d['value'] > 0]
        
        # If empty, provide some defaults to avoid empty chart
        if not pie_chart_data:
            pie_chart_data = [
                {'name': '暂无数据', 'value': 0}
            ]

        # 3. Bar Chart: Popular dimension (organization/project)
        if dimension == 'project':
            popular_projects = Project.objects.annotate(enroll_num=Count('enrollments')).order_by('-enroll_num')[:7]
            bar_names = [p.title for p in popular_projects]
            bar_counts = [p.enroll_num for p in popular_projects]
            bar_title = '热门项目'
        else:
            popular_organizations = Teacher.objects.annotate(student_num=Count('students')).order_by('-student_num')[:7]
            bar_names = [t.name for t in popular_organizations]
            bar_counts = [t.student_num for t in popular_organizations]
            bar_title = '热门机构'

        # 4. Line Chart: 14-Day Trends
        today = timezone.now().date()
        days = [today - timedelta(days=i) for i in range(range_days - 1, -1, -1)]

        def daily_counts(model, date_field):
            counts = []
            for d in days:
                start = timezone.make_aware(datetime.combine(d, datetime.min.time()))
                end = timezone.make_aware(datetime.combine(d, datetime.max.time()))
                counts.append(model.objects.filter(**{f"{date_field}__range": (start, end)}).count())
            return counts

        student_daily = daily_counts(Student, 'created_at')
        enroll_daily = daily_counts(StudentProject, 'enrolled_at')
        coupon_assign_daily = daily_counts(StudentCoupon, 'assigned_at')
        x_axis = [d.strftime('%m-%d') for d in days]

        line_chart_data = {
            'students': {
                'expectedData': student_daily,
                'actualData': student_daily,
                'xAxis': x_axis
            },
            'organizations': {
                'expectedData': [organizations_count] * len(days),
                'actualData': [organizations_count] * len(days),
                'xAxis': x_axis
            },
            'projects': {
                'expectedData': enroll_daily,
                'actualData': enroll_daily,
                'xAxis': x_axis
            },
            'coupons': {
                 'expectedData': coupon_assign_daily,
                 'actualData': coupon_assign_daily,
                 'xAxis': x_axis
            }
        }

        # 5. Coupon Status Pie Chart
        coupon_status_counts = StudentCoupon.objects.values('status').annotate(count=Count('id'))
        status_mapping = dict(StudentCoupon.STATUS_CHOICES)
        coupon_pie_chart_data = [
            {'status': item['status'], 'name': status_mapping.get(item['status'], item['status']), 'value': item['count']}
            for item in coupon_status_counts
        ]

        # 6. Student City Distribution
        city_counts = Student.objects.values('city').annotate(count=Count('id')).order_by('-count')
        
        def clean_city_name(name):
            if not name:
                return name
            
            # Mapping from keyword to DataV standard name (Full Names required for DataV GeoJSON)
            mapping = {
                '北京': '北京市', '天津': '天津市', '上海': '上海市', '重庆': '重庆市',
                '河北': '河北省', '山西': '山西省', '辽宁': '辽宁省', '吉林': '吉林省', '黑龙江': '黑龙江省',
                '江苏': '江苏省', '浙江': '浙江省', '安徽': '安徽省', '福建': '福建省', '江西': '江西省', '山东': '山东省',
                '河南': '河南省', '湖北': '湖北省', '湖南': '湖南省', '广东': '广东省', '海南': '海南省',
                '四川': '四川省', '贵州': '贵州省', '云南': '云南省', '陕西': '陕西省', '甘肃': '甘肃省', '青海': '青海省', '台湾': '台湾省',
                '内蒙古': '内蒙古自治区', '广西': '广西壮族自治区', '西藏': '西藏自治区', '宁夏': '宁夏回族自治区', '新疆': '新疆维吾尔自治区',
                '香港': '香港特别行政区', '澳门': '澳门特别行政区'
            }
            
            for key, full_name in mapping.items():
                if key in name:
                    return full_name
            
            return name

        map_chart_data = [
            {'name': clean_city_name(item['city']), 'value': item['count']}
            for item in city_counts if item['city']
        ]

        return Response({
            'panel_data': {
                'students': student_count,
                'organizations': organizations_count,
                'projects': project_count,
                'projects_active': active_project_count,
                'coupons': issued_coupon_count,
                'banners_active': active_banner_count,
                'showcases_active': active_showcase_count
            },
            'pie_chart_data': pie_chart_data,
            'pie_chart_legend': pie_chart_legend,
            'coupon_pie_chart_data': coupon_pie_chart_data,
            'map_chart_data': map_chart_data,
            'bar_chart_data': {
                'title': bar_title,
                'names': bar_names,
                'counts': bar_counts
            },
            'line_chart_data': line_chart_data
        })

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    permission_classes = [AllowAny]
    pagination_class = None  # Return all categories without pagination for the app
    search_fields = ['name']

    def list(self, request, *args, **kwargs):
        # Instead of database categories, return Project.PROJECT_TYPE_CHOICES
        data = []
        # Define some default colors if needed, or mapped by type
        colors = {
            'training': 'bg-cyan-100 text-cyan-600',
            'competition': 'bg-purple-100 text-purple-600',
            'grading': 'bg-blue-100 text-blue-600'
        }
        
        for code, name in Project.PROJECT_TYPE_CHOICES:
            data.append({
                'id': code,
                'name': name,
                'color': colors.get(code, 'bg-gray-100 text-gray-600')
            })
        return Response(data)

class TeacherViewSet(viewsets.ModelViewSet):
    queryset = Teacher.objects.all()
    serializer_class = TeacherSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    pagination_class = None # Usually few teachers, return all for selects
    search_fields = ['name', 'phone']

class TeachingCenterViewSet(viewsets.ModelViewSet):
    queryset = TeachingCenter.objects.all()
    serializer_class = TeachingCenterSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    pagination_class = None
    search_fields = ['name', 'phone']

class ProjectViewSet(viewsets.ModelViewSet):
    # queryset = Project.objects.select_related('category').all()
    queryset = Project.objects.select_related('teacher').all()
    serializer_class = ProjectSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    # search_fields = ['title', 'category__name']
    search_fields = ['title', 'teacher__name']
    # filterset_fields = ['category', 'project_type']
    filterset_fields = ['teacher', 'project_type']
    ordering_fields = ['sort_order', 'is_top', 'created_at', 'students', 'price']
    ordering = ['-is_top', 'sort_order', '-created_at']

    def get_queryset(self):
        queryset = super().get_queryset()
        # Admin users (staff) can see all projects
        if self.request.user.is_staff:
            return queryset
        # Public/Students can only see active projects
        return queryset.filter(is_active=True)

class CouponViewSet(viewsets.ModelViewSet):
    queryset = Coupon.objects.all()
    serializer_class = CouponSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    search_fields = ['title']
    filterset_fields = ['status']

    @action(detail=True, methods=['post'], url_path='distribute')
    def distribute(self, request, pk=None):
        coupon = self.get_object()
        student_ids = request.data.get('student_ids', [])
        if not student_ids:
            return Response({'error': '未选择学员'}, status=status.HTTP_400_BAD_REQUEST)
            
        if coupon.status == 'expired':
            return Response({'error': '该优惠券已过期'}, status=status.HTTP_400_BAD_REQUEST)
            
        students = Student.objects.filter(id__in=student_ids)
        created_count = 0
        for student in students:
            StudentCoupon.objects.create(
                student=student,
                coupon=coupon,
                status='assigned'
            )
            created_count += 1
            
        return Response({'message': f'成功分发给 {created_count} 位学员'}, status=status.HTTP_200_OK)

class BannerViewSet(viewsets.ModelViewSet):
    queryset = Banner.objects.all()
    serializer_class = BannerSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

class StudentViewSet(viewsets.ModelViewSet):
    queryset = Student.objects.prefetch_related('enrollments__project').all()
    serializer_class = StudentSerializer
    permission_classes = [IsAuthenticated]
    search_fields = ['name', 'phone']

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_200_OK)

    @action(detail=True, methods=['post'], url_path='assign_coupon')
    def assign_coupon(self, request, pk=None):
        student = self.get_object()
        coupon_id = request.data.get('coupon_id')
        if not coupon_id:
            return Response({'error': '未提供优惠券ID'}, status=status.HTTP_400_BAD_REQUEST)
        
        try:
            coupon = Coupon.objects.get(id=coupon_id)
        except Coupon.DoesNotExist:
            return Response({'error': '优惠券不存在'}, status=status.HTTP_404_NOT_FOUND)
            
        if coupon.status == 'expired':
             return Response({'error': '该优惠券已过期'}, status=status.HTTP_400_BAD_REQUEST)

        # Create assignment
        assignment = StudentCoupon.objects.create(
            student=student,
            coupon=coupon,
            status='assigned'
        )
        
        return Response(StudentCouponSerializer(assignment).data, status=status.HTTP_201_CREATED)

    @action(detail=False, methods=['delete'], url_path='clear')
    def clear(self, request):
        Student.objects.all().delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

class StudentCouponViewSet(viewsets.ModelViewSet):
    queryset = StudentCoupon.objects.all()
    serializer_class = StudentCouponSerializer
    permission_classes = [IsAuthenticated]
    filterset_fields = ['student', 'status']

    def perform_update(self, serializer):
        instance = serializer.instance
        # Check if status is being changed to 'used'
        new_status = serializer.validated_data.get('status')
        if new_status == 'used' and instance.status != 'used':
            serializer.save(used_at=timezone.now())
        else:
            serializer.save()

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_200_OK)

from django.db import transaction

class StudentProjectViewSet(viewsets.ModelViewSet):
    queryset = StudentProject.objects.all()
    serializer_class = StudentProjectSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filterset_fields = ['student', 'status', 'project']

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        # 1. Check for re-enrollment (Friendly Error)
        student_id = request.data.get('student')
        project_id = request.data.get('project')
        
        if student_id and project_id:
            if StudentProject.objects.filter(student_id=student_id, project_id=project_id).exists():
                return Response(
                    {'detail': '您已报名该项目，请勿重复报名'}, 
                    status=status.HTTP_400_BAD_REQUEST
                )

        # 2. Create Enrollment
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        # Increment project student count
        Project.objects.filter(id=project_id).update(students=F('students') + 1)

        # 3. Handle Coupon Redemption
        coupon_id = request.data.get('coupon_id')
        if coupon_id:
            try:
                # Lock the coupon row to prevent race conditions
                # Use filter to be safe against MultipleObjectsReturned (though id should be unique)
                scs = StudentCoupon.objects.select_for_update().filter(id=coupon_id, student_id=student_id)
                if not scs.exists():
                    raise serializers.ValidationError({'coupon_id': '优惠券不存在'})
                
                # Take the first one if multiple exist (defensive programming)
                sc = scs.first()
                
                if sc.status != 'assigned':
                    # Rollback if coupon is invalid
                    raise serializers.ValidationError({'coupon_id': '该优惠券不可用'})
                
                sc.status = 'used'
                sc.used_at = timezone.now()
                sc.save()
                
            except Exception as e:
                 if isinstance(e, serializers.ValidationError):
                     raise e
                 print(f"Coupon redemption error: {e}")
                 raise serializers.ValidationError({'coupon_id': '优惠券处理失败'})

        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_destroy(self, instance):
        project_id = instance.project_id
        super().perform_destroy(instance)
        # Decrement project student count
        Project.objects.filter(id=project_id).update(students=F('students') - 1)

class StudentHonorViewSet(viewsets.ModelViewSet):
    queryset = StudentHonor.objects.all()
    serializer_class = StudentHonorSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filterset_fields = ['student']

class StudentShowcaseViewSet(viewsets.ModelViewSet):
    queryset = StudentShowcase.objects.all()
    serializer_class = StudentShowcaseSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filterset_fields = ['student']

    def get_queryset(self):
        queryset = super().get_queryset()
        # Admin users (staff) can see all showcases
        if self.request.user.is_staff:
            return queryset
        # Public/Students can only see active showcases
        # Check if specific query param 'is_active' is passed, or default to active only for public
        # Actually, standard behavior: filter active=True for public
        return queryset.filter(is_active=True)

class UserProfileView(APIView):
    permission_classes = [AllowAny]
    authentication_classes = []
    
    def get_student_from_token(self, request):
        from .models import Student
        student = None
        auth = request.headers.get('Authorization')
        if auth and 'Bearer' in auth:
            try:
                token_str = auth.split(' ')[1]
                if token_str.startswith('mock_token_'):
                    student_id = int(token_str.split('_')[-1])
                    student = Student.objects.get(id=student_id)
            except Exception:
                pass
        return student

    def get(self, request):
        student = self.get_student_from_token(request)
        if not student:
             return Response({'error': 'Not authenticated'}, status=status.HTTP_401_UNAUTHORIZED)
             
        serializer = StudentSerializer(student)
        return Response(serializer.data)
    
    def post(self, request):
        student = self.get_student_from_token(request)
        if not student:
             return Response({'error': 'Not authenticated'}, status=status.HTTP_401_UNAUTHORIZED)

        payload = request.data or {}
        fields = ['name', 'phone', 'age', 'company_name', 'position', 'address', 'city', 'wechat_nickname', 'avatar']
        for f in fields:
            if f in payload:
                setattr(student, f, payload.get(f))
        
        # Ensure OpenID is preserved or set if missing
        if not student.openid and 'openid' in payload:
            student.openid = payload['openid']

        student.save()
        return Response(StudentSerializer(student).data)
        
class UserCouponsView(APIView):
    permission_classes = [AllowAny]
    
    def get_student_from_token(self, request):
        from .models import Student
        student = None
        auth = request.headers.get('Authorization')
        if auth and 'Bearer' in auth:
            try:
                token_str = auth.split(' ')[1]
                if token_str.startswith('mock_token_'):
                    student_id = int(token_str.split('_')[-1])
                    student = Student.objects.get(id=student_id)
            except Exception:
                pass
        return student

    def get(self, request):
        student = self.get_student_from_token(request)
        if not student:
            return Response([])
            
        coupons = StudentCoupon.objects.filter(student=student).exclude(status='revoked')
        serializer = StudentCouponSerializer(coupons, many=True)
        return Response(serializer.data)

    def post(self, request):
        student = self.get_student_from_token(request)
        if not student:
            return Response({'error': '请先登录'}, status=status.HTTP_401_UNAUTHORIZED)
        
        coupon_id = request.data.get('coupon_id')
        if not coupon_id:
             return Response({'error': '参数错误'}, status=status.HTTP_400_BAD_REQUEST)

        try:
            coupon = Coupon.objects.get(id=coupon_id)
        except Coupon.DoesNotExist:
            return Response({'error': '优惠券不存在'}, status=status.HTTP_404_NOT_FOUND)

        # Check availability
        if coupon.status != 'available':
             return Response({'error': '优惠券不可领取'}, status=status.HTTP_400_BAD_REQUEST)
        
        if coupon.expiry < timezone.now().date():
             return Response({'error': '优惠券已过期'}, status=status.HTTP_400_BAD_REQUEST)

        # Check if already claimed
        if StudentCoupon.objects.filter(student=student, coupon=coupon).exists():
             return Response({'error': '您已领取过该优惠券'}, status=status.HTTP_400_BAD_REQUEST)

        # Claim
        StudentCoupon.objects.create(
            student=student,
            coupon=coupon,
            status='assigned'
        )
        
        return Response({'message': '领取成功'}, status=status.HTTP_201_CREATED)

class AvailableCouponsView(APIView):
    permission_classes = [AllowAny]

    def get(self, request):
        # Get all available coupons with issue_type='project' (publicly visible)
        # Also check expiry
        today = timezone.now().date()
        coupons = Coupon.objects.filter(
            status='available',
            expiry__gte=today,
            issue_type='project'
        )
        serializer = CouponSerializer(coupons, many=True)
        return Response(serializer.data)

class UserProjectsView(APIView):
    permission_classes = [AllowAny]
    
    def get_student_from_token(self, request):
        from .models import Student
        student = None
        auth = request.headers.get('Authorization')
        if auth and 'Bearer' in auth:
            try:
                token_str = auth.split(' ')[1]
                if token_str.startswith('mock_token_'):
                    student_id = int(token_str.split('_')[-1])
                    student = Student.objects.get(id=student_id)
            except Exception:
                pass
        return student

    def get(self, request):
        student = self.get_student_from_token(request)
        if not student:
            return Response([])
        projects = StudentProject.objects.filter(student=student)
        serializer = StudentProjectSerializer(projects, many=True)
        return Response(serializer.data)

class UserHonorsView(APIView):
    permission_classes = [AllowAny]
    
    def get_student_from_token(self, request):
        from .models import Student
        student = None
        auth = request.headers.get('Authorization')
        if auth and 'Bearer' in auth:
            try:
                token_str = auth.split(' ')[1]
                if token_str.startswith('mock_token_'):
                    student_id = int(token_str.split('_')[-1])
                    student = Student.objects.get(id=student_id)
            except Exception:
                pass
        return student

    def get(self, request):
        student = self.get_student_from_token(request)
        if not student:
            return Response([])
        honors = StudentHonor.objects.filter(student=student)
        serializer = StudentHonorSerializer(honors, many=True)
        return Response(serializer.data)

class NotificationViewSet(viewsets.ModelViewSet):
    serializer_class = NotificationSerializer
    permission_classes = [AllowAny]

    def get_queryset(self):
        # 仅返回当前登录用户的通知
        # 需结合认证系统，假设 request.user.student 存在，或通过 openid 关联
        user = self.request.user
        
        # Attempt to find the student associated with the request
        # Note: In our current mock auth setup, 'user' might be the Django Admin user.
        # But the frontend sends 'Authorization: Bearer mock_token_{id}'.
        # DRF's default authentication might not parse this mock token into request.user.
        # However, for consistency with the plan, let's implement the token parsing logic here 
        # OR rely on a custom authentication class.
        
        # Since other views use `get_student_from_token` helper, but ViewSets usually rely on Authentication classes.
        # Let's assume we can extract the student from the token here as well.
        
        student = None
        auth = self.request.headers.get('Authorization')
        if auth and 'Bearer' in auth:
            try:
                token_str = auth.split(' ')[1]
                if token_str.startswith('mock_token_'):
                    student_id = int(token_str.split('_')[-1])
                    student = Student.objects.get(id=student_id)
            except Exception:
                pass
        
        if student:
             return Notification.objects.filter(student=student)
        
        # If accessed by admin user (Django user)
        if user.is_staff:
             return Notification.objects.all()
             
        return Notification.objects.none()

    @action(detail=False, methods=['get'])
    def unread_count(self, request):
        count = self.get_queryset().filter(is_read=False).count()
        return Response({'count': count})

    @action(detail=True, methods=['post'])
    def read(self, request, pk=None):
        notification = self.get_object()
        notification.is_read = True
        notification.save()
        return Response({'status': 'marked as read'})
    
    @action(detail=False, methods=['post'])
    def read_all(self, request):
        self.get_queryset().filter(is_read=False).update(is_read=True)
        return Response({'status': 'all marked as read'})

class NotificationBatchViewSet(viewsets.ModelViewSet):
    queryset = NotificationBatch.objects.all()
    serializer_class = NotificationBatchSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    
    def create(self, request, *args, **kwargs):
        # 1. Create Batch
        data = request.data
        send_mode = data.get('send_mode', 'custom')
        target_criteria = data.get('target_criteria', {})
        
        # Ensure target_criteria is string for TextField
        if isinstance(target_criteria, dict):
            target_criteria_str = json.dumps(target_criteria)
        else:
            target_criteria_str = target_criteria
            try:
                target_criteria = json.loads(target_criteria)
            except:
                target_criteria = {}

        # Validate basics
        # Use partial=True if some fields are missing but not required, though ModelSerializer usually handles it.
        # But we modify data to set target_criteria to string.
        # So we should create a mutable copy of data if it's immutable (QueryDict)
        if hasattr(data, '_mutable'):
            data._mutable = True
            
        data['target_criteria'] = target_criteria_str
        
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        batch = serializer.save()
        
        # 2. Find Recipients
        recipients = Student.objects.none()
        
        if send_mode == 'custom':
            student_ids = target_criteria.get('student_ids', [])
            select_all = target_criteria.get('select_all', False)
            if select_all:
                recipients = Student.objects.all()
            elif student_ids:
                recipients = Student.objects.filter(id__in=student_ids)
                
        elif send_mode == 'project':
            project_id = target_criteria.get('project_id')
            statuses = target_criteria.get('statuses', []) # ['enrolled', 'graduated']
            if project_id:
                query = {'enrolled_projects__id': project_id} # Filter students by enrolled project
                # But we want to filter by the status in that project enrollment?
                # StudentProject has status.
                # So we should query StudentProject first.
                
                sp_query = {'project_id': project_id}
                if statuses:
                    sp_query['status__in'] = statuses
                
                student_ids = StudentProject.objects.filter(**sp_query).values_list('student_id', flat=True)
                recipients = Student.objects.filter(id__in=student_ids)
                
        elif send_mode == 'coupon':
            coupon_id = target_criteria.get('coupon_id')
            coupon_status = target_criteria.get('status') # 'assigned', 'used', etc.
            if coupon_id:
                query = {'coupon_id': coupon_id}
                if coupon_status:
                    query['status'] = coupon_status
                
                student_ids = StudentCoupon.objects.filter(**query).values_list('student_id', flat=True)
                recipients = Student.objects.filter(id__in=student_ids)

        # Deduplicate recipients if needed (though IDs set should handle it, but values_list returns list)
        recipients = recipients.distinct()

        # 3. Create Notifications
        notification_list = []
        count = 0
        for student in recipients:
            notification_list.append(Notification(
                student=student,
                batch=batch,
                title=batch.title,
                content=batch.content,
                notification_type=batch.notification_type
            ))
            count += 1
            
        if notification_list:
            Notification.objects.bulk_create(notification_list)
            
        # 4. Update Count
        batch.recipient_count = count
        batch.save()
        
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=['get'])
    def recipients(self, request, pk=None):
        batch = self.get_object()
        notifications = Notification.objects.filter(batch=batch).select_related('student', 'student__teaching_center', 'student__responsible_teacher')
        
        results = []
        # Parse criteria once
        try:
            criteria = json.loads(batch.target_criteria)
        except:
            criteria = {}
            
        for n in notifications:
            student = n.student
            
            project_info = ""
            status_info = student.get_status_display() 
            
            if batch.send_mode == 'project':
                project_id = criteria.get('project_id')
                # Optimally we should prefetch this, but for now loop is okay for admin view
                sp = StudentProject.objects.filter(student=student, project_id=project_id).first()
                if sp:
                    project_info = sp.project.title
                    status_info = sp.get_status_display()
            elif batch.send_mode == 'coupon':
                 coupon_id = criteria.get('coupon_id')
                 sc = StudentCoupon.objects.filter(student=student, coupon_id=coupon_id).first()
                 if sc:
                     project_info = sc.coupon.title
                     status_info = sc.get_status_display()

            results.append({
                'id': student.id,
                'student_name': student.name,
                'student_phone': student.phone,
                'teaching_center': student.teaching_center.name if student.teaching_center else (student.responsible_teacher.name if student.responsible_teacher else '-'),
                'project_info': project_info,
                'status_info': status_info,
                'is_read': n.is_read,
                'read_at': None
            })
            
        return Response(results)

