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
from .serializers import CategorySerializer, TeacherSerializer, TeachingCenterSerializer, ProjectSerializer, CouponSerializer, BannerSerializer, StudentSerializer, StudentCouponSerializer, StudentProjectSerializer, StudentHonorSerializer, StudentShowcaseSerializer
import requests
from django.db.models import Count, F
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 Types Distribution
        # Group by project_type
        project_types_data = Project.objects.values('project_type').annotate(count=Count('id'))
        
        # Map type codes to display names
        type_mapping = dict(Project.PROJECT_TYPE_CHOICES)
        pie_chart_data = []
        for item in project_types_data:
            type_code = item['project_type']
            name = type_mapping.get(type_code, type_code)
            pie_chart_data.append({
                'type': type_code,
                'name': name,
                'value': item['count']
            })
        
        # If empty, provide some defaults to avoid empty chart
        if not pie_chart_data:
            pie_chart_data = [
                {'name': '小主持语言培训', 'value': 0},
                {'name': '赛事管理', 'value': 0},
                {'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
        ]

        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,
            'coupon_pie_chart_data': coupon_pie_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']

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', '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)
