from django.shortcuts import render
from rest_framework import generics, status, viewsets, filters
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import get_user_model, authenticate
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail
from django.db.models import Q
from django.conf import settings
from django.core.exceptions import ValidationError
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample, OpenApiResponse, OpenApiTypes, extend_schema_view, inline_serializer
from rest_framework import serializers
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from bazi.models import Person as BaziPerson
from .serializers import (
    UserRegistrationSerializer,
    UserSerializer,
    PasswordResetRequestSerializer,
    PasswordResetConfirmSerializer,
    LoginSerializer,
    LiuyaoSerializer,
    LiuyaoRequestSerializer,
    LiuyaoCalculatorSerializer,
    BaziSerializer,
    BaziRequestSerializer,
    TongshuCalendarQuerySerializer,
    Calendar10kQuerySerializer,
    Calendar10kSerializer,
    PasswordChangeSerializer,
    AccountDeletionRequestSerializer,
    TempLoginSerializer,
    TempRegisterSerializer,
    TempUserCreateSerializer,
    ReportSubmissionSerializer,
    BaziReportSerializer,
    NumberReportSerializer,
    LiuYaoReportSerializer,
    ConversationSerializer,
    ConversationListSerializer,
    CreateConversationSerializer,
    SendMessageSerializer,
    MessageSerializer,
    ConversationConfigSerializer,
)

from datetime import datetime, date, timedelta
from calendar import monthrange
from liuyao.models import liuyao
from rest_framework.authentication import SessionAuthentication
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from main.authentication import ActivityTrackingJWTAuthentication
from iching.utils import bz as bazi, liuyao as liuyao_util
from iching.utils.bazi_relations import evaluate_pair
from iching.utils.numberpower import NumberPower
from main.visitor import utils
from django.utils import timezone
from rest_framework.pagination import PageNumberPagination
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.html import strip_tags
from accounts.models import AccountDeletionRequest
from accounts.models import UserProfile
from rest_framework.decorators import action
import logging
from ai.utils.number_analysis import analyze_number
from django.http import Http404
from rest_framework.exceptions import PermissionDenied
from ai.services.factory import LLMServiceFactory
from ai.utils.config import get_ai_config
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator


logger = logging.getLogger(__name__)

class CustomPageNumberPagination(PageNumberPagination):
    """
    Custom pagination class that allows specifying page size via query parameter.
    If not specified, uses the default from settings.
    """
    page_size_query_param = 'page_size'
    max_page_size = 100  # Maximum page size to prevent abuse
    
    def get_page_size(self, request):
        try:
            page_size = int(request.query_params.get(self.page_size_query_param, 0))
            if page_size > 0:
                return min(page_size, self.max_page_size)
        except (TypeError, ValueError):
            pass
        return getattr(settings, 'PAGINATE_BY', {}).get('default', 25)

User = get_user_model()

# Add a clear comment to avoid confusion between serializers
# BaziComponentSerializer is for individual Bazi components
# BaziSerializer is the ModelSerializer for the entire Person model
class BaziComponentSerializer(serializers.Serializer):
    g = serializers.IntegerField(help_text="Heavenly stem (天干) index")
    e = serializers.IntegerField(help_text="Earthly branch (地支) index")

class BaziPillarSerializer(serializers.Serializer):
    hour = BaziComponentSerializer(help_text="Hour pillar")
    day = BaziComponentSerializer(help_text="Day pillar")
    month = BaziComponentSerializer(help_text="Month pillar")
    year = BaziComponentSerializer(help_text="Year pillar")
    date = serializers.CharField(help_text="Formatted date and time")
    empty = serializers.DictField(help_text="Empty positions for divination")

class PalaceSerializer(serializers.Serializer):
    palace = serializers.IntegerField(help_text="Palace number")
    pos = serializers.IntegerField(help_text="Position")
    shi = serializers.IntegerField(help_text="Shi position")
    yin = serializers.IntegerField(help_text="Yin position")

class YaoPositionsSerializer(serializers.Serializer):
    y1 = serializers.IntegerField(help_text="Position 1")
    y2 = serializers.IntegerField(help_text="Position 2")
    y3 = serializers.IntegerField(help_text="Position 3")
    y4 = serializers.IntegerField(help_text="Position 4")
    y5 = serializers.IntegerField(help_text="Position 5")
    y6 = serializers.IntegerField(help_text="Position 6")

class GuaSerializer(serializers.Serializer):
    gua = serializers.IntegerField(help_text="Hexagram number")
    clash6 = serializers.BooleanField(help_text="Six clash flag")
    harmony6 = serializers.BooleanField(help_text="Six harmony flag")
    palace = PalaceSerializer(help_text="Palace information")
    g = YaoPositionsSerializer(help_text="Heavenly stems for each line")
    e = YaoPositionsSerializer(help_text="Earthly branches for each line")
    yao = YaoPositionsSerializer(help_text="Line structure (broken/unbroken)")
    changeyao = YaoPositionsSerializer(help_text="Changing lines information")

class ChangedGuaSerializer(serializers.Serializer):
    gua = serializers.IntegerField(help_text="Hexagram number")
    clash6 = serializers.BooleanField(help_text="Six clash flag")
    harmony6 = serializers.BooleanField(help_text="Six harmony flag")
    palace = PalaceSerializer(help_text="Palace information")

class LiuyaoStructureSerializer(serializers.Serializer):
    ogua = GuaSerializer(help_text="Original hexagram data")
    cgua = ChangedGuaSerializer(help_text="Changed hexagram data")

class RelationshipsSerializer(serializers.Serializer):
    ogua = serializers.DictField(help_text="Relationships in original hexagram")
    cgua = serializers.DictField(help_text="Relationships in changed hexagram")

class GuaDataSerializer(serializers.Serializer):
    question = serializers.CharField(help_text="The question asked for divination")
    bz = BaziPillarSerializer(help_text="Bazi (八字) information")
    god6 = serializers.IntegerField(help_text="The Six Gods (六神) determination")
    ly = LiuyaoStructureSerializer(help_text="Liuyao structure")
    rel = RelationshipsSerializer(help_text="Relationships between elements")

class LiuyaoDetailedResponseSerializer(serializers.Serializer):
    gua = GuaDataSerializer(help_text="Main object containing all divination results")
    question = serializers.CharField(help_text="The question asked for divination")
    yao = serializers.CharField(help_text="String representation of the hexagram lines")

@extend_schema(
    description="Register a new user",
    request=OpenApiTypes.OBJECT,
    parameters=[
        OpenApiParameter(
            name="phone",
            type=str,
            location=OpenApiParameter.QUERY,
            required=True,
            description="Phone number used for login (max length: 16 characters)",
            examples=[
                OpenApiExample(
                    'Valid Phone',
                    value="1234567890"
                ),
            ]
        ),
        OpenApiParameter(
            name="email",
            type=str,
            location=OpenApiParameter.QUERY,
            required=True,
            description="Email address (required, max length: 50 characters, must be unique)",
            examples=[
                OpenApiExample(
                    'Valid Email',
                    value="user@example.com"
                ),
            ]
        ),
        OpenApiParameter(
            name="password",
            type=str,
            location=OpenApiParameter.QUERY,
            required=True,
            description="Password (must be at least 8 characters and meet Django's password validation rules)",
            examples=[
                OpenApiExample(
                    'Valid Password',
                    value="securepass123"
                ),
            ]
        ),
        OpenApiParameter(
            name="password2",
            type=str,
            location=OpenApiParameter.QUERY,
            required=True,
            description="Password confirmation (must match password)",
            examples=[
                OpenApiExample(
                    'Valid Password Confirmation',
                    value="securepass123"
                ),
            ]
        ),
        OpenApiParameter(
            name="first_name",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="User's first name (optional, max length: 150 characters)",
            examples=[
                OpenApiExample(
                    'Valid First Name',
                    value="John"
                ),
            ]
        ),
        OpenApiParameter(
            name="last_name",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="User's last name (optional, max length: 150 characters)",
            examples=[
                OpenApiExample(
                    'Valid Last Name',
                    value="Doe"
                ),
            ]
        ),
        OpenApiParameter(
            name="gender",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="User's gender (M for male, F for female, N for not specified)",
            examples=[
                OpenApiExample(
                    'Valid Gender',
                    value="M"
                ),
            ]
        ),
        OpenApiParameter(
            name="birth_date",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="User's birth date in YYYY-MM-DD format (optional)",
            examples=[
                OpenApiExample(
                    'Valid Birth Date',
                    value="1990-01-01"
                ),
            ]
        ),
        OpenApiParameter(
            name="birth_time",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="User's birth time in HH:MM format (optional)",
            examples=[
                OpenApiExample(
                    'Valid Birth Time',
                    value="12:00"
                ),
            ]
        ),
        OpenApiParameter(
            name="twin_type",
            type=int,
            location=OpenApiParameter.QUERY,
            required=False,
            description="User's twin type (0 for not a twin, 1 for elder twin, 2 for younger twin) (optional)",
            examples=[
                OpenApiExample(
                    'Valid Twin Type',
                    value=0
                ),
            ]
        ),
        OpenApiParameter(
            name="father_dob",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="Father's birth date in YYYY-MM-DD format (required if twin_type is 1) (optional)",
            examples=[
                OpenApiExample(
                    'Valid Father DOB',
                    value="1960-01-01"
                ),
            ]
        ),
        OpenApiParameter(
            name="mother_dob",
            type=str,
            location=OpenApiParameter.QUERY,
            required=False,
            description="Mother's birth date in YYYY-MM-DD format (required if twin_type is 2) (optional)",
            examples=[
                OpenApiExample(
                    'Valid Mother DOB',
                    value="1965-01-01"
                ),
            ]
        ),
    ],
    responses={
        201: OpenApiResponse(
            response=UserRegistrationSerializer,
            description='User successfully registered',
            examples=[
                OpenApiExample(
                    'Success Response',
                    value={
                        "user": {
                            "id": 1,
                            "phone": "1234567890",
                            "email": "user@example.com",
                            "first_name": "John",
                            "last_name": "Doe",
                            "gender": "M",
                            "profile": {
                                "birth_date": "1990-01-01",
                                "birth_time": "12:00",
                                "twin_type": 0,
                                "father_dob": "1960-01-01",
                                "mother_dob": "1965-01-01"
                            }
                        },
                        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
                        "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
                    }
                ),
            ]
        ),
        400: OpenApiResponse(
            description='Bad request (validation error)',
            examples=[
                OpenApiExample(
                    'Validation Error',
                    value={
                        "phone": ["This field is required."],
                        "password": [
                            "This password is too common.",
                            "Password fields didn't match.",
                            "This password is entirely numeric."
                        ],
                        "email": ["Enter a valid email address."],
                        "gender": ["This field is required."],
                        "twin_type": ["Father's birth date is required for elder twin."],
                        "father_dob": ["This field is required when twin_type is 1."],
                        "mother_dob": ["This field is required when twin_type is 2."]
                    }
                ),
            ]
        )
    },
    examples=[
        OpenApiExample(
            'Valid Registration',
            value={
                'phone': '1234567890',
                'email': 'user@example.com',
                'password': 'securepass123',
                'password2': 'securepass123',
                'first_name': 'John',
                'last_name': 'Doe',
                'gender': 'M',
                'profile': {
                    'birth_date': '1990-01-01',
                    'birth_time': '12:00',
                    'twin_type': 0,
                    'father_dob': '1960-01-01',
                    'mother_dob': '1965-01-01'
                }
            },
            request_only=True,
        ),
    ]
)
class RegisterView(generics.CreateAPIView):
    serializer_class = UserRegistrationSerializer
    permission_classes = [AllowAny]

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        
        # Generate tokens
        refresh = RefreshToken.for_user(user)
        access_token = str(refresh.access_token)
        
        # Get profile data if exists
        profile_data = {}
        try:
            # Get or create the profile
            profile, created = UserProfile.objects.get_or_create(user=user)
            profile_data = {
                'birth_date': profile.birth_date,
                'birth_time': profile.birth_time,
                'twin_type': profile.twin_type,
                'father_dob': profile.father_dob,
                'mother_dob': profile.mother_dob
            }
        except Exception:
            # If there's any error getting the profile, just return empty profile data
            pass
        
        headers = self.get_success_headers(serializer.data)
        return Response({
            'user': {
                'id': user.id,
                'phone': user.phone,
                'email': user.email,
                'first_name': user.first_name,
                'last_name': user.last_name,
                'profile': profile_data
            },
            'access_token': access_token,
            'refresh_token': str(refresh)
        }, status=status.HTTP_201_CREATED, headers=headers)

@extend_schema(
    description="Login user and get JWT tokens",
    responses={200: {"type": "object", "properties": {
        "refresh": {"type": "string", "description": "JWT refresh token"},
        "access": {"type": "string", "description": "JWT access token"}
    }}}
)
class LoginView(generics.GenericAPIView):
    permission_classes = (AllowAny,)
    serializer_class = LoginSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['phone']
        password = serializer.validated_data['password']
        
        try:
            # First try with the original phone number
            user = User.objects.get(phone=phone)
            if user.check_password(password):
                # Fire activity tracking signal for successful login
                from main.signals import fire_jwt_authenticated_signal
                fire_jwt_authenticated_signal(user, request)
                
                refresh = RefreshToken.for_user(user)
                return Response({
                    'refresh': str(refresh),
                    'access': str(refresh.access_token),
                })
            
            # If password is incorrect and phone starts with '60', try without the '6'
            if phone.startswith('60'):
                try:
                    user = User.objects.get(phone=phone[1:])
                    if user.check_password(password):
                        # Fire activity tracking signal for successful login
                        from main.signals import fire_jwt_authenticated_signal
                        fire_jwt_authenticated_signal(user, request)
                        
                        refresh = RefreshToken.for_user(user)
                        return Response({
                            'refresh': str(refresh),
                            'access': str(refresh.access_token),
                        })
                except User.DoesNotExist:
                    pass
                    
            return Response(
                {'error': 'Invalid credentials'},
                status=status.HTTP_401_UNAUTHORIZED
            )
            
        except User.DoesNotExist:
            # If user not found with original phone, try without '6' if applicable
            if phone.startswith('60'):
                try:
                    user = User.objects.get(phone=phone[1:])
                    if user.check_password(password):
                        # Fire activity tracking signal for successful login
                        from main.signals import fire_jwt_authenticated_signal
                        fire_jwt_authenticated_signal(user, request)
                        
                        refresh = RefreshToken.for_user(user)
                        return Response({
                            'refresh': str(refresh),
                            'access': str(refresh.access_token),
                        })
                except User.DoesNotExist:
                    pass
                    
            return Response(
                {'error': 'Invalid credentials'},
                status=status.HTTP_401_UNAUTHORIZED
            )

@extend_schema(
    description="Login for temporary users - migrates temp user data to existing user account",
    request=inline_serializer(
        name='TempLoginRequest',
        fields={
            'phone': serializers.CharField(help_text='Phone number for login'),
            'password': serializers.CharField(help_text='Password for login')
        }
    ),
    responses={
        200: inline_serializer(
            name='TempLoginResponse',
            fields={
                'access': serializers.CharField(help_text='JWT access token'),
                'refresh': serializers.CharField(help_text='JWT refresh token'),
                'transfer_summary': serializers.DictField(help_text='Summary of transferred data'),
                'message': serializers.CharField(help_text='Success message')
            }
        ),
        400: OpenApiResponse(description="Invalid credentials or user not found"),
        403: OpenApiResponse(description="User not authenticated as temporary user")
    },
    examples=[
        OpenApiExample(
            name='Success Response',
            value={
                "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                "transfer_summary": {
                    "bazi_records": 2,
                    "liuyao_records": 1,
                    "owner_flag_cleared": False
                },
                "message": "Login successful. Your temporary data has been transferred to your account."
            },
            response_only=True
        )
    ]
)
class TempLoginView(generics.GenericAPIView):
    """
    Login endpoint for temporary users.
    Migrates temporary user's data to existing user account and returns new JWT tokens.
    """
    permission_classes = (AllowAny,)
    authentication_classes = [ActivityTrackingJWTAuthentication]
    serializer_class = TempLoginSerializer

    def post(self, request):
        # Verify user is authenticated as temporary user
        if not request.user.is_authenticated:
            return Response(
                {'error': 'Authentication required - temporary user token needed'},
                status=status.HTTP_401_UNAUTHORIZED
            )
        
        if not getattr(request.user, 'is_temporary_user', False):
            return Response(
                {'error': 'This endpoint is only for temporary users'},
                status=status.HTTP_403_FORBIDDEN
            )
        
        # Validate request data
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['phone']
        password = serializer.validated_data['password']
        temp_user = request.user
        
        try:
            # Find target user and verify credentials
            try:
                target_user = User.objects.get(phone=phone)
            except User.DoesNotExist:
                # Try without '6' prefix if phone starts with '60'
                if phone.startswith('60'):
                    try:
                        target_user = User.objects.get(phone=phone[1:])
                    except User.DoesNotExist:
                        return Response(
                            {'error': 'Invalid credentials'},
                            status=status.HTTP_401_UNAUTHORIZED
                        )
                else:
                    return Response(
                        {'error': 'Invalid credentials'},
                        status=status.HTTP_401_UNAUTHORIZED
                    )
            
            # Verify password
            if not target_user.check_password(password):
                # Try with '6' prefix if original phone doesn't work
                if phone.startswith('60'):
                    try:
                        alt_user = User.objects.get(phone=phone[1:])
                        if not alt_user.check_password(password):
                            return Response(
                                {'error': 'Invalid credentials'},
                                status=status.HTTP_401_UNAUTHORIZED
                            )
                        target_user = alt_user
                    except User.DoesNotExist:
                        return Response(
                            {'error': 'Invalid credentials'},
                            status=status.HTTP_401_UNAUTHORIZED
                        )
                else:
                    return Response(
                        {'error': 'Invalid credentials'},
                        status=status.HTTP_401_UNAUTHORIZED
                    )
            
            # Check if target user is also temporary (shouldn't happen)
            if getattr(target_user, 'is_temporary_user', False):
                return Response(
                    {'error': 'Cannot login to another temporary user account'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            # Transfer data from temp user to target user
            from accounts.utils import transfer_user_data, cleanup_temp_user
            
            transfer_summary = transfer_user_data(temp_user, target_user)
            
            # Clean up temporary user
            try:
                cleanup_temp_user(temp_user)
            except ValueError:
                # If cleanup fails, log it but don't fail the request
                pass
            
            # Fire activity tracking signal for successful login
            from main.signals import fire_jwt_authenticated_signal
            fire_jwt_authenticated_signal(target_user, request)
            
            # Generate new tokens for target user
            refresh = RefreshToken.for_user(target_user)
            
            return Response({
                'access': str(refresh.access_token),
                'refresh': str(refresh),
                'transfer_summary': transfer_summary,
                'message': 'Login successful. Your temporary data has been transferred to your account.'
            }, status=status.HTTP_200_OK)
            
        except Exception as e:
            return Response(
                {'error': f'Login failed: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

@extend_schema(
    description="Convert temporary user to permanent user - only phone & email required, all other fields optional",
    request=TempRegisterSerializer,
    responses={
        200: inline_serializer(
            name='TempRegisterResponse',
            fields={
                'user': UserSerializer(),
                'access': serializers.CharField(help_text='JWT access token (new if password updated)'),
                'refresh': serializers.CharField(help_text='JWT refresh token (new if password updated)'),
                'message': serializers.CharField(help_text='Success message')
            }
        ),
        400: OpenApiResponse(description="Validation errors"),
        401: OpenApiResponse(description="Authentication required"),
        403: OpenApiResponse(description="User not authenticated as temporary user")
    },
    examples=[
        OpenApiExample(
            name='Minimal Registration',
            value={
                "phone": "1234567890",
                "email": "user@example.com"
            },
            request_only=True
        ),
        OpenApiExample(
            name='Full Registration with All Optional Fields', 
            value={
                "phone": "1234567890",
                "email": "user@example.com",
                "password": "newpassword123",
                "password2": "newpassword123",
                "first_name": "John",
                "last_name": "Doe",
                "gender": "M",
                "profile": {
                    "birth_date": "1990-01-01",
                    "birth_time": "12:00:00",
                    "twin_type": 0,
                    "father_dob": "1965-05-15",
                    "mother_dob": "1967-03-22",
                    "can_regenerate_ai": False
                }
            },
            request_only=True
        ),
        OpenApiExample(
            name='Registration with Profile Only (No Password)',
            value={
                "phone": "9876543210", 
                "email": "profile-user@example.com",
                "first_name": "Jane",
                "last_name": "Smith",
                "gender": "F",
                "profile": {
                    "birth_date": "1985-12-25",
                    "birth_time": "09:30:00",
                    "twin_type": 1,
                    "father_dob": "1960-08-10"
                }
            },
            request_only=True
        ),
        OpenApiExample(
            name='Success Response with Full Profile',
            value={
                "user": {
                    "id": 147,
                    "phone": "1234567890",
                    "email": "user@example.com",
                    "first_name": "John",
                    "last_name": "Doe",
                    "gender": "M",
                    "profile": {
                        "birth_date": "1990-01-01",
                        "birth_time": "12:00:00",
                        "twin_type": 0,
                        "father_dob": "1965-05-15",
                        "mother_dob": "1967-03-22",
                        "can_regenerate_ai": False
                    }
                },
                "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                "message": "Account successfully converted to permanent user with new authentication tokens"
            },
            response_only=True
        ),
        OpenApiExample(
            name='Success Response without Password Update',
            value={
                "user": {
                    "id": 148,
                    "phone": "9876543210",
                    "email": "profile-user@example.com",
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "gender": "F",
                    "profile": {
                        "birth_date": "1985-12-25",
                        "birth_time": "09:30:00",
                        "twin_type": 1,
                        "father_dob": "1960-08-10",
                        "mother_dob": None,
                        "can_regenerate_ai": False
                    }
                },
                "message": "Account successfully converted to permanent user"
            },
            response_only=True
        )
    ]
)
class TempRegisterView(generics.GenericAPIView):
    """
    Convert temporary user to permanent user.
    Only phone & email are required - other fields are optional.
    Returns updated JWT tokens if password is changed.
    """
    permission_classes = (AllowAny,)
    authentication_classes = [ActivityTrackingJWTAuthentication]
    serializer_class = TempRegisterSerializer

    def post(self, request):
        # Verify user is authenticated as temporary user
        if not request.user.is_authenticated:
            return Response(
                {'error': 'Authentication required - temporary user token needed'},
                status=status.HTTP_401_UNAUTHORIZED
            )
        
        if not getattr(request.user, 'is_temporary_user', False):
            return Response(
                {'error': 'This endpoint is only for temporary users'},
                status=status.HTTP_403_FORBIDDEN
            )
        
        temp_user = request.user
        
        # Validate request data with current user context
        serializer = self.get_serializer(data=request.data, context={'user': temp_user})
        serializer.is_valid(raise_exception=True)
        
        try:
            # Extract validated data
            validated_data = serializer.validated_data
            profile_data = validated_data.pop('profile', None)
            password = validated_data.pop('password', None)
            validated_data.pop('password2', None)  # Remove confirmation field
            
            # Update user fields
            for field, value in validated_data.items():
                setattr(temp_user, field, value)
            
            # Convert to permanent user
            temp_user.is_temporary_user = False
            
            # Update password if provided
            password_updated = False
            if password:
                temp_user.set_password(password)
                password_updated = True
            
            temp_user.save()
            
            # Update profile if data provided
            if profile_data:
                from accounts.models import UserProfile
                profile, created = UserProfile.objects.get_or_create(user=temp_user)
                
                for field, value in profile_data.items():
                    if value is not None:  # Only update fields that have values
                        setattr(profile, field, value)
                
                profile.save()
                
                # Refresh the user instance to get updated profile for serialization
                temp_user.refresh_from_db()
            
            # Generate new tokens if password was updated
            if password_updated:
                refresh = RefreshToken.for_user(temp_user)
                access_token = str(refresh.access_token)
                refresh_token = str(refresh)
            else:
                # Use existing tokens
                access_token = None
                refresh_token = None
            
            # Serialize updated user with refreshed profile data
            try:
                user_serializer = UserSerializer(temp_user)
                user_data = user_serializer.data
            except Exception as e:
                # Handle any serialization errors gracefully - get fresh user instance
                temp_user.refresh_from_db()
                try:
                    user_serializer = UserSerializer(temp_user)
                    user_data = user_serializer.data
                except Exception as e2:
                    # Last fallback with basic data
                    user_data = {
                        'id': temp_user.id,
                        'phone': temp_user.phone,
                        'email': temp_user.email,
                        'first_name': temp_user.first_name,
                        'last_name': temp_user.last_name,
                        'gender': temp_user.gender,
                        'profile': None  # Skip profile data if there's an issue
                    }
            
            response_data = {
                'user': user_data,
                'message': 'Account successfully converted to permanent user'
            }
            
            # Add new tokens if password was updated
            if password_updated:
                response_data['access'] = access_token
                response_data['refresh'] = refresh_token
                response_data['message'] += ' with new authentication tokens'
            
            return Response(response_data, status=status.HTTP_200_OK)
            
        except Exception as e:
            return Response(
                {'error': f'Registration failed: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

@extend_schema(
    description="Refresh JWT token and track user activity",
    request=inline_serializer(
        name='TokenRefreshRequest',
        fields={
            'refresh': serializers.CharField(help_text='Refresh token')
        }
    ),
    responses={
        200: inline_serializer(
            name='TokenRefreshResponse',
            fields={
                'access': serializers.CharField(help_text='New access token'),
                'refresh': serializers.CharField(help_text='New refresh token (if rotation enabled)')
            }
        ),
        401: OpenApiResponse(description="Invalid or expired refresh token")
    }
)
class CustomTokenRefreshView(generics.GenericAPIView):
    """
    Custom JWT token refresh view that automatically tracks user activity.
    
    This extends the default Simple JWT token refresh functionality to fire
    activity tracking signals whenever a refresh token is used.
    """
    permission_classes = (AllowAny,)
    
    def post(self, request):
        from rest_framework_simplejwt.views import TokenRefreshView
        from rest_framework_simplejwt.tokens import RefreshToken
        from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
        from main.signals import fire_jwt_authenticated_signal
        
        refresh_token = request.data.get('refresh')
        if not refresh_token:
            return Response(
                {'error': 'Refresh token is required'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        try:
            # Validate and decode the refresh token to get user info
            refresh = RefreshToken(refresh_token)
            user_id = refresh.payload.get('user_id')
            
            if user_id:
                try:
                    user = User.objects.get(id=user_id)
                    
                    # Fire the activity tracking signal
                    fire_jwt_authenticated_signal(user, request)
                    
                except User.DoesNotExist:
                    return Response(
                        {'error': 'User not found'},
                        status=status.HTTP_401_UNAUTHORIZED
                    )
            
            # Use the default TokenRefreshView logic to generate new tokens
            refresh_view = TokenRefreshView()
            refresh_view.request = request
            refresh_view.format_kwarg = None
            
            response = refresh_view.post(request)
            return response
            
        except TokenError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_401_UNAUTHORIZED
            )
        except Exception as e:
            logger.error(f"Token refresh error: {e}")
            return Response(
                {'error': 'Token refresh failed'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

@extend_schema(
    description="Request password reset email",
    responses={200: {"type": "object", "properties": {
        "message": {"type": "string", "description": "Success message"}
    }}}
)
class PasswordResetRequestView(generics.GenericAPIView):
    permission_classes = (AllowAny,)
    serializer_class = PasswordResetRequestSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        try:
            # First try to find user by email
            try:
                user = User.objects.get(email=serializer.validated_data['email'])
            except User.DoesNotExist:
                # If email not found, try to find by phone
                user = User.objects.get(phone=serializer.validated_data['email'])
            
            # Use Django's password reset functionality
            from django.contrib.auth.forms import PasswordResetForm
            from django.contrib.auth.tokens import default_token_generator
            from django.contrib.sites.shortcuts import get_current_site
            
            # Create a form with the user's email - use custom form for hardcoded domain
            from accounts.forms import CustomPasswordResetForm
            form = CustomPasswordResetForm({'email': user.email})
            
            if form.is_valid():
                # Send email using the form's save method, which uses the template
                opts = {
                    'use_https': request.is_secure(),
                    'token_generator': default_token_generator,
                    'from_email': settings.DEFAULT_FROM_EMAIL,
                    'email_template_name': 'accounts/password_reset_email.html',
                    'subject_template_name': 'accounts/password_reset_subject.txt',
                    'request': request,
                    'html_email_template_name': None,
                    'extra_email_context': None,
                }
                form.save(**opts)
                
                return Response(
                    {'message': 'Password reset email has been sent.'},
                    status=status.HTTP_200_OK
                )
            
        except User.DoesNotExist:
            return Response(
                {'error': 'No user found with this email or phone number.'},
                status=status.HTTP_404_NOT_FOUND
            )

@extend_schema(
    description="Confirm password reset with token",
    responses={200: {"type": "object", "properties": {
        "message": {"type": "string", "description": "Success message"}
    }}}
)
class PasswordResetConfirmView(generics.GenericAPIView):
    permission_classes = [AllowAny]
    serializer_class = PasswordResetConfirmSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        uidb64 = serializer.validated_data['uidb64']
        token = serializer.validated_data['token']
        password = serializer.validated_data['password']
        
        try:
            # Decode the user ID from uidb64
            from django.utils.http import urlsafe_base64_decode
            from django.contrib.auth.tokens import default_token_generator
            
            uid = urlsafe_base64_decode(uidb64).decode()
            user = User.objects.get(pk=uid)
            
            # Check if the token is valid
            if default_token_generator.check_token(user, token):
                # Set the new password
                user.set_password(password)
                user.save()
                
                return Response({"message": "Password has been reset successfully."}, status=status.HTTP_200_OK)
            else:
                return Response({"error": "Invalid or expired token."}, status=status.HTTP_400_BAD_REQUEST)
                
        except (TypeError, ValueError, OverflowError, User.DoesNotExist):
            return Response({"error": "Invalid or expired token."}, status=status.HTTP_400_BAD_REQUEST)

@extend_schema(
    description="Change password for authenticated user",
    request=PasswordChangeSerializer,
    responses={
        200: OpenApiResponse(
            {"type": "object", "properties": {
                "message": {"type": "string", "description": "Success message"}
            }},
            description="Password changed successfully"
        ),
        400: OpenApiResponse(
            {"type": "object", "properties": {
                "current_password": {"type": "array", "items": {"type": "string"}, "description": "Current password validation errors"},
                "new_password": {"type": "array", "items": {"type": "string"}, "description": "New password validation errors"}
            }},
            description="Validation errors"
        ),
        401: OpenApiResponse(
            description="Unauthorized - Authentication required"
        )
    },
    examples=[
        OpenApiExample(
            name="Request Example",
            value={
                "current_password": "current-password",
                "new_password": "new-password123",
                "new_password2": "new-password123"
            },
            request_only=True
        ),
        OpenApiExample(
            name="Success Response",
            value={"message": "Password changed successfully."},
            response_only=True,
            status_codes=["200"]
        ),
        OpenApiExample(
            name="Validation Error",
            value={
                "current_password": ["Current password is incorrect."],
                "new_password": ["Password fields didn't match."]
            },
            response_only=True,
            status_codes=["400"]
        )
    ]
)
class PasswordChangeView(generics.GenericAPIView):
    permission_classes = [IsAuthenticated]
    serializer_class = PasswordChangeSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # Change the password
        user = request.user
        user.set_password(serializer.validated_data["new_password"])
        user.save()
        
        # Return success message
        return Response(
            {"message": "Password changed successfully."},
            status=status.HTTP_200_OK
        )

@extend_schema(
    description="View and update user profile",
    responses={
        200: OpenApiResponse(
            response=UserSerializer,
            description='User profile retrieved/updated successfully',
            examples=[
                OpenApiExample(
                    name='Success Response',
                    value={
                        "id": 1,
                        "phone": "1234567890",
                        "email": "user@example.com",
                        "first_name": "John",
                        "last_name": "Doe",
                        "gender": "M",
                        "profile": {
                            "birth_date": "1990-01-01",
                            "birth_time": "12:00:00",
                            "twin_type": 0,
                            "father_dob": "1960-01-01",
                            "mother_dob": "1965-01-01",
                            "can_regenerate_ai": False
                        }
                    },
                    response_only=True,
                    status_codes=["200"]
                )
            ]
        ),
        401: OpenApiResponse(
            description='Unauthorized - Authentication required'
        )
    }
)
class UserProfileView(generics.RetrieveUpdateAPIView):
    permission_classes = (IsAuthenticated,)
    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user

# Liuyao Calculator API
@method_decorator(csrf_exempt, name='dispatch')
class LiuyaoCalculatorAPIView(APIView):
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]  # Support both JWT and session auth for temp users
    permission_classes = [AllowAny]
    
    @extend_schema(
        summary="Calculate Liuyao result",
        description="""
This endpoint calculates the Liuyao (六爻) divination result based on the provided input data.

**Response Structure:**

- `gua`: Main object containing all divination results
  - `question`: The question asked for divination
  - `bz`: Bazi (八字) information containing time pillars
    - `hour`, `day`, `month`, `year`: The four pillars with heavenly stems (`g`) and earthly branches (`e`)
    - `date`: Formatted date and time
    - `empty`: Empty positions for divination
  - `god6`: The Six Gods (六神) determination
  - `ly`: Liuyao structure
    - `ogua`: Original hexagram data
      - `gua`: Index of one of the 64 gua
      - `clash6`, `harmony6`: Six relationships flags
      - `palace`: Palace information for divination
        - `palace`: The palace where the gua belongs to, 0 (乾) to 7 (兑)
        - `pos`: The gua position in the palace, from 1 to 8
        - `shi`: The shi(世) position (1 to 6)
        - `yin`: The yin(应) position (1 to 6)
      - `g`, `e`: Heavenly stem and earthly branch for each line
      - `q`: The 6 relationship flags (六亲), 0: "兄弟", 1: "子孙", 2: "妻财", 3: "官鬼", 4: "父母"
      - `hg`, `he`, `hq`: Similar to `g`, `e` and `q`, only this is used for hidden gods (伏神); `hq` will only list out the yao that has hidden god, for eg: `"hq": {"y6": 1}` indicates the 6th yao is a yao with hidden god of "子孙"
      - `hqf`: while 'hq' only list out 六亲 that is not appear in the main gua, hqf (hq full) will list out the full hidden 六亲
      - `gb`: gua body (卦身), value range from 0 (子) to 11 (亥)
      - `sb`: shi body (世身), value range from 1 to 6 (which yao)
      - `yao`: Structure of 6 lines (yin/yang)
      - `changeyao`: Information on which line(s) are changing, 1 indicates change yao, 0 indicates non-changing yao
    - `cgua`: Changed hexagram data (same structure as ogua)
  - `rel`: Relationships between elements
    - `ogua`: Relationships in original hexagram
    - `cgua`: Relationships in changed hexagram

Additional fields may be present in the complete response.
""",
        request=LiuyaoCalculatorSerializer,
        parameters=[
            LiuyaoCalculatorSerializer,
        ],
        responses={
            200: LiuyaoDetailedResponseSerializer,
            400: OpenApiResponse(
                description="Error message",
                examples=[
                    OpenApiExample(
                        name='Validation Error',
                        value={
                            'error': 'Invalid date or time format'
                        },
                        status_codes=['400']
                    )
                ]
            )
        },
        examples=[
            OpenApiExample(
                name='Complete Response Example',
                value={
                    'gua': {
                        'question': 'test question',
                        'bz': {
                            'hour': {'g': 3, 'e': 11},
                            'day': {'g': 1, 'e': 11},
                            'month': {'g': 5, 'e': 3},
                            'year': {'g': 1, 'e': 5},
                            'date': '2025-03-07 21:48:46',
                            'empty': {'0': 8, '1': 9}
                        },
                        'god6': 0,
                        'ly': {
                            'ogua': {
                                'gua': 22,
                                'clash6': False,
                                'harmony6': False,
                                'palace': {'palace': 0, 'pos': 6, 'shi': 5, 'yin': 2},
                                'g': {'y1': 1, 'y2': 1, 'y3': 1, 'y4': 2, 'y5': 2, 'y6': 2},
                                'e': {'y1': 7, 'y2': 5, 'y3': 3, 'y4': 10, 'y5': 0, 'y6': 2},
                                'q': {'y1': 4, 'y2': 3, 'y3': 0, 'y4': 0, 'y5': 2, 'y6': 3},
                                'hg': {'y1': 1, 'y2': 1, 'y3': 1, 'y4': 9, 'y5': 9, 'y6': 9},
                                'he': {'y1': 7, 'y2': 5, 'y3': 3, 'y4': 1, 'y5': 11, 'y6': 9},
                                'hq': {'y6': 1},
                                'hqf': {'y1': 0, 'y2': 4, 'y3': 3, 'y4': 0, 'y5': 2, 'y6': 1},
                                'gb': 8,
                                'sb': 2,
                                'yao': {'y1': 0, 'y2': 0, 'y3': 0, 'y4': 0, 'y5': 0, 'y6': 1},
                                'changeyao': {'y1': 0, 'y2': 0, 'y3': 0, 'y4': 1, 'y5': 0, 'y6': 0}
                            },
                            'cgua': {
                                'gua': 34,
                                'clash6': False,
                                'harmony6': False,
                                'palace': {'palace': 0, 'pos': 7, 'shi': 4, 'yin': 1}
                            }
                        },
                        'rel': {
                            'ogua': {
                                'q1': {
                                    'day': ['cs12-3'],
                                    'month': ['counter', 'cs12-7']
                                }
                            },
                            'cgua': {}
                        }
                    },
                    'question': 'test question',
                    'yao': '0|0|0|000|0|1'
                },
                response_only=True,
                status_codes=['200']
            )
        ],
    )
    def get(self, request, *args, **kwargs):
        # Validate query parameters using the serializer
        serializer = LiuyaoCalculatorSerializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)
        validated_data = serializer.validated_data

        # Extract necessary data for calculations
        usecur = validated_data.get('usecur', False)
        
        try:
            # Parse the date and time
            if usecur:
                date_obj = datetime.now(bazi.gMYTimezone)
            else:
                year = validated_data.get('year')
                month = validated_data.get('month')
                day = validated_data.get('day')
                time_str = validated_data.get('time', '00:00')
                
                hours, minutes = map(int, time_str.split(':'))
                date_obj = datetime(year, month, day, hours, minutes)
            
            # Calculate bazi
            bz = bazi.getDateTimeGodEarthStem(
                date_obj.year, date_obj.month, date_obj.day, date_obj.hour, date_obj.minute
            )
            bz['date'] = date_obj.strftime('%Y-%m-%d %H:%M:%S')
            bz['empty'] = bazi.calcEarthEmpty(bz['day']['g'], bz['day']['e'])
            
            # Calculate 6 God
            god6 = liuyao_util.calc6God(bz['day']['g'])
            
            # Calculate 6 Yao
            y1 = validated_data.get('y1', '')
            y2 = validated_data.get('y2', '')
            y3 = validated_data.get('y3', '')
            y4 = validated_data.get('y4', '')
            y5 = validated_data.get('y5', '')
            y6 = validated_data.get('y6', '')
            
            ly = liuyao_util.calc6Yao(y1, y2, y3, y4, y5, y6)
            
            # Calculate Relationship
            rel = liuyao_util.calcRelationship(ly, bz)
            
            # Format response data
            question = validated_data.get('question', '')
            gua_data = {
                'question': question,
                'bz': bz,
                'god6': god6,
                'ly': ly,
                'rel': rel,
            }
            
            # Build the full response
            result = {
                'gua': gua_data,
                'question': question,
                'yao': f"{validated_data.get('y1', '')}|{validated_data.get('y2', '')}|{validated_data.get('y3', '')}|{validated_data.get('y4', '')}|{validated_data.get('y5', '')}|{validated_data.get('y6', '')}"
            }
            
            return Response(result, status=status.HTTP_200_OK)
            
        except Exception as e:
            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

    @extend_schema(
        summary="Calculate and save Liuyao result",
        description="""
This endpoint calculates the Liuyao (六爻) divination result and optionally saves it to the database.

**Authentication Behavior:**
- **Public users**: Calculation only (results returned but not saved)
- **Authenticated users**: Calculation and automatic saving to database

**Request Body:** All calculation parameters should be provided in JSON format instead of query parameters.

**Response Structure:** Same as GET method, but with additional fields for authenticated users:
- `id`: Record ID (only for authenticated users who saved the divination)
- `created_by`: User ID who created the divination (only for authenticated users)
- `created_at`: Creation timestamp (only for authenticated users)
- `updated_at`: Last update timestamp (only for authenticated users)
- `message`: Status message about calculation and saving
""",
        request=LiuyaoCalculatorSerializer,
        responses={
            200: OpenApiResponse(
                description="Calculation completed (public user - not saved)",
                examples=[
                    OpenApiExample(
                        name='Public User Response',
                        value={
                            'gua': {
                                'question': 'test question',
                                'bz': {
                                    'hour': {'g': 3, 'e': 11},
                                    'day': {'g': 1, 'e': 11},
                                    'month': {'g': 5, 'e': 3},
                                    'year': {'g': 1, 'e': 5},
                                    'date': '2025-03-07 21:48:46',
                                    'empty': {'0': 8, '1': 9}
                                },
                                'god6': 0,
                                'ly': {
                                    'ogua': {
                                        'gua': 22,
                                        'clash6': False,
                                        'harmony6': False,
                                        'palace': {'palace': 0, 'pos': 6, 'shi': 5, 'yin': 2}
                                    },
                                    'cgua': {
                                        'gua': 34,
                                        'clash6': False,
                                        'harmony6': False,
                                        'palace': {'palace': 0, 'pos': 7, 'shi': 4, 'yin': 1}
                                    }
                                },
                                'rel': {
                                    'ogua': {},
                                    'cgua': {}
                                }
                            },
                            'question': 'test question',
                            'yao': '0|0|0|000|0|1',
                            'message': 'Liuyao divination calculated (login to save)'
                        },
                        status_codes=['200']
                    )
                ]
            ),
            201: OpenApiResponse(
                description="Calculation completed and saved (authenticated user)",
                examples=[
                    OpenApiExample(
                        name='Authenticated User Response',
                        value={
                            'gua': {
                                'question': 'test question',
                                'bz': {
                                    'hour': {'g': 3, 'e': 11},
                                    'day': {'g': 1, 'e': 11},
                                    'month': {'g': 5, 'e': 3},
                                    'year': {'g': 1, 'e': 5},
                                    'date': '2025-03-07 21:48:46',
                                    'empty': {'0': 8, '1': 9}
                                },
                                'god6': 0,
                                'ly': {
                                    'ogua': {
                                        'gua': 22,
                                        'clash6': False,
                                        'harmony6': False,
                                        'palace': {'palace': 0, 'pos': 6, 'shi': 5, 'yin': 2}
                                    },
                                    'cgua': {
                                        'gua': 34,
                                        'clash6': False,
                                        'harmony6': False,
                                        'palace': {'palace': 0, 'pos': 7, 'shi': 4, 'yin': 1}
                                    }
                                },
                                'rel': {
                                    'ogua': {},
                                    'cgua': {}
                                }
                            },
                            'question': 'test question',
                            'yao': '0|0|0|000|0|1',
                            'id': 100,
                            'uuid': '3fa85f64-5717-4562-b3fc-2c963f66afa6',
                            'created_by': 1,
                            'created_at': '2025-03-07T15:23:38.757Z',
                            'updated_at': '2025-03-07T15:23:38.757Z',
                            'message': 'Liuyao divination calculated and saved to your account'
                        },
                        status_codes=['201']
                    )
                ]
            ),
            400: OpenApiResponse(
                description="Validation error",
                examples=[
                    OpenApiExample(
                        name='Validation Error',
                        value={
                            'error': 'Invalid date or time format'
                        },
                        status_codes=['400']
                    )
                ]
            )
        },
        examples=[
            OpenApiExample(
                name='Calculate and Save Request',
                value={
                    'question': 'Will my business venture succeed?',
                    'usecur': False,
                    'year': 2025,
                    'month': 3,
                    'day': 7,
                    'time': '15:30',
                    'y1': '0',
                    'y2': '0', 
                    'y3': '0',
                    'y4': '000',
                    'y5': '0',
                    'y6': '1'
                },
                request_only=True,
            ),
            OpenApiExample(
                name='Calculate Current Time Request',
                value={
                    'question': 'What should I do about my career?',
                    'usecur': True,
                    'y1': '1',
                    'y2': '0',
                    'y3': '1', 
                    'y4': '000',
                    'y5': '1',
                    'y6': '0'
                },
                request_only=True,
            ),
        ],
    )
    def post(self, request, *args, **kwargs):
        """Calculate Liuyao result and optionally save it to the database."""
        # Validate request data using the same serializer as GET
        serializer = LiuyaoCalculatorSerializer(data=request.data)
        if not serializer.is_valid():
            return Response({'error': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
            
        validated_data = serializer.validated_data

        # Extract necessary data for calculations
        usecur = validated_data.get('usecur', False)
        
        try:
            # Parse the date and time
            if usecur:
                date_obj = datetime.now(bazi.gMYTimezone)
            else:
                year = validated_data.get('year')
                month = validated_data.get('month')
                day = validated_data.get('day')
                time_str = validated_data.get('time', '00:00')
                
                hours, minutes = map(int, time_str.split(':'))
                date_obj = datetime(year, month, day, hours, minutes)
            
            # Calculate bazi
            bz = bazi.getDateTimeGodEarthStem(
                date_obj.year, date_obj.month, date_obj.day, date_obj.hour, date_obj.minute
            )
            bz['date'] = date_obj.strftime('%Y-%m-%d %H:%M:%S')
            bz['empty'] = bazi.calcEarthEmpty(bz['day']['g'], bz['day']['e'])
            
            # Calculate 6 God
            god6 = liuyao_util.calc6God(bz['day']['g'])
            
            # Calculate 6 Yao
            y1 = validated_data.get('y1', '')
            y2 = validated_data.get('y2', '')
            y3 = validated_data.get('y3', '')
            y4 = validated_data.get('y4', '')
            y5 = validated_data.get('y5', '')
            y6 = validated_data.get('y6', '')
            
            ly = liuyao_util.calc6Yao(y1, y2, y3, y4, y5, y6)
            
            # Calculate Relationship
            rel = liuyao_util.calcRelationship(ly, bz)
            
            # Format response data
            question = validated_data.get('question', '')
            gua_data = {
                'question': question,
                'bz': bz,
                'god6': god6,
                'ly': ly,
                'rel': rel,
            }
            
            # Build the response
            result = {
                'gua': gua_data,
                'question': question,
                'yao': f"{validated_data.get('y1', '')}|{validated_data.get('y2', '')}|{validated_data.get('y3', '')}|{validated_data.get('y4', '')}|{validated_data.get('y5', '')}|{validated_data.get('y6', '')}"
            }
            
            # Save if user is authenticated (including temp users from middleware)
            if request.user.is_authenticated:
                # Generate UUID for the record
                uuid_value = utils.getUUID(request)
                
                # Set temporary user flag if applicable
                is_temp_user = hasattr(request.user, 'is_temporary_user') and request.user.is_temporary_user
                
                # Create the liuyao object
                liuyao_obj = liuyao.objects.create(
                    uuid=uuid_value,
                    user=request.user,
                    qdate=date_obj,
                    question=question,
                    y1=y1,
                    y2=y2,
                    y3=y3,
                    y4=y4,
                    y5=y5,
                    y6=y6,
                    data=result['gua'],  # Store only the 'gua' object which contains the expected format
                    created_by_temp_user=is_temp_user
                )
                
                # Add database fields to response
                result['id'] = liuyao_obj.id
                result['uuid'] = str(liuyao_obj.uuid)
                result['created_by'] = liuyao_obj.user.id
                result['created_at'] = liuyao_obj.created_at
                result['updated_at'] = liuyao_obj.last_modified_at
                result['message'] = 'Liuyao divination calculated and saved to your account'
                
                # Add temp user token info if available (only for temp users)
                if hasattr(request, 'temp_user_token') and hasattr(request.user, 'is_temporary_user') and request.user.is_temporary_user:
                    result["temp_user"] = request.temp_user_token
                
                return Response(result, status=status.HTTP_201_CREATED)
            else:
                # This should rarely happen since middleware creates temp users, but keep as fallback
                # Just return calculation result without saving
                result['message'] = 'Liuyao divination calculated (not saved - no authentication)'
                return Response(result, status=status.HTTP_200_OK)
            
        except Exception as e:
            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

# Liuyao API ViewSet
@extend_schema_view(
    list=extend_schema(
        summary="List all Liuyao objects",
        description="Retrieve a list of all Liuyao objects created by the current user. Results can be sorted by any field using the 'ordering' query parameter and paginated using 'page' and 'page_size' parameters.",
        parameters=[
            OpenApiParameter(
                name="ordering",
                type=str,
                location=OpenApiParameter.QUERY,
                description="Comma-separated list of fields to order by. Prefix with '-' for descending order. Available fields: id, qdate (question date, the date that is used to calculate the bazi and liuyao result), created_at, last_modified_at. If not specified, it will be ordered by created_at descending order.",
                required=False,
                examples=[
                    OpenApiExample(
                        'Sort by creation date descending',
                        value="-created_at"
                    ),
                    OpenApiExample(
                        'Sort by question date descending then created date descending',
                        value="-qdate,-created_at"
                    )
                ]
            ),
            OpenApiParameter(
                name="page",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Page number (1-based)",
                required=False,
                examples=[
                    OpenApiExample(
                        'First page',
                        value=1
                    )
                ]
            ),
            OpenApiParameter(
                name="page_size",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Number of items per page. If not specified, uses the default from settings.",
                required=False,
                examples=[
                    OpenApiExample(
                        '10 items per page',
                        value=10
                    ),
                    OpenApiExample(
                        '25 items per page',
                        value=25
                    )
                ]
            )
        ],
        responses={
            200: OpenApiResponse(
                response=LiuyaoSerializer(many=True),
                description="List of Liuyao objects",
                examples=[
                    OpenApiExample(
                        name='Liuyao List Example',
                        value={
                            "id": 100,
                            "qdate": "2025-03-07T15:23:38.757Z",
                            "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                            "question": "Test question",
                            "y1": "0",
                            "y2": "0",
                            "y3": "0",
                            "y4": "000",
                            "y5": "0",
                            "y6": "1",
                            "reading": "Interpretation text",
                            "feedback": "User feedback",
                            "data": "See the /api/liuyao/calc endpoint for the complete data structure. This field contains the full calculation result.",
                            "created_at": "2025-03-07T15:23:38.757Z",
                            "last_modified_at": "2025-03-07T15:23:38.757Z",
                            "user": 1
                        }
                    )
                ]
            )
        }
    ),
    retrieve=extend_schema(
        summary="Retrieve a Liuyao object",
        description="Get details of a specific Liuyao object by its ID.",
        responses={
            200: OpenApiResponse(
                response=LiuyaoSerializer,
                description="Liuyao object details",
                examples=[
                    OpenApiExample(
                        name='Liuyao Detail Example',
                        value={
                            "id": 100,
                            "qdate": "2025-03-07T15:23:38.757Z",
                            "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                            "question": "Test question",
                            "y1": "0",
                            "y2": "0",
                            "y3": "0",
                            "y4": "000",
                            "y5": "0",
                            "y6": "1",
                            "reading": "Interpretation text",
                            "feedback": "User feedback",
                            "data": "See the /api/liuyao/calc endpoint for the complete data structure. This field contains the full calculation result.",
                            "created_at": "2025-03-07T15:23:38.757Z",
                            "last_modified_at": "2025-03-07T15:23:38.757Z",
                            "user": 1
                        }
                    )
                ]
            )
        }
    ),
    create=extend_schema(
        summary="Create a Liuyao object",
        description="Add a new Liuyao object to the system. The user field is automatically populated with the authenticated user.",
        request=LiuyaoRequestSerializer,
        examples=[
            OpenApiExample(
                name='Create Liuyao Example',
                value={
                    "qdate": "2025-03-07T15:23:38.757Z",
                    "question": "Test question",
                    "y1": "0",
                    "y2": "0",
                    "y3": "0",
                    "y4": "000",
                    "y5": "0",
                    "y6": "1",
                    "reading": "Interpretation text",
                    "feedback": "User feedback",
                    "data": "The JSON data as returned when calling api/liuyao/calc"
                },
                request_only=True,
            ),
        ],
        responses={
            201: OpenApiResponse(
                response=LiuyaoSerializer,
                description="Created Liuyao object",
                examples=[
                    OpenApiExample(
                        name='Created Liuyao Example',
                        value={
                            "id": 100,
                            "qdate": "2025-03-07T15:23:38.757Z",
                            "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                            "question": "Test question",
                            "y1": "0",
                            "y2": "0",
                            "y3": "0",
                            "y4": "000",
                            "y5": "0",
                            "y6": "1",
                            "reading": "Interpretation text",
                            "feedback": "User feedback",
                            "data": "See the /api/liuyao/calc endpoint for the complete data structure. This field contains the full calculation result.",
                            "created_at": "2025-03-07T15:23:38.757Z",
                            "last_modified_at": "2025-03-07T15:23:38.757Z",
                            "user": 1
                        }
                    )
                ]
            )
        }
    ),
    update=extend_schema(
        summary="Update a Liuyao object",
        description="Update an existing Liuyao object by its ID. The user field is automatically maintained.",
        request=LiuyaoRequestSerializer,
        examples=[
            OpenApiExample(
                name='Update Liuyao Example',
                value={
                    "qdate": "2025-03-07T15:23:38.757Z",
                    "question": "Updated question",
                    "y1": "0",
                    "y2": "0",
                    "y3": "0",
                    "y4": "000",
                    "y5": "0",
                    "y6": "1",
                    "reading": "Updated interpretation",
                    "feedback": "Updated feedback",
                    "data": "The JSON data as returned when calling api/liuyao/calc"
                },
                request_only=True,
            ),
        ]
    ),
    partial_update=extend_schema(
        summary="Partially update a Liuyao object",
        description="Partially update an existing Liuyao object by its ID. Only provide the fields you want to update.",
        request=LiuyaoRequestSerializer,
        examples=[
            OpenApiExample(
                name='Partial Update Example',
                value={
                    "question": "Updated question only",
                    "reading": "Updated reading only"
                },
                request_only=True,
            ),
        ]
    ),
    destroy=extend_schema(
        summary="Delete a Liuyao object",
        description="Delete a specific Liuyao object by its ID."
    ),
)
@method_decorator(csrf_exempt, name='dispatch')
class LiuyaoViewSet(viewsets.ModelViewSet):
    queryset = liuyao.objects.all()
    serializer_class = LiuyaoSerializer
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]  # Support both JWT and session auth
    permission_classes = [AllowAny]  # Make it accessible to everyone, we'll filter by user later
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['id', 'qdate', 'created_at', 'last_modified_at']
    ordering = ['-created_at']  # Default ordering
    pagination_class = CustomPageNumberPagination

    def get_serializer_class(self):
        """Use LiuyaoRequestSerializer for create/update operations to allow optional data field"""
        if self.action in ['create', 'update', 'partial_update']:
            return LiuyaoRequestSerializer
        return LiuyaoSerializer

    def perform_create(self, serializer):
        # Always generate a UUID
        uuid_value = utils.getUUID(self.request)
        
        # Get user if authenticated
        user = self.request.user
        user_id = user.id if user.is_authenticated else None
        
        # Set temporary user flag if applicable
        is_temp_user = user.is_authenticated and hasattr(user, 'is_temporary_user') and user.is_temporary_user
        
        # Save with both UUID and user_id (if authenticated)
        serializer.save(uuid=uuid_value, user_id=user_id, created_by_temp_user=is_temp_user)
    
    def get_queryset(self):
        """
        Filter queryset to return only records belonging to the requesting user.
        """
        user = self.request.user
        if user.is_authenticated:
            return liuyao.objects.filter(user=user)
        else:
            # Anonymous users get empty queryset
            return liuyao.objects.none()
    
    def get_object(self):
        """Override to add additional permission checking for individual record access."""
        obj = super().get_object()
        user = self.request.user
        
        # For DELETE action, enforce stricter permissions
        if self.action == 'destroy':
            if not user.is_authenticated:
                raise PermissionDenied("Authentication required for deletion.")
            
            # Only allow deletion if user owns the object
            if obj.user != user:
                raise PermissionDenied("You do not have permission to delete this object.")
        
        return obj

@extend_schema_view(
    list=extend_schema(
        summary="List all Bazi charts",
        description="Retrieve a list of all Bazi charts created by the current user. Results can be sorted by any field using the 'ordering' query parameter and paginated using 'page' and 'page_size' parameters.",
        parameters=[
            OpenApiParameter(
                name="owner",
                type=bool,
                location=OpenApiParameter.QUERY,
                description="Filter by owner status. Set to 'true' to get only your own record, 'false' to get all other records.",
                required=False,
                examples=[
                    OpenApiExample(
                        'Get own record',
                        value="true"
                    ),
                    OpenApiExample(
                        'Get other records',
                        value="false"
                    )
                ]
            ),
            OpenApiParameter(
                name="name",
                type=str,
                location=OpenApiParameter.QUERY,
                description="Filter by name (case-insensitive partial match)",
                required=False,
                examples=[
                    OpenApiExample(
                        'Filter by name',
                        value="John"
                    )
                ]
            ),
            OpenApiParameter(
                name="ordering",
                type=str,
                location=OpenApiParameter.QUERY,
                description="Comma-separated list of fields to order by. Prefix with '-' for descending order. Available fields: name, birth_date, created_at, updated_at. If not specified, it will be ordered by created_at descending order.",
                required=False,
                examples=[
                    OpenApiExample(
                        'Sort by creation date descending',
                        value="-created_at"
                    ),
                    OpenApiExample(
                        'Sort by birth date descending then name ascending',
                        value="-birth_date,name"
                    )
                ]
            ),
            OpenApiParameter(
                name="page",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Page number (1-based)",
                required=False,
                examples=[
                    OpenApiExample(
                        'First page',
                        value=1
                    )
                ]
            ),
            OpenApiParameter(
                name="page_size",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Number of items per page. If not specified, uses the default from settings.",
                required=False,
                examples=[
                    OpenApiExample(
                        '10 items per page',
                        value=10
                    ),
                    OpenApiExample(
                        '25 items per page',
                        value=25
                    )
                ]
            )
        ],
        responses={
            200: OpenApiResponse(
                response=BaziSerializer(many=True),
                description="List of Bazi charts",
                examples=[
                    OpenApiExample(
                        name='Bazi List Example',
                        value={
                            "id": 1,
                            "name": "Example Person",
                            "gender": "M",
                            "birth_date": "1990-01-01",
                            "birth_time": "12:00:00",
                            "notes": "Example notes",
                            "created_at": "2023-01-01T12:00:00Z",
                            "updated_at": "2023-01-01T12:00:00Z",
                            "bazi_result": {
                                "year": {"god": 6, "earth": 6},
                                "month": {"god": 2, "earth": 0},
                                "day": {"god": 2, "earth": 6},
                                "hour": {"god": 4, "earth": 6}
                            },
                            "number_result": "Refer the response structure in the /api/bazi/bazi (POST) for details.",
                            "created_by": 1
                        }
                    )
                ]
            )
        }
    ),
    retrieve=extend_schema(
        summary="Retrieve a Bazi chart",
        description="Get details of a specific Bazi chart by its ID. The response includes the fully calculated Bazi chart details. Refer the response structure in the /api/bazi/bazi (POST) for details.",
        parameters=[
            OpenApiParameter(
                name="id",
                type=int,
                location=OpenApiParameter.PATH,
                description="A unique integer value identifying the Bazi chart to retrieve.",
                required=True,
            ),
        ],
        responses={
            200: OpenApiTypes.OBJECT,
            404: OpenApiTypes.OBJECT,
        },
        examples=[
            OpenApiExample(
                name='Bazi Detail Example',
                value={
                    "id": 39,
                    "name": "Example Person",
                    "gender": "M",
                    "birth_date": "1990-01-01",
                    "birth_time": "12:00:00",
                    "notes": "Example notes",
                    "created_at": "2023-01-01T12:00:00Z",
                    "updated_at": "2023-01-01T12:00:00Z",
                    "created_by": 1,
                    "result": "See the /api/bazi/bazi (POST) endpoint for the complete data structure. This field contains the full bazi result.",
                    "number": "See the /api/bazi/bazi (POST) endpoint for the complete data structure. This field contains the full number analysis result."
                },
                response_only=True,
                status_codes=["200"],
            ),
        ],
    ),
    create=extend_schema(
        summary="Calculate and optionally save a Bazi chart",
        description="""
Calculate a Bazi (八字) chart based on the provided birth information.

**Response Structure:**

- `name`: Person's name
- `gender`: Gender (M/男, F/女, N/不指定)
- `birth_date`: Birth date in YYYY-MM-DD format
- `birth_time`: Birth time in HH:MM:SS format (optional)
- `notes`: Additional notes about this chart (optional)
- `result`: The calculated Bazi chart with detailed analysis
  - `year`, `month`, `day`, `hour`: The four pillars with:
    - `god`: Heavenly stem (天干) in Chinese character
    - `earth`: Earthly branch (地支) in Chinese character
    - `earth_element`, `god_element`: Element of the earthly branch (wood/fire/earth/metal/water)
    - `ten_god`: Ten Gods (十神) relationship to day stem
    - `hidden_gods`: Array of hidden gods (藏干) with:
      - `god`: Hidden heavenly stem
      - `ten_god`: Ten Gods relationship to day stem
    - `empty`: Whether this pillar is in an empty position (空亡)
    - `god_idx`, `earth_idx`: Numeric indices for stems and branches
    - `star_luck_idx`, `self_luck_idx`: Numeric indices for 12长生, star_luck is for the 星运, while self_luck is for the 自坐
      - The 12长生:
        ```
        0: "长生", 1: "沐浴", 2: "冠带", 3: "临官", 4: "帝旺", 5: "衰", 6: "病", 7: "死", 8: "墓", 9: "绝", 10: "胎", 11: "养"
        ```
  - `sha_gods`: Sha Gods (神煞) information for each pillar:
    - `year`, `month`, `day`, `hour`: The four pillars with:
        - `gods` with:
            - `name`: Name of the sha god
            - `kindness`: Whether the god is auspicious (0), inauspicious (1) or neutral (2)
  - `sha_god_locations`: Sha Gods (神煞) location information:
    - `gid`: the sha god identifier
        - List of god id:
            ```
            0: "tian_yi" (天乙), 1: "tian_de" (天德), 2: "yue_de" (月德), 3: "taiji" (太极), 4: "lushen" (禄神), 5: "wenchang" (文昌), 6: "yima" (驿马), 7: "taohua" (桃花), 8: "hongluan" (红鸾), 9: "tianxi" (天喜), 10: "jiangxing" (将星), 11: "fuxing" (福星), 12: "huagai" (华盖), 13: "jiesha" (劫煞), 14: "wangshen" (亡神), 15: "yangren" (羊刃), 16: "zaisha" (灾煞), 17: "guchen" (孤辰), 18: "guashu" (寡宿), 19: "yinchayangcuo" (阴差阳错), 20: "tianyi" (天医), 21: "feiren" (飞刃), 22: "gejiao" (隔角), 23: "shieda" (十恶大败), 24: "liuxiu" (六秀), 25: "jinyu" (金舆)
            ```
    - `god`: the sha god shortname
    - `loc`: the sha god location, this will map to the god stem (0-9) / earth branch (0-11) index.
        - List of god stem / earth branch:
            ```
            God stem: 0: "甲", 1: "乙", 2: "丙", 3: "丁", 4: "戊", 5: "己", 6: "庚", 7: "辛", 8: "壬", 9: "癸"
            Earth branch: 0: "子", 1: "丑", 2: "寅", 3: "卯", 4: "辰", 5: "巳", 6: "午", 7: "未", 8: "申", 9: "酉", 10: "戌", 11: "亥"
            ```
        - The information on the type of location (either god or branch) will be available in the `ge`. This can be multiple items, and each item will map accordingly to the `ge`, `pillar` and `type`. For eg, if `loc` = {0, 8}, `ge` = {'e', 'e'}, `pillar` = {'d', 'y'} it means the first item 0 is 子 (`ge` = 'e' means earth branch) on the day pillar, while the second item 8 is 申 on the year pillar.
    - `ge`: the type of location, either god stem (g) or earth branch (e)
    - `pillar`: indicates the location of where the sha god exists, it can be either y/m/d/h, indicating year/month/day/hour.
    - `upc`: unique pillar count, if the sha god exists in more than pillar, for eg in both the day and year pillar, then this `upc` = 2.
    - `type`: the type of sha god, currently only applies to tian_yi, where y1 = 阳, y0 = 阴.
  - `life_number`: the life number of the person
  - `life_palace`: the life palace of the person
- `number`: The numerology profile based on the date of birth
    - `formula`: Birthdate formula string (e.g., "01011990")
    - `number`: The main life number
    - `number_5e`: Five Element name of the number (wood/fire/earth/metal/water)
    - `number_5el`: Ordered five element cycle starting from the number's element, so that it starts from the number_5e, the cycle is wood(1)>fire(2)>earth(3)>metal(4)>water(5), so if the number_5e = "metal", the number_5el will become 4,5,1,2,3
    - `head`: First digit (起头数) of the number string
    - `mingpan`: 命盘, dictionary with keys `a` to `x`, `north`, `south`, `east`, `west`.
    - `have_nos`: List of present numbers
    - `no_nos`: List of missing numbers
    - `zodiac`: Zodiac index (0–11)
    - `zodiac_boost`: Number boost from zodiac
    - `number_5ec`: Dict of element index (1–5) and their counts
    - `number_5es`: Ordered element counts starting from `number_5e`, if follow above eg where number_5e="metal", and the number_5es={4,3,2,0,3}, it means the user has metal=4,water=3,wood=2,fire=0,earth=3
    - `year_number`: Annual number
    - `year_5e`: Five Element of the year
    - `year_5el`: Ordered year element cycle
    - `year_5ec`: Year element counts by index
    - `year_5es`: Ordered year element counts
    - `liunian`: 流年数盘 (a to x, north & south)
    - `pairs81`: List of 13 strings mapping to:
        1. 事业基因
        2. 事业过程
        3. 事业过程
        4. 事业结果
        5. 感情基因
        6. 感情过程
        7. 感情过程
        8. 感情结果
        9. 人生基因
        10. 人生过程
        11. 人生过程
        12. 人生结果
        13. 隐藏禀赋
- `id`: Record ID (only for authenticated users who saved the chart)
- `created_by`: User ID who created the chart (only for authenticated users)
- `created_at`: Creation timestamp (only for authenticated users)
- `updated_at`: Last update timestamp (only for authenticated users)
- `message`: Status message about the calculation and saving status

Authentication behavior:
- Public users can calculate charts (results will be returned but not saved)
- Authenticated users automatically have their charts saved
""",
        request=BaziRequestSerializer,
        parameters=[
            BaziRequestSerializer,
        ],
        responses={
            201: OpenApiTypes.OBJECT,
            200: OpenApiTypes.OBJECT,
            400: OpenApiTypes.OBJECT,
        },
        examples=[
            OpenApiExample(
                name='Calculate Bazi Example',
                value={
                    "name": "Example Person",
                    "gender": "M",
                    "birth_date": "1990-01-01",
                    "birth_time": "12:00:00",
                    "twin_type": "0",
                    "father_dob": "1970-01-01",
                    "mother_dob": "1971-12-31",
                    "notes": "Example notes",
                },
                request_only=True,
            ),
            OpenApiExample(
                name='Success Response (Authenticated)',
                value={
                    "name": "2025",
                    "gender": "N",
                    "birth_date": "2025-02-03",
                    "birth_time": "22:11:00",
                    "notes": "",
                    "result": {
                        "year": {
                            "god": "乙",
                            "earth": "巳",
                            "earth_element": "火",
                            "ten_god": "食神",
                            "hidden_gods": [
                                {
                                    "god": "丙",
                                    "ten_god": "正财"
                                },
                                {
                                    "god": "庚",
                                    "ten_god": "正印"
                                },
                                {
                                    "god": "戊",
                                    "ten_god": "正官"
                                }
                            ],
                            "empty": True,
                            "god_idx": 1,
                            "earth_idx": 5
                        },
                        "month": {
                            "god": "戊",
                            "earth": "寅",
                            "earth_element": "木",
                            "ten_god": "正官",
                            "hidden_gods": [
                                {
                                    "god": "甲",
                                    "ten_god": "伤官"
                                },
                                {
                                    "god": "丙",
                                    "ten_god": "正财"
                                },
                                {
                                    "god": "戊",
                                    "ten_god": "正官"
                                }
                            ],
                            "empty": False,
                            "god_idx": 4,
                            "earth_idx": 2
                        },
                        "day": {
                            "god": "癸",
                            "earth": "卯",
                            "earth_element": "木",
                            "ten_god": "比肩",
                            "hidden_gods": [
                                {
                                    "god": "乙",
                                    "ten_god": "食神"
                                }
                            ],
                            "empty": False,
                            "god_idx": 9,
                            "earth_idx": 3
                        },
                        "hour": {
                            "god": "癸",
                            "earth": "亥",
                            "earth_element": "水",
                            "ten_god": "比肩",
                            "hidden_gods": [
                                {
                                    "god": "壬",
                                    "ten_god": "劫财"
                                },
                                {
                                    "god": "甲",
                                    "ten_god": "伤官"
                                }
                            ],
                            "empty": False,
                            "god_idx": 9,
                            "earth_idx": 11
                        },
                        "sha_gods": {
                            "year": {
                                "gods": [
                                    {
                                        "name": "驿马",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "太极贵人",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "天乙贵人",
                                        "kindness": 0
                                    }
                                ]
                            },
                            "month": {
                                "gods": [
                                    {
                                        "name": "亡神",
                                        "kindness": 1
                                    },
                                    {
                                        "name": "金舆",
                                        "kindness": 0
                                    }
                                ]
                            },
                            "day": {
                                "gods": [
                                    {
                                        "name": "福星",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "天乙贵人",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "将星",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "文昌",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "灾煞",
                                        "kindness": 1
                                    }
                                ]
                            },
                            "hour": {
                                "gods": [
                                    {
                                        "name": "羊刃",
                                        "kindness": 1
                                    }
                                ]
                            }
                        }
                    },
                    "number": {
                        "formula": "01011990",
                        "number": 3,
                        "number_5e": "fire",
                        "number_5el": [4, 5, 1, 2, 3],
                        "head": 1,
                        "mingpan": {
                            "a": 0, "b": 1, "c": 0, "d": 1, "e": 1, "f": 9,
                            "g": 9, "h": 0, "i": 1, "j": 1, "k": 1, "l": 9,
                            "m": 2, "n": 1, "o": 3, "p": 4, "q": 5, "r": 9,
                            "s": 3, "t": 3, "u": 6, "v": 2, "w": 1, "x": 3,
                            "north": 4, "south": 9, "west": 9, "east": 6
                        },
                        "have_nos": [1, 2, 3, 9],
                        "no_nos": [4, 5, 6, 7, 8],
                        "zodiac": 5,
                        "zodiac_boost": "6",
                        "number_5ec": {
                            "1": 6,
                            "2": 2,
                            "3": 3,
                            "4": 4,
                            "5": 1
                        },
                        "number_5es": [4, 1, 6, 2, 3],
                        "year_number": 2,
                        "year_5e": "water",
                        "year_5el": [2, 3, 4, 5, 1],
                        "year_5ec": {
                            "1": 4,
                            "2": 7,
                            "3": 3,
                            "4": 2,
                            "5": 0
                        },
                        "year_5es": [7, 3, 2, 0, 4],
                        "liunian": {
                            "a": 0, "b": 1, "c": 0, "d": 1, "e": 2, "f": 0,
                            "g": 2, "h": 5, "i": 1, "j": 1, "k": 2, "l": 7,
                            "m": 2, "n": 9, "o": 2, "p": 2, "q": 4, "r": 6,
                            "s": 3, "t": 3, "u": 6, "v": 2, "w": 7, "x": 9,
                            "north": 1, "south": 6
                        },
                        "pairs81": [
                            "112", "123", "123", "336", "191", "112", "911",
                            "213", "213", "235", "134", "459", "426"
                        ]
                    },
                    "id": 30,
                    "created_by": 1,
                    "created_at": "2025-03-08T10:30:28.790974Z",
                    "updated_at": "2025-03-08T10:30:28.790989Z",
                    "message": "Bazi chart calculated and saved to your account"
                },
                response_only=True,
                status_codes=["201"],
            ),
            OpenApiExample(
                name='Success Response (Public User)',
                value={
                    "name": "2025",
                    "gender": "N",
                    "birth_date": "2025-02-03",
                    "birth_time": "22:11:00",
                    "notes": "",
                    "result": {
                        "year": {
                            "god": "乙",
                            "earth": "巳",
                            "earth_element": "火",
                            "ten_god": "食神",
                            "hidden_gods": [
                                {
                                    "god": "丙",
                                    "ten_god": "正财"
                                },
                                {
                                    "god": "庚",
                                    "ten_god": "正印"
                                },
                                {
                                    "god": "戊",
                                    "ten_god": "正官"
                                }
                            ],
                            "empty": True,
                            "god_idx": 1,
                            "earth_idx": 5
                        },
                        "month": {
                            "god": "戊",
                            "earth": "寅",
                            "earth_element": "木",
                            "ten_god": "正官",
                            "hidden_gods": [
                                {
                                    "god": "甲",
                                    "ten_god": "伤官"
                                },
                                {
                                    "god": "丙",
                                    "ten_god": "正财"
                                },
                                {
                                    "god": "戊",
                                    "ten_god": "正官"
                                }
                            ],
                            "empty": False,
                            "god_idx": 4,
                            "earth_idx": 2
                        },
                        "day": {
                            "god": "癸",
                            "earth": "卯",
                            "earth_element": "木",
                            "ten_god": "比肩",
                            "hidden_gods": [
                                {
                                    "god": "乙",
                                    "ten_god": "食神"
                                }
                            ],
                            "empty": False,
                            "god_idx": 9,
                            "earth_idx": 3
                        },
                        "hour": {
                            "god": "癸",
                            "earth": "亥",
                            "earth_element": "水",
                            "ten_god": "比肩",
                            "hidden_gods": [
                                {
                                    "god": "壬",
                                    "ten_god": "劫财"
                                },
                                {
                                    "god": "甲",
                                    "ten_god": "伤官"
                                }
                            ],
                            "empty": False,
                            "god_idx": 9,
                            "earth_idx": 11
                        },
                        "sha_gods": {
                            "year": {
                                "gods": [
                                    {
                                        "name": "驿马",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "太极贵人",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "天乙贵人",
                                        "kindness": 0
                                    }
                                ]
                            },
                            "month": {
                                "gods": [
                                    {
                                        "name": "亡神",
                                        "kindness": 1
                                    },
                                    {
                                        "name": "金舆",
                                        "kindness": 0
                                    }
                                ]
                            },
                            "day": {
                                "gods": [
                                    {
                                        "name": "福星",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "天乙贵人",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "将星",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "文昌",
                                        "kindness": 0
                                    },
                                    {
                                        "name": "灾煞",
                                        "kindness": 1
                                    }
                                ]
                            },
                            "hour": {
                                "gods": [
                                    {
                                        "name": "羊刃",
                                        "kindness": 1
                                    }
                                ]
                            }
                        }
                    },
                    "number": {
                        "formula": "01011990",
                        "number": 3,
                        "number_5e": "fire",
                        "number_5el": [4, 5, 1, 2, 3],
                        "head": 1,
                        "mingpan": {
                            "a": 0, "b": 1, "c": 0, "d": 1, "e": 1, "f": 9,
                            "g": 9, "h": 0, "i": 1, "j": 1, "k": 1, "l": 9,
                            "m": 2, "n": 1, "o": 3, "p": 4, "q": 5, "r": 9,
                            "s": 3, "t": 3, "u": 6, "v": 2, "w": 1, "x": 3,
                            "north": 4, "south": 9, "west": 9, "east": 6
                        },
                        "have_nos": [1, 2, 3, 9],
                        "no_nos": [4, 5, 6, 7, 8],
                        "zodiac": 5,
                        "zodiac_boost": "6",
                        "number_5ec": {
                            "1": 6,
                            "2": 2,
                            "3": 3,
                            "4": 4,
                            "5": 1
                        },
                        "number_5es": [4, 1, 6, 2, 3],
                        "year_number": 2,
                        "year_5e": "water",
                        "year_5el": [2, 3, 4, 5, 1],
                        "year_5ec": {
                            "1": 4,
                            "2": 7,
                            "3": 3,
                            "4": 2,
                            "5": 0
                        },
                        "year_5es": [7, 3, 2, 0, 4],
                        "liunian": {
                            "a": 0, "b": 1, "c": 0, "d": 1, "e": 2, "f": 0,
                            "g": 2, "h": 5, "i": 1, "j": 1, "k": 2, "l": 7,
                            "m": 2, "n": 9, "o": 2, "p": 2, "q": 4, "r": 6,
                            "s": 3, "t": 3, "u": 6, "v": 2, "w": 7, "x": 9,
                            "north": 1, "south": 6
                        },
                        "pairs81": [
                            "112", "123", "123", "336", "191", "112", "911",
                            "213", "213", "235", "134", "459", "426"
                        ]
                    },
                    "message": "Bazi chart calculated (login to save)"
                },
                response_only=True,
                status_codes=["200"],
            ),
            OpenApiExample(
                name='Validation Error',
                value={
                    "error": "Invalid date format or missing required fields"
                },
                response_only=True,
                status_codes=["400"],
            ),
        ],
    ),
    update=extend_schema(
        summary="Update a Bazi chart",
        description="Update an existing Bazi chart by its ID. The response includes the fully calculated Bazi chart details. Refer the response structure in the /api/bazi/bazi (POST) for details.",
        request=BaziRequestSerializer,
        parameters=[
            OpenApiParameter(
                name="id",
                type=int,
                location=OpenApiParameter.PATH,
                description="A unique integer value identifying the Bazi chart to update.",
                required=True,
            ),
        ],
        responses={
            200: OpenApiTypes.OBJECT,
            400: OpenApiTypes.OBJECT,
        },
        examples=[
            OpenApiExample(
                name='Update Bazi Example',
                value={
                    "name": "Updated Person",
                    "gender": "F",
                    "birth_date": "1990-01-01",
                    "birth_time": "14:00:00",
                    "twin_type": "0",
                    "father_dob": "1970-01-01",
                    "mother_dob": "1970-12-31",
                    "notes": "Updated notes"
                },
                request_only=True,
            ),
            OpenApiExample(
                name='Updated Bazi Response',
                value={
                    "id": 39,
                    "name": "Updated Person",
                    "gender": "F",
                    "birth_date": "1990-01-01",
                    "birth_time": "14:00:00",
                    "notes": "Updated notes",
                    "created_at": "2023-01-01T12:00:00Z",
                    "updated_at": "2023-01-01T15:00:00Z",
                    "created_by": 1,
                    "result": "See the /api/bazi/bazi (POST) endpoint for the complete data structure. This field contains the full bazi result.",
                    "number": "See the /api/bazi/bazi (POST) endpoint for the complete data structure. This field contains the full number analysis result."
                },
                response_only=True,
                status_codes=["200"],
            ),
        ],
    ),
    partial_update=extend_schema(
        summary="Partially update a Bazi chart",
        description="Partially update an existing Bazi chart by its ID. Only provide the fields you want to update. The response includes the fully calculated Bazi chart details. Refer the response structure in the /api/bazi/bazi (POST) for details.",
        request=BaziRequestSerializer,
        parameters=[
            OpenApiParameter(
                name="id",
                type=int,
                location=OpenApiParameter.PATH,
                description="A unique integer value identifying the Bazi chart to update.",
                required=True,
            ),
        ],
        responses={
            200: OpenApiTypes.OBJECT,
            400: OpenApiTypes.OBJECT,
        },
        examples=[
            OpenApiExample(
                name='Partial Update Example',
                value={
                    "name": "Renamed Person",
                    "notes": "Only these fields will be updated"
                },
                request_only=True,
            ),
            OpenApiExample(
                name='Partially Updated Bazi Response',
                value={
                    "id": 39,
                    "name": "Renamed Person",
                    "gender": "M",  # Unchanged
                    "birth_date": "1990-01-01",  # Unchanged
                    "birth_time": "12:00:00",  # Unchanged
                    "notes": "Only these fields will be updated",
                    "created_at": "2023-01-01T12:00:00Z",
                    "updated_at": "2023-01-01T15:30:00Z",
                    "created_by": 1,
                    "result": "See the /api/bazi/bazi (POST) endpoint for the complete data structure. This field contains the full bazi result.",
                    "number": "See the /api/bazi/bazi (POST) endpoint for the complete data structure. This field contains the full number analysis result."
                },
                response_only=True,
                status_codes=["200"],
            ),
        ],
    ),
    destroy=extend_schema(
        summary="Delete a Bazi chart",
        description="Delete a specific Bazi chart by its ID.",
        parameters=[
            OpenApiParameter(
                name="id",
                type=int,
                location=OpenApiParameter.PATH,
                description="A unique integer value identifying the Bazi chart to delete.",
                required=True,
            ),
        ],
        responses={
            204: OpenApiTypes.NONE,
            404: OpenApiTypes.OBJECT,
        },
    ),
)
@method_decorator(csrf_exempt, name='dispatch')
class BaziViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Bazi chart objects providing CRUD operations and calculations.
    Public users can calculate charts (results will be returned but not saved).
    Authenticated users automatically have their charts saved.
    """
    serializer_class = BaziSerializer
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]  # Support both JWT and session auth for temp users
    permission_classes = [AllowAny]  # Allow public access for calculation
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['name', 'birth_date', 'created_at', 'updated_at']
    ordering = ['-created_at']  # Default ordering
    pagination_class = CustomPageNumberPagination
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']
    
    def get_queryset(self):
        """
        This view returns a list of Bazi charts with optional filtering.
        - For authenticated users: Return user's records only
        - For unauthenticated users: Return empty queryset
        """
        user = self.request.user
        if not user.is_authenticated:
            return BaziPerson.objects.none()

        queryset = BaziPerson.objects.filter(created_by=user)

        # Filter by owner if specified
        owner = self.request.query_params.get('owner', None)
        if owner is not None:
            try:
                owner_bool = owner.lower() == 'true'
                queryset = queryset.filter(owner=owner_bool)
            except ValueError:
                pass

        # Filter by name if specified
        name = self.request.query_params.get('name', None)
        if name is not None:
            queryset = queryset.filter(name__icontains=name)

        return queryset
    
    def list(self, request, *args, **kwargs):
        """
        Override list to apply user-specific filtering while keeping get_queryset open for DRF.
        """
        # Get filtered queryset based on user authentication
        queryset = self._get_user_filtered_queryset()
        
        # Apply pagination
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
    
    def _get_user_filtered_queryset(self):
        """
        Get queryset filtered by user authentication and request parameters.
        """
        user = self.request.user
        if not user.is_authenticated:
            return BaziPerson.objects.none()
            
        queryset = BaziPerson.objects.filter(created_by=user)
        
        # Filter by owner if specified
        owner = self.request.query_params.get('owner', None)
        if owner is not None:
            try:
                owner_bool = owner.lower() == 'true'
                queryset = queryset.filter(owner=owner_bool)
            except ValueError:
                pass
                
        # Filter by name if specified
        name = self.request.query_params.get('name', None)
        if name is not None:
            queryset = queryset.filter(name__icontains=name)
            
        return queryset
    
    def _format_bazi_response(self, person):
        """
        Helper method to format the response with calculated Bazi details.
        Always calculates the full BaZi result to ensure all fields are populated.
        """
        # Always calculate the full BaZi result to ensure all fields are populated
        # This ensures the response includes year, month, day, hour, sha_gods, etc.
        person._skip_bazi_calculation = True
        bazi_result = person.calculate_bazi()
        
        # Prepare consistent response format
        # Optionally compute pairwise relations vs owner and persist compact arrays
        try:
            if person.created_by:
                owner = (
                    BaziPerson.objects.filter(created_by=person.created_by, owner=True)
                    .order_by('-created_at')
                    .first()
                )
                if owner and owner.id != person.id and owner.bazi_result and person.bazi_result:
                    og = owner.bazi_result.get('day', {}).get('god')
                    oe = owner.bazi_result.get('day', {}).get('earth')
                    pg = person.bazi_result.get('day', {}).get('god')
                    pe = person.bazi_result.get('day', {}).get('earth')
                    if og is not None and oe is not None and pg is not None and pe is not None:
                        rel = evaluate_pair(og, oe, pg, pe)
                        # Persist compact arrays and counts
                        to_update = []
                        if person.relation_good != rel.get('good'):
                            person.relation_good = rel.get('good')
                            to_update.append('relation_good')
                        if person.relation_bad != rel.get('bad'):
                            person.relation_bad = rel.get('bad')
                            to_update.append('relation_bad')
                        good_count = len(rel.get('good') or [])
                        bad_count = len(rel.get('bad') or [])
                        if person.relation_good_count != good_count:
                            person.relation_good_count = good_count
                            to_update.append('relation_good_count')
                        if person.relation_bad_count != bad_count:
                            person.relation_bad_count = bad_count
                            to_update.append('relation_bad_count')
                        if to_update:
                            from django.utils import timezone as dj_tz
                            person.relation_updated_at = dj_tz.now()
                            to_update.append('relation_updated_at')
                            person.save(update_fields=to_update)
        except Exception:
            pass

        response_data = {
            "id": person.id,
            "name": person.name,
            "gender": person.gender,
            "birth_date": person.birth_date,
            "birth_time": person.birth_time,
            "twin_type": person.twin_type,
            "father_dob": person.father_dob,
            "mother_dob": person.mother_dob,
            "notes": person.notes,
            "owner": person.owner,
            "result": bazi_result,
            "number": person.number_result,
            "created_at": person.created_at,
            "updated_at": person.updated_at,
            "created_by": person.created_by.id if person.created_by else None
        }

        # Attach pairwise relations if present (compact arrays)
        response_data["relation_good"] = person.relation_good or []
        response_data["relation_bad"] = person.relation_bad or []

        return response_data
    
    def retrieve(self, request, *args, **kwargs):
        """
        Override to include calculated Bazi details in the response.
        """
        instance = self.get_object()
        response_data = self._format_bazi_response(instance)
        return Response(response_data)
    
    def perform_update(self, serializer):
        """
        Override to prevent signal interference during API updates.
        """
        # Set a flag to prevent immediate relation calculation during API updates
        # The signal will handle delayed calculation to avoid transaction conflicts
        instance = serializer.instance
        instance._skip_relation_calculation = True
        serializer.save()

    def update(self, request, *args, **kwargs):
        """
        Override to include calculated Bazi details in the response after update.
        """
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        # Get the updated instance and format response
        updated_instance = self.get_object()
        response_data = self._format_bazi_response(updated_instance)
        
        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(response_data)
    
    def partial_update(self, request, *args, **kwargs):
        """
        Override to ensure partial updates use the same response format.
        """
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)
    
    def create(self, request, *args, **kwargs):
        """
        Create action handling both calculation and optional saving.
        - Public users: Calculate only
        - Authenticated users: Calculate and save
        """
        serializer = BaziRequestSerializer(data=request.data)
        if not serializer.is_valid():
            return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
            
        # Extract validated data
        validated_data = serializer.validated_data
        
        # Create a Person object but don't save it yet
        person = BaziPerson(
            name=validated_data['name'],
            gender=validated_data['gender'],
            birth_date=validated_data['birth_date'],
            birth_time=validated_data.get('birth_time'),
            twin_type=validated_data.get('twin_type', 0),
            father_dob=validated_data.get('father_dob'),
            mother_dob=validated_data.get('mother_dob'),
            notes=validated_data.get('notes', '')
        )
        
        # Validate twin type requirements
        twin_type = validated_data.get('twin_type', 0)
        if twin_type == 1 and not validated_data.get('father_dob'):
            return Response({"error": {"father_dob": ["双大需要填写父亲出生日期"]}}, 
                           status=status.HTTP_400_BAD_REQUEST)
        elif twin_type == 2 and not validated_data.get('mother_dob'):
            return Response({"error": {"mother_dob": ["双小需要填写母亲出生日期"]}}, 
                           status=status.HTTP_400_BAD_REQUEST)
        
        # Set a flag to skip automatic bazi calculation on save
        person._skip_bazi_calculation = True
        
        # Calculate Bazi manually
        bazi_result = person.calculate_bazi()
        
        # Prepare response
        response_data = {
            "name": person.name,
            "gender": person.gender,
            "birth_date": person.birth_date,
            "birth_time": person.birth_time,
            "twin_type": person.twin_type,
            "father_dob": person.father_dob,
            "mother_dob": person.mother_dob,
            "notes": person.notes,
            "result": bazi_result
        }
        
        # Save record (user is authenticated either as regular user or temp user from middleware)
        if request.user.is_authenticated:
            person.created_by = request.user
            # Set flag if user is temporary
            if hasattr(request.user, 'is_temporary_user') and request.user.is_temporary_user:
                person.created_by_temp_user = True
            
            # Check for existing records to determine owner status
            existing_records = BaziPerson.objects.filter(created_by=request.user)
            
            # Set owner flag for first BaZi record
            person.owner = not existing_records.exists()
            person.save()
            
            response_data["number"] = person.number_result
            response_data["id"] = person.id
            response_data["created_by"] = person.created_by.id
            response_data["created_at"] = person.created_at
            response_data["updated_at"] = person.updated_at
            response_data["message"] = "Bazi chart calculated and saved to your account"
            
            # Add temp user token info if available (only for temp users)
            if hasattr(request, 'temp_user_token') and hasattr(request.user, 'is_temporary_user') and request.user.is_temporary_user:
                response_data["temp_user"] = request.temp_user_token
            
            return Response(response_data, status=status.HTTP_201_CREATED)
        else:
            # This should rarely happen now since middleware creates temp users
            response_data["message"] = "Bazi chart calculated (not saved - no authentication)"
            return Response(response_data, status=status.HTTP_200_OK)


    @extend_schema(  # Document GET
        operation_id="bazi_analysis_get",
        methods=["GET"],
        summary="Get AI analysis for a BaZi chart",
        description="""
Retrieve the AI analysis for a BaZi chart if it exists.
If no analysis exists, returns a status of "not-available".
""",
        responses={
            200: OpenApiResponse(
                response=OpenApiTypes.OBJECT,
                description="Analysis retrieved successfully or status indicated",
                examples=[
                    OpenApiExample(
                        name='Completed Analysis',
                        value={
                            "status": "completed",
                            "analysis": {
                                "bazi_analysis": {
                                    "personality_analysis": "包含性格分析的文本...",
                                    "life_structure": "命局结构与五行平衡分析...",
                                    "career_wealth": "事业与财富展望分析...",
                                    "relationships": "人际关系与婚姻模式分析...",
                                    "health": "健康考量分析...",
                                    "life_trajectory": "人生轨迹分析...",
                                    "current_influences": "当前大运与流年影响分析...",
                                    "special_elements": "特殊格局与神煞分析...",
                                    "practical_recommendations": "实用建议总结..."
                                },
                                "prompt": "请分析以下八字命盘，提供详细的命理解读和人生建议...",
                                "provider": "groq",
                                "model": "llama3.3-70b",
                                "think": "我需要分析这个八字命盘，首先整理基本信息..."
                            },
                            "timestamp": "2023-05-30T12:34:56.789Z"
                        },
                        response_only=True
                    ),
                    OpenApiExample(
                        name='No Analysis Available',
                        value={
                            "status": "not-available"
                        },
                        response_only=True
                    )
                ]
            )
        }
    )
    @extend_schema(  # Document POST
        operation_id="bazi_analysis_post",
        methods=["POST"],
        summary="Generate AI analysis for a BaZi chart",
        description="""
Generate a new analysis (or regenerate an existing one).

## Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `provider` | string | No | LLM provider to use. Possible values: `groq`, `openai`. If not specified, uses the default from settings. |
| `model` | string | No | Specific model to use. See available models below. If not specified, uses the default for the chosen provider. |
| `language` | string | No | Language for the analysis. Possible values: `zh-hans` (Chinese Simplified), `en` (English). Default is `zh-hans`. |
| `regenerate` | boolean | No | Set to `true` to force regeneration of an existing analysis. Default is `false`. |

## Permission Requirements

Regeneration requires special permissions:
- Only users with the `can_regenerate_ai` privilege can regenerate analyses
- Without this privilege, users can only generate an analysis once per chart

## Available Providers and Models

1. **Groq Models**:
   - `llama3.3-70b`: Llama 3.3 70B Versatile - Best quality with 128k context
   - `llama4-maverick`: Llama 4 Maverick 17B - Good balance with 128k context 
   - `llama3.1-8b`: Llama 3.1 8B Instant - Faster model with 128k context

2. **OpenAI Models**:
   - `gpt-4o`: GPT-4o - Latest version, best quality
   - `gpt-4-turbo`: GPT-4 Turbo - Fast with good quality
   - `gpt-3.5-turbo`: GPT-3.5 Turbo - Fastest, good for simpler tasks
   - `gpt-4`: GPT-4 - Original GPT-4 model
   - `gpt-4.1`: GPT-4.1
   - `gpt-5`: GPT-5

If no provider or model is specified, the system will use the default values from settings.
""",
        parameters=[],
        request=inline_serializer(
            name='BaziAnalysisRequest',
            fields={
                'provider': serializers.CharField(
                    required=False, 
                    allow_null=True, 
                    help_text="LLM provider to use. Possible values: 'groq', 'openai'. If not specified, uses the default from settings."
                ),
                'model': serializers.CharField(
                    required=False, 
                    allow_null=True, 
                    help_text="Specific model identifier (e.g., 'llama3.3-70b', 'gpt-4o'). If not specified, uses the default model for the selected provider."
                ),
                'language': serializers.CharField(
                    required=False,
                    default='zh-hans',
                    help_text="Language for the analysis. Possible values: 'zh-hans' (Chinese Simplified), 'en' (English). Default is 'zh-hans'."
                ),
                'regenerate': serializers.BooleanField(
                    required=False, 
                    default=False, 
                    help_text="Set to true to force regeneration of an existing analysis. Requires 'can_regenerate_ai' privilege. Default is false."
                ),
            }
        ),
        examples=[
            OpenApiExample(
                name='Generate Analysis Request',
                value={
                    "provider": "groq",
                    "model": "llama3.3-70b",
                    "regenerate": False
                },
                request_only=True
            ),
            OpenApiExample(
                name='Regenerate Analysis Request',
                value={
                    "provider": "openai",
                    "model": "gpt-4o",
                    "regenerate": True
                },
                request_only=True
            ),
        ],
        responses={
            201: OpenApiResponse(
                response=OpenApiTypes.OBJECT,
                description="Analysis successfully generated",
                examples=[
                    OpenApiExample(
                        name='Analysis Created',
                        value={
                            "status": "completed",
                            "analysis": {
                                "bazi_analysis": {
                                    "personality_analysis": "包含性格分析的文本...",
                                    "life_structure": "命局结构与五行平衡分析...",
                                    "career_wealth": "事业与财富展望分析...",
                                    "relationships": "人际关系与婚姻模式分析...",
                                    "health": "健康考量分析...",
                                    "life_trajectory": "人生轨迹分析...",
                                    "current_influences": "当前大运与流年影响分析...",
                                    "special_elements": "特殊格局与神煞分析...",
                                    "practical_recommendations": "实用建议总结..."
                                },
                                "prompt": "请分析以下八字命盘，提供详细的命理解读和人生建议...",
                                "provider": "groq",
                                "model": "llama3.3-70b",
                                "think": "我需要分析这个八字命盘，首先整理基本信息..."
                            },
                            "timestamp": "2023-05-30T12:34:56.789Z"
                        },
                        response_only=True
                    )
                ]
            ),
            403: OpenApiResponse(
                response=OpenApiTypes.OBJECT,
                description="Permission denied for regeneration",
                examples=[
                    OpenApiExample(
                        'Permission Error',
                        value={
                            "status": "error",
                            "error": "您没有重新生成AI分析的权限。请联系管理员获取权限。"
                        }
                    )
                ]
            ),
            500: OpenApiResponse(
                response=OpenApiTypes.OBJECT,
                description="Error generating analysis",
                examples=[
                    OpenApiExample(
                        'Analysis Error',
                        value={
                            "status": "error",
                            "error": "Failed to connect to AI provider: Connection timeout"
                        }
                    )
                ]
            )
        }
    )
    @action(detail=True, methods=['get', 'post'], url_path='analysis')
    def analysis(self, request, pk=None):
        instance = self.get_object()
        
        # Handle GET request - only retrieve existing analysis
        if request.method == 'GET':
            # Check if analysis exists and is completed
            if instance.ai_analysis and instance.analysis_status == 'completed':
                return Response({
                    'status': 'completed',
                    'analysis': instance.ai_analysis,
                    'timestamp': instance.analysis_timestamp,
                    'analysis_status': instance.analysis_status,
                    'bazi_analysis_reported': instance.bazi_analysis_reported,
                    'bazi_report_status': instance.bazi_report_status,
                    'bazi_report_category': instance.bazi_report_category,
                    'bazi_report_message': instance.bazi_report_message,
                    'bazi_report_admin_notes': instance.bazi_report_admin_notes,
                    'bazi_report_timestamp': instance.bazi_report_timestamp,
                    'bazi_report_resolved_at': instance.bazi_report_resolved_at,
                    'number_analysis_reported': instance.number_analysis_reported,
                    'number_report_status': instance.number_report_status,
                    'number_report_category': instance.number_report_category,
                    'number_report_message': instance.number_report_message,
                    'number_report_admin_notes': instance.number_report_admin_notes,
                    'number_report_timestamp': instance.number_report_timestamp,
                    'number_report_resolved_at': instance.number_report_resolved_at
                })
            else:
                # Return not-available status if no analysis exists
                return Response({
                    'status': 'not-available'
                })
        
        # Handle POST request - generate or regenerate analysis
        elif request.method == 'POST':
            # Get parameters from request data only (not from query params)
            provider = request.data.get('provider')
            model = request.data.get('model')
            language = request.data.get('language', 'zh-hans')  # Default to Chinese
            
            # Validate language
            from ai.utils.i18n import validate_language
            language = validate_language(language)
            
            # Get defaults from configuration for any missing values
            try:
                config = get_ai_config('bazi')
                if not provider:
                    provider = config.get('provider')
                if not model:
                    model = config.get('model')
            except Exception as e:
                logger.warning(f"Could not get default AI config for bazi: {e}")
                # Fall back to None values, which will use system defaults
            
            # Handle regenerate as a boolean, properly parsing string values
            regenerate_value = request.data.get('regenerate', 'false')
            if isinstance(regenerate_value, bool):
                regenerate = regenerate_value
            else:
                regenerate = regenerate_value.lower() == 'true'
            
            # Check if analysis exists
            if instance.ai_analysis and instance.analysis_status == 'completed':
                # If regenerate is False, return the existing analysis instead of regenerating
                if not regenerate:
                                    return Response({
                    'status': 'completed',
                    'analysis': instance.ai_analysis,
                    'timestamp': instance.analysis_timestamp,
                    'analysis_status': instance.analysis_status,
                    'bazi_analysis_reported': instance.bazi_analysis_reported,
                    'bazi_report_status': instance.bazi_report_status,
                    'bazi_report_category': instance.bazi_report_category,
                    'bazi_report_message': instance.bazi_report_message,
                    'bazi_report_admin_notes': instance.bazi_report_admin_notes,
                    'bazi_report_timestamp': instance.bazi_report_timestamp,
                    'bazi_report_resolved_at': instance.bazi_report_resolved_at,
                    'number_analysis_reported': instance.number_analysis_reported,
                    'number_report_status': instance.number_report_status,
                    'number_report_category': instance.number_report_category,
                    'number_report_message': instance.number_report_message,
                    'number_report_admin_notes': instance.number_report_admin_notes,
                    'number_report_timestamp': instance.number_report_timestamp,
                    'number_report_resolved_at': instance.number_report_resolved_at
                })
                    
                # If regenerate is True, check permissions
                user = request.user
                can_regenerate = False
                
                if user.is_authenticated and hasattr(user, 'profile') and user.profile.can_regenerate_ai:
                    can_regenerate = True
                        
                if not can_regenerate:
                    return Response({
                        'status': 'error',
                        'error': '您没有重新生成AI分析的权限。请联系管理员获取权限。'
                    }, status=403)
            
            # Ensure the BaZi data is properly calculated
            if not instance.bazi_result:
                instance.calculate_bazi()
                instance.save(update_fields=['bazi_result'])
            
            # Generate analysis
            try:
                # Import here to avoid circular imports
                from ai.utils.bazi import analyze_bazi
                
                # Update status to pending
                instance.analysis_status = 'pending'
                instance.analysis_timestamp = timezone.now()
                instance.save(update_fields=['analysis_status', 'analysis_timestamp'])
                
                # Process analysis synchronously
                result = analyze_bazi(instance, model_key=model, provider=provider, language=language)
                
                # Update status to completed
                instance.analysis_status = 'completed'
                instance.save(update_fields=['analysis_status'])
                
                return Response({
                    'status': 'completed',
                    'analysis': result,
                    'timestamp': instance.analysis_timestamp,
                    'analysis_status': instance.analysis_status,
                    'bazi_analysis_reported': instance.bazi_analysis_reported,
                    'bazi_report_status': instance.bazi_report_status,
                    'bazi_report_category': instance.bazi_report_category,
                    'bazi_report_message': instance.bazi_report_message,
                    'bazi_report_admin_notes': instance.bazi_report_admin_notes,
                    'bazi_report_timestamp': instance.bazi_report_timestamp,
                    'bazi_report_resolved_at': instance.bazi_report_resolved_at,
                    'number_analysis_reported': instance.number_analysis_reported,
                    'number_report_status': instance.number_report_status,
                    'number_report_category': instance.number_report_category,
                    'number_report_message': instance.number_report_message,
                    'number_report_admin_notes': instance.number_report_admin_notes,
                    'number_report_timestamp': instance.number_report_timestamp,
                    'number_report_resolved_at': instance.number_report_resolved_at
                }, status=201)
            except ValueError as e:
                # Handle specific ValueErrors with detailed messages
                error_message = str(e)
                instance.analysis_status = 'error'
                instance.save(update_fields=['analysis_status'])
                
                # Create more user-friendly error messages
                if "Failed to calculate BaZi data" in error_message:
                    user_message = "无法计算八字数据，请确保出生日期和时间正确。如果您是双胞胎，请确保填写了父亲或母亲的出生日期。"
                elif "BaZi data is missing required pillars" in error_message:
                    user_message = "八字数据缺少必要的柱位，请确保出生日期和时间正确。"
                elif "BaZi data has None for" in error_message:
                    user_message = "八字数据计算出现问题，请重新检查出生信息是否准确。"
                elif "Could not initialize LLM service" in error_message:
                    user_message = "AI服务暂时不可用，请稍后再试。"
                elif "Could not change model" in error_message:
                    user_message = "所选AI模型暂时不可用，请尝试其他模型。"
                elif "Could not prepare BaZi prompt" in error_message:
                    user_message = "准备分析提示时出错，可能是八字数据格式有问题。请重新检查出生信息。"
                elif "Error getting completion from LLM service" in error_message:
                    user_message = "从AI服务获取分析结果时出错，请稍后再试。"
                elif "Error processing LLM response" in error_message:
                    user_message = "处理AI回复时出错，AI可能返回了意外的格式。请尝试其他模型。"
                elif "Error preparing analysis dictionary" in error_message:
                    user_message = "准备分析结果时出错，请尝试使用其他AI模型。"
                elif "Error saving analysis to database" in error_message:
                    user_message = "保存分析结果时出错，请稍后再试。"
                else:
                    user_message = f"分析生成失败: {error_message}"
                
                logger.error(f"BaZi analysis error for person {instance.id}: {error_message}")
                
                return Response({
                    'status': 'error',
                    'error': user_message,
                    'detail': error_message  # Include original error for debugging
                }, status=500)
            except Exception as e:
                # Handle errors
                error_message = str(e)
                instance.analysis_status = 'error'
                instance.save(update_fields=['analysis_status'])
                logger.error(f"Unexpected error in BaZi analysis for person {instance.id}: {error_message}", exc_info=True)
                
                return Response({
                    'status': 'error',
                    'error': '分析过程中发生意外错误，请稍后再试或联系客服。',
                    'detail': error_message  # Include original error for debugging
                }, status=500)
    
    @extend_schema(
        summary="Get BaZi records with relationships",
        description="""
Returns paginated list of BaZi records that have pairwise relationship data with the logged-in user.
This endpoint filters out records with no relationships for cleaner pairwise relationship analysis.

## Purpose
This endpoint is specifically designed for the **Pairwise Relations** tab in the relations page,
showing only people who have meaningful relationships (good or bad) with the current user.

## Filtering Logic
- **Owner Records**: Excluded (only shows other people's records)
- **Relationship Requirement**: Must have `relation_good_count > 0` OR `relation_bad_count > 0`
- **Ordering**: By creation date (newest first)

## Response Structure

### Paginated Response
```json
{
  "count": 25,
  "next": "http://example.com/api/bazi/bazi/with-relations/?page=2",
  "previous": null,
  "results": [
    {
      "id": 123,
      "name": "张三",
      "gender": "M",
      "birth_date": "1990-01-01",
      "birth_time": "10:00:00",
      "bazi_result": {
        "year": {"god": "庚", "earth": "午"},
        "month": {"god": "己", "earth": "丑"},
        "day": {"god": "甲", "earth": "子"},
        "hour": {"god": "乙", "earth": "丑"}
      },
      "relation_good": [
        {
          "t": "r",
          "c": 0,
          "i": null,
          "5e": null
        },
        {
          "t": "r", 
          "c": 2,
          "i": null,
          "5e": 2
        }
      ],
      "relation_bad": [
        {
          "t": "r",
          "c": 3,
          "i": null,
          "5e": null
        }
      ],
      "relation_good_count": 2,
      "relation_bad_count": 1
    }
  ]
}
```

### Non-Paginated Response
```json
{
  "count": 5,
  "results": [...]
}
```

## Relationship Data Structure

### Good Relationships (`relation_good`)
Array of positive relationship reasons (mix of relations and sha gods):

**Relations (`t = "r"`):**
- **六合 (Liu He - Six Harmony)**: `{"t": "r", "c": 0}`
- **半三合 (Half Triad)**: `{"t": "r", "c": 1}`
- **天干合 (Heavenly Stems Harmony)**: `{"t": "r", "c": 2, "5e": <element_index>}`

**Sha Gods (`t = "s"`):**
- **天乙贵人 (Tian Yi Noble)**: `{"t": "s", "i": 0}`
- **文昌贵人 (Wen Chang Noble)**: `{"t": "s", "i": 5}`
- **桃花 (Peach Blossom)**: `{"t": "s", "i": 7}`

### Bad Relationships (`relation_bad`)
Array of negative relationship reasons (relations only):

**Relations (`t = "r"`):**
- **六冲 (Liu Chong - Six Clash)**: `{"t": "r", "c": 3}`
- **六害 (Liu Hai - Six Harm)**: `{"t": "r", "c": 4}`
- **刑 (Punishment)**: `{"t": "r", "c": 5}`

### Type Field (`t`) Explanation
- `"r"`: **Relations** - Direct BaZi interactions (harmony, clash, etc.)
- `"s"`: **Sha Gods** - Auspicious/inauspicious deities based on traditional calculations

### Index Mappings

- Relation Codes (`c` when `t = "r"`): `0: 六合, 1: 半三合, 2: 天干合, 3: 六冲, 4: 六害, 5: 刑`

- Sha God Indices (`i` when `t = "s"`) (from `gShagodNames` in `bzshagod.py`): `0: 天乙, 1: 天德, 2: 月德, 3: 太极, 4: 禄神, 5: 文昌, 6: 驿马, 7: 桃花, 8: 红鸾, 9: 天喜, 10: 将星, 11: 福星, 12: 华盖, 13: 劫煞, 14: 亡神, 15: 羊刃, 16: 灾煞, 17: 孤辰, 18: 寡宿, 19: 阴差阳错, 20: 天医, 21: 飞刃, 22: 隔角, 23: 十恶大败, 24: 六秀, 25: 金舆`

- Five Elements (`5e`) — only for 天干合 (`t = "r"`, `c = 2`): `0: 木, 1: 火, 2: 土, 3: 金, 4: 水`

## Pagination
- **Default page size**: Follows `PAGINATE_BY['default']` setting
- **Page parameter**: `?page=N` where N is the page number
- **Page size parameter**: `?page_size=N` to override default

## Use Cases
1. **Relations Page**: Display pairwise relationships in the first tab
2. **Relationship Analysis**: Show only people with meaningful connections
3. **Data Filtering**: Avoid displaying empty relationship records
4. **Performance**: Efficient pagination for large datasets

## Authentication
Requires authenticated user. Returns empty results for unauthenticated requests.
        """,
        parameters=[
            OpenApiParameter(
                name='page', 
                type=OpenApiTypes.INT, 
                location=OpenApiParameter.QUERY, 
                required=False,
                description='Page number for pagination (default: 1)'
            ),
            OpenApiParameter(
                name='page_size', 
                type=OpenApiTypes.INT, 
                location=OpenApiParameter.QUERY, 
                required=False,
                description='Number of results per page (default: follows PAGINATE_BY setting)'
            ),
        ],
        responses={
            200: inline_serializer(
                name='BaziWithRelationsResponse',
                fields={
                    'count': serializers.IntegerField(
                        help_text='Total number of BaZi records with relationships'
                    ),
                    'next': serializers.URLField(
                        required=False, 
                        allow_null=True,
                        help_text='URL for next page of results (if available)'
                    ),
                    'previous': serializers.URLField(
                        required=False, 
                        allow_null=True,
                        help_text='URL for previous page of results (if available)'
                    ),
                    'results': serializers.ListField(
                        child=inline_serializer(
                            name='BaziPersonWithRelations',
                            fields={
                                'id': serializers.IntegerField(help_text='Person ID'),
                                'name': serializers.CharField(help_text='Person name'),
                                'gender': serializers.ChoiceField(
                                    choices=['M', 'F'],
                                    help_text='Gender: M=Male, F=Female'
                                ),
                                'birth_date': serializers.DateField(help_text='Birth date in YYYY-MM-DD format'),
                                'birth_time': serializers.TimeField(
                                    required=False,
                                    help_text='Birth time in HH:MM:SS format (may be null)'
                                ),
                                'bazi_result': serializers.DictField(
                                    help_text='Four pillars BaZi calculation result'
                                ),
                                'relation_good': serializers.ListField(
                                    child=serializers.DictField(),
                                    required=False,
                                    help_text='Array of good relationship reasons'
                                ),
                                'relation_bad': serializers.ListField(
                                    child=serializers.DictField(),
                                    required=False,
                                    help_text='Array of bad relationship reasons'
                                ),
                                'relation_good_count': serializers.IntegerField(
                                    help_text='Number of good relationships found'
                                ),
                                'relation_bad_count': serializers.IntegerField(
                                    help_text='Number of bad relationships found'
                                ),
                            }
                        ),
                        help_text='Array of BaZi person records with relationship data'
                    ),
                }
            ),
            401: inline_serializer(
                name='UnauthorizedResponse',
                fields={
                    'detail': serializers.CharField(default='Authentication credentials were not provided.')
                }
            ),
        }
    )
    @action(detail=False, methods=['get'], url_path='with-relations')
    def with_relations(self, request):
        """
        Return only BaZi records that have pairwise relationships (relation_good_count > 0 or relation_bad_count > 0).
        """
        if not request.user or not request.user.is_authenticated:
            return Response({'results': [], 'count': 0}, status=200)
        
        # Filter records with relationships (owner=False means other people's records that have relationships)
        queryset = BaziPerson.objects.filter(
            created_by=request.user,
            owner=False
        ).filter(
            Q(relation_good_count__gt=0) | Q(relation_bad_count__gt=0)
        ).order_by('-created_at')
        
        # Apply pagination
        page = self.paginate_queryset(queryset)
        if page is not None:
            # Format each record
            formatted_results = []
            for person in page:
                formatted_results.append(self._format_bazi_response(person))
            
            return self.get_paginated_response(formatted_results)
        
        # No pagination
        formatted_results = []
        for person in queryset:
            formatted_results.append(self._format_bazi_response(person))
        
        return Response({'results': formatted_results, 'count': len(formatted_results)})

class TongshuCalendarAPIView(APIView):
    """API view for retrieving Chinese calendar data."""
    permission_classes = [AllowAny]

    @extend_schema(
        summary="Get monthly calendar data",
        description="""
Retrieve Chinese calendar data for a specific month, including:
- Lunar dates
- Solar terms
- Heavenly stems and earthly branches
- Five elements
- Zodiac animals
- Navigation links to adjacent months

If no year or month is provided, the current month is returned.

**Response Structure:**

- `year`: Requested year (integer)
- `month`: Requested month (integer, 1-12)
- `month_god`: Month's heavenly stem (天干) in Chinese, calculated using the 15th day
- `month_earth`: Month's earthly branch (地支) in Chinese, calculated using the 15th day
- `days`: Two-dimensional array of day objects (organized by weeks)
  - Each day object contains:
    - `date`: ISO date string (YYYY-MM-DD)
    - `day`: Day of month (integer)
    - `is_adjacent_month`: Whether this day belongs to adjacent month (boolean)
    - `is_today`: Whether this day is today (boolean)
    - `bazi`: Four pillars information
      - `year`, `month`, `day`, `hour`: Each pillar contains:
        - `god`: Heavenly stem (天干) in Chinese
        - `earth`: Earthly branch (地支) in Chinese
    - `lunar`: Raw lunar date information
      - `d`: Lunar day (integer)
      - `m`: Lunar month (integer)
      - `y`: Lunar year (integer)
      - `leap`: Whether this is a leap month (boolean)
    - `lunar_formatted`: Full lunar date in Chinese characters
    - `lunar_title`: Lunar month in Chinese characters
    - `lunar_desc`: Lunar day in Chinese characters
    - `lunar_leap`: Leap month indicator, if applicable
    - `jianchu`: Jianchu (建除) cycle value in Chinese
    - `has_solar_term`: Whether this day has a solar term (boolean)
    - `solar_term_index`: Index of the solar term (0-23)
    - `month_earth`: Month's earthly branch in Chinese
    - `month_god`: Month's heavenly stem in Chinese
    - `month_element`: Month's five element in English (wood, fire, earth, metal, water)
- `solar_terms`: Array of solar terms in this month
  - Each solar term contains:
    - `name`: Name of the solar term in Chinese
    - `date`: Date and time (ISO format)
    - `index`: Index of the solar term
- `prev_month`: Reference to previous month
  - `year`: Year of previous month
  - `month`: Month number of previous month
- `next_month`: Reference to next month
  - `year`: Year of next month
  - `month`: Month number of next month
""",
        parameters=[
            OpenApiParameter(
                name="year",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Year (defaults to current year if not provided)",
                required=False,
            ),
            OpenApiParameter(
                name="month",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Month (1-12, defaults to current month if not provided)",
                required=False,
            ),
        ],
        responses={
            200: OpenApiTypes.OBJECT,
            400: OpenApiTypes.OBJECT,
        },
        examples=[
            OpenApiExample(
                name='Success Response',
                value={
                    "year": 2025,
                    "month": 3,
                    "month_god": "戊",
                    "month_earth": "寅",
                    "days": [
                        [
                            {
                                "date": "2025-02-24",
                                "day": 24,
                                "is_adjacent_month": True,
                                "is_today": False,
                                "bazi": {
                                    "year": {
                                        "god": "乙",
                                        "earth": "巳"
                                    },
                                    "month": {
                                        "god": "戊",
                                        "earth": "寅"
                                    },
                                    "day": {
                                        "god": "甲",
                                        "earth": "子"
                                    },
                                    "hour": {
                                        "god": "庚",
                                        "earth": "午"
                                    }
                                },
                                "lunar": {
                                    "d": 27,
                                    "m": 1,
                                    "y": 2025,
                                    "leap": False
                                },
                                "lunar_formatted": "正月廿七",
                                "lunar_title": "正月",
                                "lunar_desc": "廿七",
                                "lunar_leap": "",
                                "jianchu": "开",
                                "has_solar_term": False,
                                "solar_term_index": 0,
                                "month_earth": "寅",
                                "month_god": "戊",
                                "month_element": "wood"
                            },
                            {
                                "date": "2025-03-05",
                                "day": 5,
                                "is_adjacent_month": False,
                                "is_today": False,
                                "bazi": {
                                    "year": {
                                        "god": "乙",
                                        "earth": "巳"
                                    },
                                    "month": {
                                        "god": "戊",
                                        "earth": "寅"
                                    },
                                    "day": {
                                        "god": "癸",
                                        "earth": "酉"
                                    },
                                    "hour": {
                                        "god": "戊",
                                        "earth": "午"
                                    }
                                },
                                "lunar": {
                                    "d": 6,
                                    "m": 2,
                                    "y": 2025,
                                    "leap": False
                                },
                                "lunar_formatted": "惊蛰 16:07",
                                "lunar_title": "惊蛰",
                                "lunar_desc": "16:07",
                                "lunar_leap": "",
                                "jianchu": "破",
                                "has_solar_term": True,
                                "solar_term_index": 2,
                                "month_earth": "寅",
                                "month_god": "戊",
                                "month_element": "wood"
                            }
                        ]
                    ],
                    "solar_terms": [
                        {
                            "name": "惊蛰",
                            "date": "2025-03-05T08:07:16",
                            "index": 2
                        },
                        {
                            "name": "春分",
                            "date": "2025-03-20T09:01:29",
                            "index": 3
                        }
                    ],
                    "prev_month": {
                        "year": 2025,
                        "month": 2
                    },
                    "next_month": {
                        "year": 2025,
                        "month": 4
                    }
                },
                response_only=True,
                status_codes=["200"],
            ),
            OpenApiExample(
                name='Error Response',
                value={
                    "error": "Invalid month. Must be between 1 and 12."
                },
                response_only=True,
                status_codes=["400"],
            ),
        ],
    )
    def get(self, request, *args, **kwargs):
        """Get calendar data for a specific month or the current month."""
        serializer = TongshuCalendarQuerySerializer(data=request.query_params)
        
        if not serializer.is_valid():
            return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

        # Get year and month from query params or use current date
        validated_data = serializer.validated_data
        today = timezone.now().date()
        year = validated_data.get('year', today.year)
        month = validated_data.get('month', today.month)

        # Validate month
        if not 1 <= month <= 12:
            return Response({"error": "Invalid month. Must be between 1 and 12."}, 
                           status=status.HTTP_400_BAD_REQUEST)

        # Use the existing view logic from tongshu app, but adapted for API
        from tongshu.views import get_month_calendar, get_solar_terms
        
        try:
            # Get calendar data
            days_data = get_month_calendar(year, month)
            solar_terms = get_solar_terms(year, month)
            
            # Serialize datetime objects in days_data
            for day_data in days_data:
                if 'date' in day_data and isinstance(day_data['date'], date):
                    day_data['date'] = day_data['date'].isoformat()
                    
                if 'solar_terms' in day_data and day_data['solar_terms']:
                    for term in day_data['solar_terms']:
                        if 'date' in term and isinstance(term['date'], datetime):
                            term['date'] = term['date'].isoformat()
            
            # Serialize datetime objects in solar_terms
            for term_name in solar_terms:
                term = solar_terms[term_name]
                if 'd' in term and isinstance(term['d'], str):
                    # Convert ISO format to a more readable format if needed
                    dt = datetime.fromisoformat(term['d'].replace('Z', '+00:00'))
                    term['date'] = dt.isoformat()
                    
                if 'date' not in term and 'd' in term:
                    term['date'] = term['d']
            
            # Standardize solar_terms to be a list for response
            solar_terms_list = []
            if 'first' in solar_terms:
                solar_terms_list.append({
                    'name': solar_terms['first']['c'],
                    'date': solar_terms['first'].get('date', solar_terms['first']['d']),
                    'index': solar_terms['first'].get('i', 0)
                })
            if 'second' in solar_terms:
                solar_terms_list.append({
                    'name': solar_terms['second']['c'],
                    'date': solar_terms['second'].get('date', solar_terms['second']['d']),
                    'index': solar_terms['second'].get('i', 0)
                })
            
            # Determine previous and next months
            if month == 1:
                prev_month = {'year': year - 1, 'month': 12}
            else:
                prev_month = {'year': year, 'month': month - 1}
                
            if month == 12:
                next_month = {'year': year + 1, 'month': 1}
            else:
                next_month = {'year': year, 'month': month + 1}
            
            # Get the god/earth for the month
            month_bazi = bazi.calcBazi(year, month, 15)
            # Format and return data
            response_data = {
                'year': year,
                'month': month,
                'month_god': bazi.gGodstem[month_bazi['month']['g']],
                'month_earth': bazi.gEarthstem[month_bazi['month']['e']],
                'days': days_data,
                'solar_terms': solar_terms_list,
                'prev_month': prev_month,
                'next_month': next_month,
            }
            
            return Response(response_data)
        except Exception as e:
            return Response({"error": f"Error calculating calendar data: {str(e)}"}, 
                           status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class TongshuCalendarAPIViewV2(APIView):
    """API view for retrieving Chinese calendar data using the optimized TongshuCalendar model."""
    permission_classes = [AllowAny]

    @extend_schema(
        summary="Get optimized monthly calendar data (V2)",
        description="""
Retrieve Chinese calendar data for a specific month using the optimized TongshuCalendar model.
This endpoint provides the same data as the original endpoint but with improved performance.

Features:
- Optimized data structure for faster processing
- Compact arrays for reduced payload size
- Elements represented as indices (wood=0, fire=1, earth=2, metal=3, water=4)

If no year or month is provided, the current month is returned.

**Response Structure:**

- `year`: Requested year (integer)
- `month`: Requested month (integer, 1-12)
- `month_bazi`: Month's BaZi information [year_god_index, year_earth_index, month_god_index, month_earth_index]
- `month_5e`: Month's five element index (0=wood, 1=fire, 2=earth, 3=metal, 4=water)
- `days`: Two-dimensional array of day objects (organized by weeks)
  - Each day object contains:
    - `date`: ISO date string (YYYY-MM-DD)
    - `day`: Day of month (integer)
    - `is_today`: Whether this day is today (1=true, 0=false)
    - `bazi`: Three pillars information as indices [year_god, year_earth, month_god, month_earth, day_god, day_earth]
    - `lunar`: Lunar date information [day, month, year, is_leap]
    - `jianchu`: Jianchu (建除) cycle index (0: "建", 1: "除", 2: "满", 3: "平", 4: "定", 5: "执", 6: "破", 7: "危", 8: "成", 9: "收", 10: "开", 11: "闭")
    - `solar`: Solar term information with conditional time:
      - For regular days: [term_index, day_is_term, term_5element_index]
      - For solar term days: [term_index, day_is_term, term_5element_index, term_time]
      where:
        - term_index: Solar term index (0-23)
            - 0: "立春", 1: "雨水", 2: "惊蛰", 3: "春分", 4: "清明", 5: "谷雨", 6: "立夏", 7: "小满", 8: "芒种", 9: "夏至", 10: "小暑", 11: "大暑", 12: "立秋", 13: "处暑", 14: "白露", 15: "秋分", 16: "寒露", 17: "霜降", 18: "立冬", 19: "小雪", 20: "大雪", 21: "冬至", 22: "小寒", 23: "大寒"
        - day_is_term: Whether this day is a solar term (1=true, 0=false)
        - term_5element_index: Five element index (0=wood, 1=fire, 2=earth, 3=metal, 4=water)
        - term_time: Only present when day_is_term=1, contains the time of the solar term in "HH:MM" format
    - `ybp`: Yellow/Black Path (黄黑道) index (0-11):
        - God index mappings: 0: "青龙" (good), 1: "明堂" (good), 2: "天刑" (bad), 3: "朱雀" (bad), 4: "金匮" (good), 5: "天德" (good), 6: "白虎" (bad), 7: "玉堂" (good), 8: "天牢" (bad), 9: "玄武" (bad), 10: "司命" (good), 11: "勾陈" (bad)
- `prev_month`: Reference to previous month [year, month]
- `next_month`: Reference to next month [year, month]
""",
        parameters=[
            OpenApiParameter(
                name="year",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Year (defaults to current year if not provided)",
                required=False,
            ),
            OpenApiParameter(
                name="month",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Month (1-12, defaults to current month if not provided)",
                required=False,
            ),
            OpenApiParameter(
                name="extra_days",
                type=int,
                location=OpenApiParameter.QUERY,
                description="Number of extra days to include before/after the month (default varies based on month layout, maximum 100, negative values treated as 0)",
                required=False,
            ),
        ],
        responses={
            200: OpenApiTypes.OBJECT,
            400: OpenApiTypes.OBJECT,
        },
        examples=[
            OpenApiExample(
                name='Success Response',
                value={
                    "year": 2025,
                    "month": 3,
                    "month_bazi": [4, 2, 4, 2],  # [year_god_index, year_earth_index, month_god_index, month_earth_index]
                    "month_5e": 0,  # wood=0, fire=1, earth=2, metal=3, water=4
                    "days": [
                        [
                            ["2025-02-24", 24, 0, [1, 5, 4, 2, 0, 0], [27, 1, 2025, 0], 10, [0, 0, 0], 0],
                            ["2025-02-25", 25, 0, [1, 5, 4, 2, 1, 1], [28, 1, 2025, 0], 11, [0, 0, 0], 1],
                            # More days...
                        ],
                        [
                            # Regular day example
                            ["2025-03-01", 1, 0, [1, 5, 4, 2, 5, 5], [3, 2, 2025, 0], 1, [2, 0, 0], 5],
                            # Solar term day example
                            ["2025-03-05", 5, 0, [1, 5, 4, 2, 9, 9], [7, 2, 2025, 0], 5, [2, 1, 0, "16:07"], 10],
                            # More days...
                        ],
                        # More weeks...
                    ],
                    "prev_month": [2025, 2],
                    "next_month": [2025, 4]
                },
                response_only=True,
                status_codes=["200"],
            ),
            OpenApiExample(
                name='Error Response',
                value={
                    "error": "Invalid month. Must be between 1 and 12."
                },
                response_only=True,
                status_codes=["400"],
            ),
        ],
    )
    def get(self, request, *args, **kwargs):
        """Get calendar data for a specific month or the current month using the optimized TongshuCalendar model."""
        serializer = TongshuCalendarQuerySerializer(data=request.query_params)
        
        if not serializer.is_valid():
            return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

        # Get year and month from query params or use current date
        validated_data = serializer.validated_data
        today = timezone.now().date()
        year = validated_data.get('year', today.year)
        month = validated_data.get('month', today.month)
        
        # Get extra_days parameter (optional)
        extra_days = request.query_params.get('extra_days')
        if extra_days is not None:
            try:
                extra_days = int(extra_days)
                # Handle negative values by setting to 0
                if extra_days < 0:
                    extra_days = 0
                # Cap extra_days at 100
                extra_days = min(extra_days, 100)
            except ValueError:
                return Response({"error": "extra_days must be a valid integer"}, 
                              status=status.HTTP_400_BAD_REQUEST)
        else:
            # Calculate default extra_days if not provided
            first_day = date(year, month, 1)
            first_weekday = first_day.weekday()  # 0 = Monday, 6 = Sunday
            
            _, num_days = monthrange(year, month)
            last_day = date(year, month, num_days)
            last_weekday = last_day.weekday()
            
            # Calculate days needed before and after
            days_before = first_weekday
            days_after = 6 - last_weekday
            
            # If there are 3 or fewer days after, add an additional week
            if days_after <= 3:
                days_after += 7
            
            # Get extra days needed for padding
            extra_days = max(days_before, days_after)

        # Validate month
        if not 1 <= month <= 12:
            return Response({"error": "Invalid month. Must be between 1 and 12."}, 
                           status=status.HTTP_400_BAD_REQUEST)

        # Import the TongshuCalendar model
        from tongshu.models import TongshuCalendar
        
        try:
            # Get optimized calendar data - exclude lunar_title_desc for API
            calendar_data = TongshuCalendar.get_month_data(year, month, extra_days=extra_days, include_lunar_title_desc=False)
            
            # Format and return data
            response_data = {
                'year': calendar_data[0],
                'month': calendar_data[1],
                'month_bazi': calendar_data[2],
                'month_5e': calendar_data[3],
                'days': calendar_data[4],
                'prev_month': calendar_data[5],
                'next_month': calendar_data[6]
            }
            
            return Response(response_data)
        except Exception as e:
            return Response({"error": f"Error calculating calendar data: {str(e)}"}, 
                           status=status.HTTP_500_INTERNAL_SERVER_ERROR)

@extend_schema(
    summary="Get yearly Chinese calendar data",
    description="""
Retrieve comprehensive Chinese calendar data for an entire year.

**Access Control:**
- **Public users**: Limited to accessing only the previous year, current year, and next year
- **Authenticated users**: Can access calendar data for any year (range 1900-2100)

**Response Structure:**

- `year`: Requested year (integer)
- `year_data`: Information about the requested year
  - `element`: Year's element in English (wood/fire/earth/metal/water)
  - `animal`: Chinese zodiac animal (龙/蛇/马/羊/猴/鸡/狗/猪/鼠/牛/虎/兔)
  - `star`: Flying star (1白/2黑/3碧/4绿/5黄/6白/7赤/8白/9紫)
- `cycles`: Array of monthly cycles data (one entry per month, 12 total)
  - Each cycle contains:
    - `first`: First solar term data of the month
      - `i`: Solar term index (0-23)
      - `c`: Name of the solar term in Chinese
      - `d`: Date and time of the solar term (YYYY-MM-DD HH:MM:SS format)
      - `next`: Date and time of the next solar term (YYYY-MM-DD HH:MM:SS format)
      - `bz`: Bazi data (Chinese four pillars) for this solar term
        - `year`, `month`, `day`, `hour`: Each pillar contains indices:
          - `g`: Heavenly stem index (天干)
          - `e`: Earthly branch index (地支)
        - `month` also contains:
          - `element`: Element of the month in English (wood/fire/earth/metal/water)
      - `dates`: Array of days in this half of the month
        - Each day contains:
          - `lunar`: Lunar date information
            - `d`: Lunar day (integer)
            - `m`: Lunar month (integer)
            - `y`: Lunar year (integer)
            - `leap`: Whether this is a leap month (boolean)
          - `d`: Day of month (integer)
          - `m`: Month number (integer)
          - `w`: Weekday (0=Monday through 6=Sunday)
          - `jc12`: Jianchu (建除) cycle index, 0: "建", 1: "除", 2: "满", 3: "平", 4: "定", 5: "执", 6: "破", 7: "危", 8: "成", 9: "收", 10: "开", 11: "闭"
    - `second`: Second solar term data of the month (same structure as `first`)
    - `count`: Count of days in this month
    - `star`: Flying star for this month (1白/2黑/3碧/4绿/5黄/6白/7赤/8白/9紫)
- `paginate`: Pagination information
  - `prev`: Previous year (or false if at lower boundary)
  - `next`: Next year (or false if at upper boundary)
""",
    parameters=[
        OpenApiParameter(
            name="year",
            type=int,
            location=OpenApiParameter.QUERY,
            description="Year to retrieve calendar data for (range: 1900-2100)",
            required=True,
            examples=[
                OpenApiExample(
                    'Current Year',
                    value=datetime.now().year
                ),
                OpenApiExample(
                    'Future Year',
                    value=datetime.now().year + 1
                ),
            ]
        ),
    ],
    request=Calendar10kQuerySerializer,
    responses={
        200: OpenApiResponse(
            response=Calendar10kSerializer,
            description="Successfully retrieved calendar data",
            examples=[
                OpenApiExample(
                    name='Example Calendar10k Response',
                    value={
                        "year": 2025,
                        "year_data": {
                            "element": "fire",
                            "animal": "蛇",
                            "star": "2黑"
                        },
                        "cycles": [
                            {
                                "first": {
                                    "i": 0,
                                    "c": "立春",
                                    "d": "2025-02-03 22:10:28",
                                    "next": "2025-02-18 18:06:34",
                                    "bz": {
                                        "hour": {
                                            "g": 9,
                                            "e": 11
                                        },
                                        "day": {
                                            "g": 9,
                                            "e": 3
                                        },
                                        "month": {
                                            "g": 4,
                                            "e": 2,
                                            "element": "wood"
                                        },
                                        "year": {
                                            "g": 1,
                                            "e": 5
                                        }
                                    },
                                    "dates": [
                                        {
                                            "lunar": {
                                                "d": 6,
                                                "m": 1,
                                                "y": 2025,
                                                "leap": False
                                            },
                                            "d": 3,
                                            "m": 2,
                                            "w": 0,
                                            "jc12": 1
                                        },
                                        {
                                            "lunar": {
                                                "d": 7,
                                                "m": 1,
                                                "y": 2025,
                                                "leap": False
                                            },
                                            "d": 4,
                                            "m": 2,
                                            "w": 1,
                                            "jc12": 2
                                        }
                                    ]
                                },
                                "second": {
                                    "i": 1,
                                    "c": "雨水",
                                    "d": "2025-02-18 18:06:34",
                                    "next": "2025-03-05 16:07:16",
                                    "bz": {
                                        "hour": {
                                            "g": 2,
                                            "e": 9
                                        },
                                        "day": {
                                            "g": 4,
                                            "e": 10
                                        },
                                        "month": {
                                            "g": 4,
                                            "e": 2,
                                            "element": "wood"
                                        },
                                        "year": {
                                            "g": 1,
                                            "e": 5
                                        }
                                    },
                                    "dates": []
                                },
                                "count": 30,
                                "star": "2黑"
                            }
                        ],
                        "paginate": {
                            "next": 2026,
                            "prev": 2024
                        }
                    },
                    status_codes=["200"]
                )
            ]
        ),
        400: OpenApiResponse(
            description="Invalid parameters provided",
            examples=[
                OpenApiExample(
                    name='Invalid Year',
                    value={"error": "Year parameter is required"},
                    status_codes=["400"]
                ),
                OpenApiExample(
                    name='Out of Range',
                    value={"error": "The request year is out of range."},
                    status_codes=["400"]
                )
            ]
        ),
        403: OpenApiResponse(
            description="Access forbidden - attempting to access a year outside the allowed range",
            examples=[
                OpenApiExample(
                    name='Access Restricted',
                    value={"error": "Access to years outside of [2022, 2023, 2024] is restricted to authenticated users"},
                    status_codes=["403"]
                )
            ]
        )
    },
    tags=["calendar10k"]
)
class Calendar10kAPIView(APIView):
    """API view for retrieving calendar10k data for an entire year with access control restrictions."""
    permission_classes = [AllowAny]
    
    def get(self, request, *args, **kwargs):
        """
        Get calendar data for an entire year with access control.
        
        Public users are limited to accessing previous year, current year, and next year.
        Authenticated users can access data for any year between 1900 and 2100.
        """
        # Extract year parameter
        try:
            year = int(request.query_params.get('year', 0))
            if year == 0:
                return Response({"error": "Year parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
        except (ValueError, TypeError):
            return Response({"error": "Year must be a valid integer"}, status=status.HTTP_400_BAD_REQUEST)
        
        # Validate year range
        if not (1900 <= year <= 2100):
            return Response({"error": "The request year is out of range."}, status=status.HTTP_400_BAD_REQUEST)
        
        # Access control: Check if the requested year is allowed
        current_year = timezone.now().year
        if not request.user.is_authenticated:
            allowed_years = [current_year - 1, current_year, current_year + 1]
            if year not in allowed_years:
                return Response(
                    {"error": f"Access to years outside of [{current_year-1}, {current_year}, {current_year+1}] is restricted to authenticated users"}, 
                    status=status.HTTP_403_FORBIDDEN
                )
        
        # Here, we would call the same function as used in the year_view
        try:
            # Import necessary utilities
            from iching.utils import bz, bz24, lunar_calendar
            
            # Calculate the solar terms for the year
            cycles = bz24.calc24CycleAdjustedYear(year, groupByMonth=True)
            
            year_data = {}
            # Process the cycles similar to views.year_view
            for i in range(len(cycles)):
                mdx = 0
                for key, val in cycles[i].items():
                    if key not in ['first', 'second']:
                        continue
                    
                    # convert val['d'] and val['next'] to UTC+8
                    val['d'] = datetime.strptime(val['d'], '%Y-%m-%d %H:%M:%S') + timedelta(hours=8)
                    val['next'] = datetime.strptime(val['next'], '%Y-%m-%d %H:%M:%S') + timedelta(hours=8)

                    date = val['d']
                    bzdate = bz.getCalendar10kGodEarthStem(date.year, date.month, date.day, date.hour, date.minute, date.second)
                    cycles[i][key]['bz'] = bzdate
                    cycles[i][key]['bz']['month']['element'] = bz.getEarthElement(bzdate['month']['e'])
                    # cycles[i][key]['time'] = date.strftime('%H:%M:%S')

                    next_date = val['next']
                    dates = []
                    cur = datetime(date.year, date.month, date.day)
                    end = datetime(next_date.year, next_date.month, next_date.day)
                    first = True
                    
                    while(cur < end):
                        d = {}
                        d['lunar'] = lunar_calendar.convertLunar(cur)
                        d['d'] = cur.day
                        d['m'] = cur.month
                        d['w'] = cur.weekday()

                        # Calculate jian chu 12 god
                        if first:
                            de = bzdate['day']['e']
                            me = bzdate['month']['e']
                        else:
                            de = (de + 1) % 12
                            me = (me + 1) % 12
                        daybz = {'day': {'e': de}, 'month': {'e': me}}
                        d['jc12'] = bz.calcJianChu12God(cur.year, cur.month, cur.day, daybz)

                        first = False
                        dates.append(d)
                        mdx += 1
                        cur = cur + timedelta(days=1)
                    
                    # format d & next to date format
                    val['d'] = val['d'].strftime('%Y-%m-%d %H:%M:%S')
                    val['next'] = val['next'].strftime('%Y-%m-%d %H:%M:%S')

                    cycles[i][key]['dates'] = dates
                
                cycles[i]['count'] = mdx
                cycles[i]['star'] = bz.getStarName(bz.calcMonthFlyingStar(date.year, date.month))
            
            year_data['element'] = bz.getEarthElement(cycles[0]['first']['bz']['year']['e'])
            year_data['animal'] = bz.getEarthAnimal(cycles[0]['first']['bz']['year']['e'])
            year_data['star'] = bz.getStarName(bz.calcYearFlyingStar(year))

            # Pagination for next & prev year
            prevyear = year-1 if year > 1900 else False
            nextyear = year+1 if year < 2100 else False
            paginate = {'next': nextyear, 'prev': prevyear}

            # Create response data
            response_data = {
                'year': year,
                'year_data': {
                    'element': bz.getEarthElement(cycles[0]['first']['bz']['year']['e']),
                    'animal': bz.getEarthAnimal(cycles[0]['first']['bz']['year']['e']),
                    'star': bz.getStarName(bz.calcYearFlyingStar(year))
                },
                'cycles': cycles,
                'paginate': paginate,
            }
            
            return Response(response_data)
            
        except Exception as e:
            return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

@extend_schema(
    description="Request account deletion for authenticated user",
    request=AccountDeletionRequestSerializer,
    responses={
        200: OpenApiResponse(
            {"type": "object", "properties": {
                "message": {"type": "string", "description": "Success message"},
                "email": {"type": "string", "description": "Email where confirmation was sent"}
            }},
            description="Deletion request initiated successfully"
        ),
        401: OpenApiResponse(
            description="Unauthorized - Authentication required"
        )
    },
    examples=[
        OpenApiExample(
            name="Request Example",
            value={
                "reason": "Optional reason for account deletion"
            },
            request_only=True
        ),
        OpenApiExample(
            name="Success Response",
            value={
                "message": "Account deletion request initiated. Please check your email for confirmation.",
                "email": "user@example.com"
            },
            response_only=True,
            status_codes=["200"]
        )
    ],
)
class AccountDeletionRequestView(generics.GenericAPIView):
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]  # Support both JWT and session auth
    permission_classes = [IsAuthenticated]
    serializer_class = AccountDeletionRequestSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # Create deletion request
        expires_at = timezone.now() + timedelta(hours=24)
        deletion_request = AccountDeletionRequest.objects.create(
            user=request.user,
            expires_at=expires_at,
            is_active=True,
            reason=serializer.validated_data.get('reason', '')
        )
        
        # Generate confirmation URL using the site's domain
        confirmation_url = request.build_absolute_uri(
            reverse('accounts:confirm_deletion', kwargs={'token': deletion_request.token})
        )
        
        # Determine user's email
        email = request.user.email or request.user.phone
        
        # Prepare and send email
        context = {
            'user': request.user,
            'confirmation_url': confirmation_url,
            'current_year': timezone.now().year,
            'site_title': getattr(settings, 'SITE_TITLE', '易经'),
        }
        
        html_message = render_to_string('accounts/emails/account_deletion_confirmation.html', context)
        subject = '确认删除您的账户'
        
        send_mail(
            subject=subject,
            message=strip_tags(html_message),
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[email],
            html_message=html_message,
            fail_silently=False,
        )
        
        return Response({
            "message": "Account deletion request initiated. Please check your email for confirmation.",
            "email": email
        }, status=status.HTTP_200_OK)

class AppVersionView(APIView):
    permission_classes = [AllowAny]
    
    def get(self, request, platform):
        if platform not in ['ios', 'android']:
            return Response({'error': 'Invalid platform'}, status=400)
            
        version_info = settings.APP_VERSIONS.get(platform, {
            'version': '0.0.0',
            'enforce': False,
            'descr': 'Version information not available'
        })
        
        return Response(version_info)

@extend_schema(  # Document GET
    operation_id="number_analysis_get",
    methods=["GET"],
    summary="Get AI analysis for a Number chart",
    description="""
Retrieve the AI analysis for a Number chart if it exists.
If no analysis exists, returns a status of "not_found".
""",
    responses={
        200: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Analysis retrieved successfully or status indicated",
            examples=[
                OpenApiExample(
                    name='Completed Analysis',
                    value={
                        "status": "completed",
                        "analysis": {
                            "number_analysis": {
                                "personal_number_meaning": {
                                    "title": "个人数字意义",
                                    "content": "您的个人数字是4，代表成功和稳定。这个数字通常与勤奋、责任感和实用性相关联..."
                                },
                                "character_strengths": {
                                    "title": "性格优势",
                                    "content": "作为数字4的人，您具有出色的组织能力和严谨的逻辑思维..."
                                },
                                "life_challenges": {
                                    "title": "生命挑战",
                                    "content": "您可能面临的主要挑战是过于固执或墨守成规..."
                                },
                                "career_path": {
                                    "title": "事业路径分析",
                                    "content": "您适合需要精确、结构和可靠性的职业领域..."
                                },
                                "relationship_patterns": {
                                    "title": "人际关系模式",
                                    "content": "在关系中，您往往是可靠和值得信赖的伙伴..."
                                }
                            },
                            "prompt": "请分析以下数字命盘，提供详细的解读和人生建议...",
                            "provider": "groq",
                            "model": "llama3.3-70b",
                            "think": "我需要分析这个数字命盘，首先整理基本信息..."
                        },
                        "timestamp": "2023-05-30T12:34:56.789Z"
                    },
                    response_only=True
                ),
                OpenApiExample(
                    name='No Analysis Available',
                    value={
                        "status": "not_found",
                        "message": "No analysis available"
                    },
                    response_only=True
                )
            ]
        )
    }
)
@extend_schema(  # Document POST
    operation_id="number_analysis_post",
    methods=["POST"],
    summary="Generate AI analysis for a Number chart",
    description="""
Generate a new analysis (or regenerate an existing one) for a Number chart.

## Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `regenerate` | boolean | No | Set to `true` to force regeneration of an existing analysis. Default is `false`. |
| `provider` | string | No | LLM provider to use. Possible values: `groq`, `openai`. If not specified, uses the default from settings. |
| `model` | string | No | Specific model to use. See available models in the `/api/bazi/{id}/analysis/` endpoint documentation. If not specified, uses the default for the chosen provider. |

## Permission Requirements

Regeneration requires special permissions:
- Only users with the `can_regenerate_ai` privilege can regenerate analyses
- Without this privilege, users can only generate an analysis once per chart

Refer to the `/api/bazi/{id}/analysis/` endpoint documentation for the list of available models.
""",
    parameters=[],
    request=inline_serializer(
        name='NumberAnalysisRequest',
        fields={
            'regenerate': serializers.BooleanField(
                required=False, 
                default=False, 
                help_text="Set to true to force regeneration of an existing analysis. Requires 'can_regenerate_ai' privilege. Default is false."
            ),
            'provider': serializers.CharField(
                required=False, 
                allow_null=True, 
                help_text="LLM provider to use. Possible values: 'groq', 'openai'. If not specified, uses the default from settings."
            ),
            'model': serializers.CharField(
                required=False, 
                allow_null=True, 
                help_text="Specific model identifier. If not specified, uses the default model for the selected provider."
            ),
        }
    ),
    examples=[
        OpenApiExample(
            name='Generate Analysis Request',
            value={
                "provider": "groq",
                "model": "llama3.3-70b",
                "regenerate": False
            },
            request_only=True
        ),
        OpenApiExample(
            name='Regenerate Analysis Request',
            value={
                "provider": "openai",
                "model": "gpt-4o",
                "regenerate": True
            },
            request_only=True
        ),
    ],
    responses={
        200: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Analysis successfully generated or retrieved",
            examples=[
                OpenApiExample(
                    name='Analysis Created',
                    value={
                        "status": "completed",
                        "analysis": {
                            "number_analysis": {
                                "personal_number_meaning": {
                                    "title": "个人数字意义",
                                    "content": "您的个人数字是4，代表成功和稳定。这个数字通常与勤奋、责任感和实用性相关联..."
                                },
                                "character_strengths": {
                                    "title": "性格优势",
                                    "content": "作为数字4的人，您具有出色的组织能力和严谨的逻辑思维..."
                                },
                                "life_challenges": {
                                    "title": "生命挑战",
                                    "content": "您可能面临的主要挑战是过于固执或墨守成规..."
                                },
                                "career_path": {
                                    "title": "事业路径分析",
                                    "content": "您适合需要精确、结构和可靠性的职业领域..."
                                },
                                "relationship_patterns": {
                                    "title": "人际关系模式",
                                    "content": "在关系中，您往往是可靠和值得信赖的伙伴..."
                                }
                            },
                            "prompt": "请分析以下数字命盘，提供详细的解读和人生建议...",
                            "provider": "groq",
                            "model": "llama3.3-70b",
                            "think": "我需要分析这个数字命盘，首先整理基本信息..."
                        },
                        "timestamp": "2023-05-30T12:34:56.789Z"
                    },
                    response_only=True
                )
            ]
        ),
        403: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Permission denied for regeneration",
            examples=[
                OpenApiExample(
                    'Permission Error',
                    value={
                        "error": "You do not have permission to regenerate analysis"
                    },
                    response_only=True
                )
            ]
        ),
        404: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Person not found",
            examples=[
                OpenApiExample(
                    'Not Found Error',
                    value={
                        "error": "Person not found"
                    },
                    response_only=True
                )
            ]
        ),
        500: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Error generating analysis",
            examples=[
                OpenApiExample(
                    'Analysis Error',
                    value={
                        "error": "Failed to generate analysis",
                        "detail": "Error message with details about what went wrong"
                    },
                    response_only=True
                )
            ]
        )
    }
)
@api_view(['GET', 'POST'])
@permission_classes([AllowAny])  # Base permission class
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
def number_analysis(request, person_id):
    try:
        person = BaziPerson.objects.get(id=person_id)
        
        # Check if person is public or user has access
        if person.created_by and not request.user.is_staff:
            if not request.user.is_authenticated or request.user != person.created_by:
                return Response({
                    'error': 'You do not have permission to access this person'
                }, status=status.HTTP_403_FORBIDDEN)
        
        if request.method == 'GET':
            if not person.number_ai_analysis:
                return Response({
                    'status': 'not_found',
                    'message': 'No analysis available'
                })
            
            return Response({
                'status': 'completed',
                'analysis': person.number_ai_analysis,
                'timestamp': person.number_analysis_timestamp,
                'analysis_status': person.number_analysis_status,
                'number_analysis_reported': person.number_analysis_reported,
                'number_report_status': person.number_report_status,
                'number_report_category': person.number_report_category,
                'number_report_message': person.number_report_message,
                'number_report_admin_notes': person.number_report_admin_notes,
                'number_report_timestamp': person.number_report_timestamp,
                'number_report_resolved_at': person.number_report_resolved_at,
                'bazi_analysis_reported': person.bazi_analysis_reported,
                'bazi_report_status': person.bazi_report_status,
                'bazi_report_category': person.bazi_report_category,
                'bazi_report_message': person.bazi_report_message,
                'bazi_report_admin_notes': person.bazi_report_admin_notes,
                'bazi_report_timestamp': person.bazi_report_timestamp,
                'bazi_report_resolved_at': person.bazi_report_resolved_at
            })
            
        elif request.method == 'POST':
            # Check for regeneration permission if regenerate=True
            regenerate = request.data.get('regenerate', False)
            if isinstance(regenerate, str):
                regenerate = regenerate.lower() in ['true', 'yes', '1', 't']
                
            if regenerate:
                if not request.user.is_authenticated:
                    return Response({
                        'error': 'Authentication required for regeneration'
                    }, status=status.HTTP_403_FORBIDDEN)
                    
                if not request.user.is_staff and not request.user.profile.can_regenerate_ai:
                    return Response({
                        'error': 'You do not have permission to regenerate analysis'
                    }, status=status.HTTP_403_FORBIDDEN)
            
            # Get provider and model from request data
            provider = request.data.get('provider')
            model = request.data.get('model')
            
            # Get defaults from configuration for any missing values
            try:
                config = get_ai_config('number')
                if not provider:
                    provider = config.get('provider')
                if not model:
                    model = config.get('model')
            except Exception as e:
                logger.warning(f"Could not get default AI config for number: {e}")
                # Fall back to None values, which will use system defaults
            
            try:
                # Pass provider and model to analyze_number
                analysis = analyze_number(person, model, provider)
                # Return empty analysis if that's what we got
                if analysis is None:
                    return Response({
                        'error': 'Failed to generate analysis',
                        'detail': 'Analysis generation failed'
                    }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                
                return Response({
                    'status': 'completed',
                    'analysis': analysis,
                    'timestamp': person.number_analysis_timestamp,
                    'analysis_status': person.number_analysis_status,
                    'number_analysis_reported': person.number_analysis_reported,
                    'number_report_status': person.number_report_status,
                    'number_report_category': person.number_report_category,
                    'number_report_message': person.number_report_message,
                    'number_report_admin_notes': person.number_report_admin_notes,
                    'number_report_timestamp': person.number_report_timestamp,
                    'number_report_resolved_at': person.number_report_resolved_at,
                    'bazi_analysis_reported': person.bazi_analysis_reported,
                    'bazi_report_status': person.bazi_report_status,
                    'bazi_report_category': person.bazi_report_category,
                    'bazi_report_message': person.bazi_report_message,
                    'bazi_report_admin_notes': person.bazi_report_admin_notes,
                    'bazi_report_timestamp': person.bazi_report_timestamp,
                    'bazi_report_resolved_at': person.bazi_report_resolved_at
                })
                
            except Exception as e:
                logger.error(f"Error generating Number analysis: {str(e)}")
                # Update person's analysis status to error
                person.number_analysis_status = 'error'
                person.save(update_fields=['number_analysis_status'])
                return Response({
                    'error': 'Failed to generate analysis',
                    'detail': str(e)
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                
    except BaziPerson.DoesNotExist:
        return Response({
            'error': 'Person not found'
        }, status=status.HTTP_404_NOT_FOUND)
    except Exception as e:
        logger.error(f"Error in number_analysis view: {str(e)}")
        return Response({
            'error': 'Internal server error',
            'detail': str(e)
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

@extend_schema(  # Document GET
    operation_id="liuyao_analysis_get",
    methods=["GET"],
    summary="Get AI analysis for a LiuYao divination",
    description="""
Retrieve the AI analysis for a LiuYao divination if it exists.
If no analysis exists, returns a status of "not_found".
""",
    responses={
        200: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Analysis retrieved successfully or status indicated",
            examples=[
                OpenApiExample(
                    name='Completed Analysis',
                    value={
                        "status": "completed",
                        "analysis": {
                            "liuyao_analysis": "Full response text from the LLM",
                            "sections": {
                                "hexagram_interpretation": "本卦含义及与问题的关联分析",
                                "changing_lines": "所有动爻的解释及意义",
                                "relationship_analysis": "本卦与变卦之间的关系分析",
                                "timing_analysis": "时间指示及季节影响分析",
                                "practical_advice": "根据解读给求卦者的实用建议",
                                "special_considerations": "本次预测的特殊考虑因素或不寻常特点"
                            },
                            "prompt": "Full text of the prompt sent to the LLM",
                            "provider": "groq",
                            "model": "llama3.3-70b",
                            "think": "AI's thinking process"
                        },
                        "timestamp": "2023-05-30T12:34:56.789Z"
                    },
                    response_only=True
                ),
                OpenApiExample(
                    name='No Analysis Available',
                    value={
                        "status": "not_found",
                        "message": "No analysis available"
                    },
                    response_only=True
                )
            ]
        )
    }
)
@extend_schema(  # Document POST
    operation_id="liuyao_analysis_post",
    methods=["POST"],
    summary="Generate AI analysis for a LiuYao divination",
    description="""
Generate a new analysis (or regenerate an existing one) for a LiuYao divination.

## Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `regenerate` | boolean | No | Set to `true` to force regeneration of an existing analysis. Default is `false`. |
| `provider` | string | No | LLM provider to use. Possible values: `groq`, `openai`. If not specified, uses the default from settings. |
| `model` | string | No | Specific model to use. If not specified, uses the default for the chosen provider. |
| `language` | string | No | Language for the analysis. Possible values: `zh-hans` (Chinese Simplified), `en` (English). Default is `zh-hans`. |

## Permission Requirements

Regeneration requires special permissions:
- Only users with the `can_regenerate_ai` privilege can regenerate analyses
- Without this privilege, users can only generate an analysis once per divination
""",
    parameters=[],
    request=inline_serializer(
        name='LiuYaoAnalysisRequest',
        fields={
            'regenerate': serializers.BooleanField(
                required=False, 
                default=False, 
                help_text="Set to true to force regeneration of an existing analysis. Requires 'can_regenerate_ai' privilege. Default is false."
            ),
            'provider': serializers.CharField(
                required=False, 
                allow_null=True, 
                help_text="LLM provider to use. Possible values: 'groq', 'openai'. If not specified, uses the default from settings."
            ),
            'model': serializers.CharField(
                required=False, 
                allow_null=True, 
                help_text="Specific model identifier. If not specified, uses the default model for the selected provider."
            ),
            'language': serializers.CharField(
                required=False,
                default='zh-hans',
                help_text="Language for the analysis. Possible values: 'zh-hans' (Chinese Simplified), 'en' (English). Default is 'zh-hans'."
            ),
        }
    ),
    examples=[
        OpenApiExample(
            name='Generate Analysis Request',
            value={
                "provider": "groq",
                "model": "llama3.3-70b",
                "regenerate": False
            },
            request_only=True
        ),
        OpenApiExample(
            name='Regenerate Analysis Request',
            value={
                "provider": "openai",
                "model": "gpt-4o",
                "regenerate": True
            },
            request_only=True
        ),
    ],
    responses={
        200: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Analysis successfully generated or retrieved",
            examples=[
                OpenApiExample(
                    name='Analysis Created',
                    value={
                        "status": "completed",
                        "analysis": {
                            "liuyao_analysis": "Full response text from the LLM",
                            "sections": {
                                "hexagram_interpretation": "本卦含义及与问题的关联分析",
                                "changing_lines": "所有动爻的解释及意义",
                                "relationship_analysis": "本卦与变卦之间的关系分析",
                                "timing_analysis": "时间指示及季节影响分析",
                                "practical_advice": "根据解读给求卦者的实用建议",
                                "special_considerations": "本次预测的特殊考虑因素或不寻常特点"
                            },
                            "prompt": "Full text of the prompt sent to the LLM",
                            "provider": "groq",
                            "model": "llama3.3-70b",
                            "think": "AI's thinking process"
                        },
                        "timestamp": "2023-05-30T12:34:56.789Z"
                    },
                    response_only=True
                )
            ]
        ),
        403: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Permission denied for regeneration",
            examples=[
                OpenApiExample(
                    'Permission Error',
                    value={
                        "error": "You do not have permission to regenerate analysis"
                    },
                    response_only=True
                )
            ]
        ),
        404: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="LiuYao divination not found",
            examples=[
                OpenApiExample(
                    'Not Found Error',
                    value={
                        "error": "LiuYao divination not found"
                    },
                    response_only=True
                )
            ]
        ),
        500: OpenApiResponse(
            response=OpenApiTypes.OBJECT,
            description="Error generating analysis",
            examples=[
                OpenApiExample(
                    'Analysis Error',
                    value={
                        "error": "Failed to generate analysis",
                        "detail": "Error message with details about what went wrong"
                    },
                    response_only=True
                )
            ]
        )
    }
)
@api_view(['GET', 'POST'])
@permission_classes([AllowAny])  # Base permission class
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
def liuyao_analysis(request, pk):
    """Get or generate AI analysis for a LiuYao divination."""
    try:
        # Get the LiuYao instance
        liuyao_obj = liuyao.objects.get(pk=pk)
    except liuyao.DoesNotExist:
        return Response({"error": "LiuYao divination not found"}, status=status.HTTP_404_NOT_FOUND)
    
    # Handle GET request (return existing analysis)
    if request.method == 'GET':
        # Check if analysis exists
        if liuyao_obj.analysis_status == 'completed' and liuyao_obj.ai_analysis:
            return Response({
                "status": "completed",
                "analysis": liuyao_obj.ai_analysis,
                "timestamp": liuyao_obj.analysis_timestamp,
                "analysis_status": liuyao_obj.analysis_status,
                "analysis_reported": liuyao_obj.analysis_reported,
                "report_status": liuyao_obj.report_status,
                "report_category": liuyao_obj.report_category,
                "report_message": liuyao_obj.report_message,
                "report_admin_notes": liuyao_obj.report_admin_notes,
                "report_timestamp": liuyao_obj.report_timestamp,
                "report_resolved_at": liuyao_obj.report_resolved_at
            })
        elif liuyao_obj.analysis_status == 'pending':
            return Response({
                "status": "pending",
                "message": "Analysis is being generated"
            })
        elif liuyao_obj.analysis_status == 'error':
            return Response({
                "status": "error",
                "message": "Error generating analysis"
            })
        else:
            return Response({
                "status": "not_found",
                "message": "No analysis available"
            })
    
    # Handle POST request (generate new analysis)
    elif request.method == 'POST':
        # Check permissions for regeneration
        regenerate_value = request.data.get('regenerate', 'false')
        if isinstance(regenerate_value, bool):
            regenerate = regenerate_value
        else:
            regenerate = regenerate_value.lower() == 'true'
        
        if regenerate and liuyao_obj.analysis_status == 'completed' and liuyao_obj.ai_analysis:
            # Check if user has permission to regenerate
            if not hasattr(request.user, 'profile') or not request.user.profile.can_regenerate_ai:
                return Response({
                    "error": "You do not have permission to regenerate analysis"
                }, status=status.HTTP_403_FORBIDDEN)
        
        # Get provider, model, and language parameters
        provider = request.data.get('provider', None)
        model = request.data.get('model', None)
        language = request.data.get('language', 'zh-hans')  # Default to Chinese
        
        # Validate language
        from ai.utils.i18n import validate_language
        language = validate_language(language)
        
        # Get defaults from configuration for any missing values
        try:
            config = get_ai_config('liuyao')
            if not provider:
                provider = config.get('provider')
            if not model:
                model = config.get('model')
        except Exception as e:
            logger.warning(f"Could not get default AI config for liuyao: {e}")
            # Fall back to None values, which will use system defaults
        
        try:
            # Import here to avoid circular imports
            from ai.utils.liuyao_analysis import analyze_liuyao
            
            # Set status to pending
            liuyao_obj.analysis_status = 'pending'
            liuyao_obj.save(update_fields=['analysis_status'])
            
            # Generate the analysis
            result = analyze_liuyao(liuyao_obj, model_key=model, provider=provider, language=language)
            
            # Return the result
            if result:
                return Response({
                    "status": "completed",
                    "analysis": result,
                    "timestamp": liuyao_obj.analysis_timestamp,
                    "analysis_status": liuyao_obj.analysis_status,
                    "analysis_reported": liuyao_obj.analysis_reported
                })
            else:
                return Response({
                    "status": "error",
                    "message": "Error generating analysis"
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                
        except Exception as e:
            # Log the error
            logger.error(f"Error generating LiuYao analysis: {str(e)}", exc_info=True)
            
            # Update status to error
            liuyao_obj.analysis_status = 'error'
            liuyao_obj.save(update_fields=['analysis_status'])
            
            # Return error response
            return Response({
                "error": "Failed to generate analysis",
                "detail": str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

@extend_schema(
    description="Create a temporary user and return JWT tokens",
    request=TempUserCreateSerializer,
    responses={
        201: inline_serializer(
            name='TempUserCreateResponse',
            fields={
                'user_id': serializers.IntegerField(help_text='Temporary user ID'),
                'access': serializers.CharField(help_text='JWT access token (permanent - never expires)'),
                'refresh': serializers.CharField(help_text='JWT refresh token (permanent - never expires)'),
                'is_temporary': serializers.BooleanField(help_text='Indicates this is a temporary user'),
                'message': serializers.CharField(help_text='Success message'),
                'expires_in': serializers.IntegerField(help_text='Access token expiry time in seconds (null for permanent tokens)', allow_null=True),
                'is_permanent_session': serializers.BooleanField(help_text='Indicates if the session is permanent (never expires)')
            }
        ),
        400: OpenApiResponse(
            description="Validation errors",
            examples=[
                OpenApiExample(
                    name='Validation Error',
                    value={
                        "father_dob": ["Father's birth date is required for elder twin."],
                        "gender": ["Gender must be 'M' (male), 'F' (female), or 'N' (not specified)."]
                    },
                    response_only=True
                )
            ]
        )
    },
    examples=[
        OpenApiExample(
            name='Create Temp User with Full Data',
            value={
                "name": "测试用户",
                "gender": "M",
                "birth_date": "1990-01-01",
                "birth_time": "12:00",
                "twin_type": 0
            },
            request_only=True
        ),
        OpenApiExample(
            name='Create Temp User with Minimal Data',
            value={
                "name": "Simple User"
            },
            request_only=True
        ),
        OpenApiExample(
            name='Create Empty Temp User',
            value={},
            request_only=True
        ),
        OpenApiExample(
            name='Success Response',
            value={
                "user_id": 123,
                "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                "is_temporary": True,
                "message": "Temporary user created successfully",
                "expires_in": None,
                "is_permanent_session": True
            },
            response_only=True
        )
    ]
)
class TempUserCreateView(generics.GenericAPIView):
    """
    Create a temporary user with optional profile data and return JWT tokens.
    
    This endpoint allows public access (no authentication required) and creates
    a temporary user account that can be used to store divination records.
    All input fields are optional.
    """
    permission_classes = [AllowAny]
    serializer_class = TempUserCreateSerializer
    
    def post(self, request):
        # Check for forbidden fields before serializer validation
        if 'phone' in request.data:
            return Response({
                'phone': ['Phone field is not allowed. Phone numbers are system-generated for temporary users.']
            }, status=status.HTTP_400_BAD_REQUEST)
        
        if 'email' in request.data:
            return Response({
                'email': ['Email field is not allowed. Email addresses are system-generated for temporary users.']
            }, status=status.HTTP_400_BAD_REQUEST)
        
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        try:
            # Prepare user data for temporary user creation
            user_data = {}
            validated_data = serializer.validated_data
            
            # Convert data for create_temporary_user function
            for field in ['name', 'gender', 'twin_type', 'birth_city', 'timezone']:
                if field in validated_data:
                    user_data[field] = validated_data[field]
            
            # Convert date fields to strings
            if 'birth_date' in validated_data:
                user_data['birth_date'] = validated_data['birth_date'].strftime('%Y-%m-%d')
            
            if 'birth_time' in validated_data:
                user_data['birth_time'] = validated_data['birth_time'].strftime('%H:%M')
            
            if 'father_dob' in validated_data:
                user_data['father_dob'] = validated_data['father_dob'].strftime('%Y-%m-%d')
            
            if 'mother_dob' in validated_data:
                user_data['mother_dob'] = validated_data['mother_dob'].strftime('%Y-%m-%d')
            
            # Create temporary user
            from accounts.utils import create_temporary_user
            temp_user = create_temporary_user(user_data)
            
            # Generate permanent JWT tokens for temporary user
            from main.tokens import create_tokens_for_user
            token_data = create_tokens_for_user(temp_user)
            
            return Response({
                'user_id': temp_user.id,
                'access': token_data['access'],
                'refresh': token_data['refresh'],
                'is_temporary': True,
                'message': 'Temporary user created successfully',
                'expires_in': token_data['expires_in'],  # None for permanent tokens
                'is_permanent_session': token_data['is_permanent']
            }, status=status.HTTP_201_CREATED)
            
        except Exception as e:
            logger.error(f"Error creating temporary user: {e}")
            return Response(
                {'error': f'Failed to create temporary user: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


# LiuyaoDetailView and LiuyaoDeleteView have been removed.
# These functionalities are now provided by the LiuyaoViewSet:
# - GET /api/liuyao/{id}/ -> LiuyaoViewSet.retrieve()
# - DELETE /api/liuyao/{id}/ -> LiuyaoViewSet.destroy()
# The ViewSet provides better consistency and follows REST API patterns.


@method_decorator(csrf_exempt, name='dispatch')
@extend_schema(
    description="Convert JWT token to session authentication",
    request=inline_serializer(
        name='JWTToSessionRequest',
        fields={
            'access_token': serializers.CharField(help_text='JWT access token to convert to session')
        }
    ),
    responses={
        200: inline_serializer(
            name='JWTToSessionResponse',
            fields={
                'success': serializers.BooleanField(help_text='Indicates successful session creation'),
                'message': serializers.CharField(help_text='Success message'),
                'user_id': serializers.IntegerField(help_text='User ID for the session'),
                'is_temporary': serializers.BooleanField(help_text='Indicates if this is a temporary user')
            }
        ),
        400: OpenApiResponse(
            description="Invalid or missing access token",
            examples=[
                OpenApiExample(
                    name='Missing Token',
                    value={
                        "error": "Access token is required"
                    },
                    response_only=True
                )
            ]
        ),
        401: OpenApiResponse(
            description="Invalid or expired access token",
            examples=[
                OpenApiExample(
                    name='Invalid Token',
                    value={
                        "error": "Invalid or expired access token"
                    },
                    response_only=True
                )
            ]
        )
    },
    examples=[
        OpenApiExample(
            name='Convert JWT to Session',
            value={
                "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
            },
            request_only=True
        ),
        OpenApiExample(
            name='Success Response',
            value={
                "success": True,
                "message": "Session authentication established successfully",
                "user_id": 123,
                "is_temporary": True
            },
            response_only=True
        )
    ]
)
class JWTToSessionView(generics.GenericAPIView):
    """
    Convert a JWT access token to session authentication.
    
    This endpoint allows converting JWT authentication to Django session 
    authentication for web applications that need both token and session support.
    """
    permission_classes = [AllowAny]
    
    def post(self, request):
        access_token = request.data.get('access_token')
        
        if not access_token:
            return Response({
                'error': 'Access token is required'
            }, status=status.HTTP_400_BAD_REQUEST)
        
        try:
            # Validate the JWT token
            from rest_framework_simplejwt.tokens import AccessToken
            from django.contrib.auth import get_user_model, login
            
            token = AccessToken(access_token)
            user_id = token.payload.get('user_id')
            
            if not user_id:
                return Response({
                    'error': 'Invalid token payload'
                }, status=status.HTTP_401_UNAUTHORIZED)
            
            # Get the user
            User = get_user_model()
            try:
                user = User.objects.get(id=user_id)
            except User.DoesNotExist:
                return Response({
                    'error': 'User not found'
                }, status=status.HTTP_401_UNAUTHORIZED)
            
            # Log the user in to create a session
            login(request, user)
            
            return Response({
                'success': True,
                'message': 'Session authentication established successfully',
                'user_id': user.id,
                'is_temporary': user.is_temporary_user
            }, status=status.HTTP_200_OK)
            
        except Exception as e:
            logger.error(f"Error converting JWT to session: {e}")
            return Response({
                'error': 'Invalid or expired access token'
            }, status=status.HTTP_401_UNAUTHORIZED)


# AI Analysis Report Views

@extend_schema(
    operation_id="report_bazi_analysis",
    summary="Report BaZi Analysis",
    description="Submit a report for inappropriate or problematic BaZi analysis content",
    request=ReportSubmissionSerializer,
    responses={
        201: OpenApiResponse(description="Report submitted successfully"),
        400: OpenApiResponse(description="Invalid data or analysis cannot be reported"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Analysis not found"),
        403: OpenApiResponse(description="Not authorized to report this analysis"),
    }
)
@method_decorator(csrf_exempt, name='dispatch')
class BaziReportView(generics.CreateAPIView):
    """Submit a report for BaZi analysis"""
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]
    permission_classes = [IsAuthenticated]
    serializer_class = ReportSubmissionSerializer
    
    def perform_create(self, serializer):
        person_id = self.kwargs['pk']
        try:
            # Get the person record - only allow user to report their own analyses
            person = BaziPerson.objects.get(id=person_id, created_by=self.request.user)
        except BaziPerson.DoesNotExist:
            raise ValidationError("BaZi analysis not found or you don't have permission to report it")
        
        # Validate can report
        if not person.can_report_bazi_analysis():
            raise ValidationError("This BaZi analysis cannot be reported (not completed or already reported)")
            
        # Save report
        category = serializer.validated_data['category']
        message = serializer.validated_data.get('message', '')
        
        success = person.report_bazi_analysis(
            category=category,
            user_message=message,
            reported_by=self.request.user
        )
        
        if not success:
            raise ValidationError("Failed to submit report")
        
        # Send notification email to admins
        from .utils import send_admin_report_notification
        email_sent = send_admin_report_notification(person, 'bazi', category, message)
        if not email_sent:
            # Log the failure but don't fail the request
            # Suppress warnings during testing to keep test output clean
            from django.conf import settings
            should_be_silent = (
                getattr(settings, 'TESTING', False) or
                'test' in getattr(settings, 'EMAIL_BACKEND', '') or
                'locmem' in getattr(settings, 'EMAIL_BACKEND', '') or
                hasattr(settings, 'TEST_RUNNER')
            )
            
            if not should_be_silent:
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f"Failed to send admin notification for BaZi report (Person ID: {person.id})")
    
    def create(self, request, *args, **kwargs):
        try:
            response = super().create(request, *args, **kwargs)
            return Response({
                'success': True,
                'message': 'BaZi analysis report submitted successfully'
            }, status=status.HTTP_201_CREATED)
        except ValidationError as e:
            return Response({
                'error': str(e)
            }, status=status.HTTP_400_BAD_REQUEST)


@extend_schema(
    operation_id="report_number_analysis",
    summary="Report Number Analysis",
    description="Submit a report for inappropriate or problematic Number analysis content",
    request=ReportSubmissionSerializer,
    responses={
        201: OpenApiResponse(description="Report submitted successfully"),
        400: OpenApiResponse(description="Invalid data or analysis cannot be reported"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Analysis not found"),
        403: OpenApiResponse(description="Not authorized to report this analysis"),
    }
)
@method_decorator(csrf_exempt, name='dispatch')
class NumberReportView(generics.CreateAPIView):
    """Submit a report for Number analysis"""
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]
    permission_classes = [IsAuthenticated]
    serializer_class = ReportSubmissionSerializer
    
    def perform_create(self, serializer):
        person_id = self.kwargs['pk']
        try:
            # Get the person record - only allow user to report their own analyses
            person = BaziPerson.objects.get(id=person_id, created_by=self.request.user)
        except BaziPerson.DoesNotExist:
            raise ValidationError("Number analysis not found or you don't have permission to report it")
        
        # Validate can report
        if not person.can_report_number_analysis():
            raise ValidationError("This Number analysis cannot be reported (not completed or already reported)")
            
        # Save report
        category = serializer.validated_data['category']
        message = serializer.validated_data.get('message', '')
        
        success = person.report_number_analysis(
            category=category,
            user_message=message,
            reported_by=self.request.user
        )
        
        if not success:
            raise ValidationError("Failed to submit report")
        
        # Send notification email to admins
        from .utils import send_admin_report_notification
        email_sent = send_admin_report_notification(person, 'number', category, message)
        if not email_sent:
            # Log the failure but don't fail the request
            # Suppress warnings during testing to keep test output clean
            from django.conf import settings
            should_be_silent = (
                getattr(settings, 'TESTING', False) or
                'test' in getattr(settings, 'EMAIL_BACKEND', '') or
                'locmem' in getattr(settings, 'EMAIL_BACKEND', '') or
                hasattr(settings, 'TEST_RUNNER')
            )
            
            if not should_be_silent:
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f"Failed to send admin notification for Number report (Person ID: {person.id})")
    
    def create(self, request, *args, **kwargs):
        try:
            response = super().create(request, *args, **kwargs)
            return Response({
                'success': True,
                'message': 'Number analysis report submitted successfully'
            }, status=status.HTTP_201_CREATED)
        except ValidationError as e:
            return Response({
                'error': str(e)
            }, status=status.HTTP_400_BAD_REQUEST)


@extend_schema(
    operation_id="report_liuyao_analysis",
    summary="Report LiuYao Analysis",
    description="Submit a report for inappropriate or problematic LiuYao analysis content",
    request=ReportSubmissionSerializer,
    responses={
        201: OpenApiResponse(description="Report submitted successfully"),
        400: OpenApiResponse(description="Invalid data or analysis cannot be reported"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Analysis not found"),
        403: OpenApiResponse(description="Not authorized to report this analysis"),
    }
)
@method_decorator(csrf_exempt, name='dispatch')
class LiuYaoReportView(generics.CreateAPIView):
    """Submit a report for LiuYao analysis"""
    authentication_classes = [ActivityTrackingJWTAuthentication, SessionAuthentication]
    permission_classes = [IsAuthenticated]
    serializer_class = ReportSubmissionSerializer
    
    def perform_create(self, serializer):
        liuyao_id = self.kwargs['pk']
        try:
            # Get the liuyao record - only allow user to report their own analyses
            liuyao_obj = liuyao.objects.get(id=liuyao_id, user=self.request.user)
        except liuyao.DoesNotExist:
            raise ValidationError("LiuYao analysis not found or you don't have permission to report it")
        
        # Validate can report
        if not liuyao_obj.can_report_analysis():
            raise ValidationError("This LiuYao analysis cannot be reported (not completed or already reported)")
            
        # Save report
        category = serializer.validated_data['category']
        message = serializer.validated_data.get('message', '')
        
        success = liuyao_obj.report_analysis(
            category=category,
            user_message=message,
            reported_by=self.request.user
        )
        
        if not success:
            raise ValidationError("Failed to submit report")
        
        # Send notification email to admins
        from .utils import send_admin_report_notification
        email_sent = send_admin_report_notification(liuyao_obj, 'liuyao', category, message)
        if not email_sent:
            # Log the failure but don't fail the request
            # Suppress warnings during testing to keep test output clean
            from django.conf import settings
            should_be_silent = (
                getattr(settings, 'TESTING', False) or
                'test' in getattr(settings, 'EMAIL_BACKEND', '') or
                'locmem' in getattr(settings, 'EMAIL_BACKEND', '') or
                hasattr(settings, 'TEST_RUNNER')
            )
            
            if not should_be_silent:
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f"Failed to send admin notification for LiuYao report (LiuYao ID: {liuyao_obj.id})")
    
    def create(self, request, *args, **kwargs):
        try:
            response = super().create(request, *args, **kwargs)
            return Response({
                'success': True,
                'message': 'LiuYao analysis report submitted successfully'
            }, status=status.HTTP_201_CREATED)
        except ValidationError as e:
            return Response({
                'error': str(e)
            }, status=status.HTTP_400_BAD_REQUEST)


# Conversation API Views
from ai.models import Conversation, Message, ConversationSubject
from ai.utils.conversation import create_conversation, create_bazi_conversation, create_liuyao_conversation, send_conversation_message
from django.conf import settings


@extend_schema(
    summary="List conversations for a BaZi record",
    description="Get all conversations for a specific BaZi record (Person) for the authenticated user",
    responses={
        200: ConversationListSerializer(many=True),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Person not found"),
    }
)
@api_view(['GET'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def list_conversations(request, person_id):
    """List all conversations for a BaZi record"""
    try:
        person = BaziPerson.objects.get(id=person_id, created_by=request.user)
    except BaziPerson.DoesNotExist:
        return Response(
            {"error": "Person not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this person
    subject = ConversationSubject.get_or_create_subject('bazi', person_id)
    conversations = Conversation.objects.filter(
        subject=subject,
        user=request.user
    ).prefetch_related('messages').order_by('-updated_at')
    
    serializer = ConversationListSerializer(conversations, many=True)
    return Response({
        "count": len(serializer.data),
        "results": serializer.data
    })


@extend_schema(
    summary="Get conversation detail",
    description="Get full conversation with all messages for a specific BaZi record",
    responses={
        200: ConversationSerializer,
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Conversation not found"),
    }
)
@api_view(['GET'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def get_conversation(request, person_id, conversation_id):
    """Get conversation detail with all messages"""
    try:
        person = BaziPerson.objects.get(id=person_id, created_by=request.user)
    except BaziPerson.DoesNotExist:
        return Response(
            {"error": "Person not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this person
    subject = ConversationSubject.get_or_create_subject('bazi', person_id)
    try:
        conversation = Conversation.objects.get(
            id=conversation_id,
            subject=subject,
            user=request.user
        )
    except Conversation.DoesNotExist:
        return Response(
            {"error": "Conversation not found"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    serializer = ConversationSerializer(conversation)
    return Response(serializer.data)


@extend_schema(
    summary="Create conversation",
    description="Create a new conversation thread for a BaZi record",
    request=CreateConversationSerializer,
    responses={
        201: ConversationSerializer,
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Person not found"),
    }
)
@api_view(['POST'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def create_conversation_view(request, person_id):
    """Create a new conversation for a BaZi record"""
    try:
        person = BaziPerson.objects.get(id=person_id, created_by=request.user)
    except BaziPerson.DoesNotExist:
        return Response(
            {"error": "Person not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Check if user already has a conversation for this person (optional - can allow multiple)
    # For now, we'll allow multiple conversations
    
    conversation = create_bazi_conversation(
        user=request.user,
        person=person,
        title=None  # No title needed
    )
    
    serializer = ConversationSerializer(conversation)
    return Response(serializer.data, status=status.HTTP_201_CREATED)


@extend_schema(
    summary="Send message",
    description="Send a user message and get AI response in a conversation",
    request=SendMessageSerializer,
    responses={
        200: inline_serializer(
            name='SendMessageResponse',
            fields={
                'conversation_id': serializers.IntegerField(),
                'user_message': MessageSerializer(),
                'assistant_message': MessageSerializer(),
            }
        ),
        400: OpenApiResponse(description="Validation error or message limit reached"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Conversation not found"),
        403: OpenApiResponse(description="Permission denied for provider/model override"),
    }
)
@api_view(['POST'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def send_message(request, person_id, conversation_id):
    """Send a message in a conversation"""
    try:
        person = BaziPerson.objects.get(id=person_id, created_by=request.user)
    except BaziPerson.DoesNotExist:
        return Response(
            {"error": "Person not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this person
    subject = ConversationSubject.get_or_create_subject('bazi', person_id)
    try:
        conversation = Conversation.objects.get(
            id=conversation_id,
            subject=subject,
            user=request.user
        )
    except Conversation.DoesNotExist:
        return Response(
            {"error": "Conversation not found"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Validate request data
    serializer = SendMessageSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    # Check message limit (admin configurable)
    from ai.models import ConversationConfig
    max_messages = ConversationConfig.get_max_messages()
    current_message_count = conversation.get_message_count()
    if current_message_count >= max_messages:
        return Response(
            {"error": f"Conversation has reached the maximum message limit ({max_messages})"},
            status=status.HTTP_400_BAD_REQUEST
        )
    
    # Check provider/model override permission
    provider = serializer.validated_data.get('provider')
    model = serializer.validated_data.get('model')
    
    if provider or model:
        # Check if user has can_regenerate_ai privilege
        if not hasattr(request.user, 'profile') or not request.user.profile.can_regenerate_ai:
            return Response(
                {"error": "You do not have permission to override AI provider/model"},
                status=status.HTTP_403_FORBIDDEN
            )
    
    try:
        # Send message and get AI response
        assistant_msg = send_conversation_message(
            conversation=conversation,
            user_message=serializer.validated_data['message'],
            provider=provider,
            model=model,
            language=serializer.validated_data.get('language', 'zh-hans')
        )
        
        # Get the user message (last user message)
        user_msg = conversation.messages.filter(role='user').order_by('-created_at').first()
        
        return Response({
            "conversation_id": conversation.id,
            "user_message": MessageSerializer(user_msg).data,
            "assistant_message": MessageSerializer(assistant_msg).data,
        })
    except Exception as e:
        logger.error(f"Error sending conversation message: {str(e)}", exc_info=True)
        
        # Get the failed user message (should be the last one with failed status)
        failed_user_msg = conversation.messages.filter(role='user', status='failed').order_by('-created_at').first()
        
        if failed_user_msg:
            return Response({
                "conversation_id": conversation.id,
                "user_message": MessageSerializer(failed_user_msg).data,
                "assistant_message": None,
                "error": "Failed to send message",
                "detail": str(e),
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        else:
            return Response(
                {"error": "Failed to send message", "detail": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


@extend_schema(
    summary="Retry failed message",
    description="Retry sending a failed message. Allows editing the message content. Resends the message along with all previous messages to maintain context.",
    request=inline_serializer(
        name='RetryMessageRequest',
        fields={
            'message_id': serializers.IntegerField(required=False, help_text='Message ID to retry (optional, defaults to last failed message)'),
            'message': serializers.CharField(required=False, help_text='New message content (optional, if not provided uses existing message content)'),
        }
    ),
    responses={
        200: inline_serializer(
            name='RetryMessageResponse',
            fields={
                'conversation_id': serializers.IntegerField(),
                'user_message': MessageSerializer(),
                'assistant_message': MessageSerializer(),
            }
        ),
        400: OpenApiResponse(description="Validation error"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Conversation or message not found"),
    }
)
@api_view(['POST'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def retry_message(request, person_id, conversation_id):
    """Retry sending a failed message"""
    try:
        person = BaziPerson.objects.get(id=person_id, created_by=request.user)
    except BaziPerson.DoesNotExist:
        return Response(
            {"error": "Person not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this person
    subject = ConversationSubject.get_or_create_subject('bazi', person_id)
    try:
        conversation = Conversation.objects.get(
            id=conversation_id,
            subject=subject,
            user=request.user
        )
    except Conversation.DoesNotExist:
        return Response(
            {"error": "Conversation not found"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get the message to retry (by ID if provided, otherwise last failed message, otherwise last user message)
    message_id = request.data.get('message_id')
    if message_id:
        try:
            message_to_retry = conversation.messages.get(id=message_id, role='user')
        except Message.DoesNotExist:
            return Response(
                {"error": "Message not found"},
                status=status.HTTP_404_NOT_FOUND
            )
    else:
        # Try to get last failed message, otherwise get last user message
        message_to_retry = conversation.messages.filter(role='user', status='failed').order_by('-created_at').first()
        if not message_to_retry:
            message_to_retry = conversation.messages.filter(role='user').order_by('-created_at').first()
    
    if not message_to_retry:
        return Response(
            {"error": "No user message found to retry"},
            status=status.HTTP_400_BAD_REQUEST
        )
    
    # Get new message content if provided, otherwise use existing
    new_message_content = request.data.get('message', message_to_retry.content)
    
    # Get language from last assistant message or default
    last_assistant_msg = conversation.messages.filter(role='assistant').order_by('-created_at').first()
    language = last_assistant_msg.meta.get('language', 'zh-hans') if last_assistant_msg and last_assistant_msg.meta else 'zh-hans'
    
    # Get provider/model from request (user selection), fallback to last assistant message, then defaults
    provider = request.data.get('provider')
    model = request.data.get('model')
    if not provider and last_assistant_msg:
        provider = last_assistant_msg.provider
    if not model and last_assistant_msg:
        model = last_assistant_msg.model
    
    try:
        # Resend the message (this will resend everything: message + previous responses)
        # Pass existing_user_message to reuse the failed message instead of creating a new one
        assistant_msg = send_conversation_message(
            conversation=conversation,
            user_message=new_message_content,
            provider=provider,
            model=model,
            language=language,
            existing_user_message=message_to_retry
        )
        
        # Refresh message_to_retry to get updated status
        message_to_retry.refresh_from_db()
        
        return Response({
            "conversation_id": conversation.id,
            "user_message": MessageSerializer(message_to_retry).data,
            "assistant_message": MessageSerializer(assistant_msg).data,
        })
    except Exception as e:
        logger.error(f"Error retrying conversation message: {str(e)}", exc_info=True)
        
        # Refresh message_to_retry to get updated status (should be failed now)
        message_to_retry.refresh_from_db()
        
        # Return the failed message (should be message_to_retry)
        failed_user_msg = message_to_retry
        
        if failed_user_msg:
            return Response({
                "conversation_id": conversation.id,
                "user_message": MessageSerializer(failed_user_msg).data,
                "assistant_message": None,
                "error": "Failed to retry message",
                "detail": str(e),
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        else:
            return Response(
                {"error": "Failed to retry message", "detail": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


# LiuYao Conversation API Views
@extend_schema(
    summary="List conversations for a LiuYao record",
    description="Get all conversations for a specific LiuYao record for the authenticated user",
    responses={
        200: ConversationListSerializer(many=True),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="LiuYao record not found"),
    }
)
@api_view(['GET'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def list_liuyao_conversations(request, liuyao_id):
    """List all conversations for a LiuYao record"""
    try:
        liuyao_obj = liuyao.objects.get(id=liuyao_id, user=request.user)
    except liuyao.DoesNotExist:
        return Response(
            {"error": "LiuYao record not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this liuyao
    subject = ConversationSubject.get_or_create_subject('liuyao', liuyao_id)
    conversations = Conversation.objects.filter(
        subject=subject,
        user=request.user
    ).prefetch_related('messages').order_by('-updated_at')
    
    serializer = ConversationListSerializer(conversations, many=True)
    return Response({
        "count": len(serializer.data),
        "results": serializer.data
    })


@extend_schema(
    summary="Get LiuYao conversation detail",
    description="Get full conversation with all messages for a specific LiuYao record",
    responses={
        200: ConversationSerializer,
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Conversation not found"),
    }
)
@api_view(['GET'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def get_liuyao_conversation(request, liuyao_id, conversation_id):
    """Get conversation detail with all messages"""
    try:
        liuyao_obj = liuyao.objects.get(id=liuyao_id, user=request.user)
    except liuyao.DoesNotExist:
        return Response(
            {"error": "LiuYao record not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this liuyao
    subject = ConversationSubject.get_or_create_subject('liuyao', liuyao_id)
    try:
        conversation = Conversation.objects.get(
            id=conversation_id,
            subject=subject,
            user=request.user
        )
    except Conversation.DoesNotExist:
        return Response(
            {"error": "Conversation not found"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    serializer = ConversationSerializer(conversation)
    return Response(serializer.data)


@extend_schema(
    summary="Create LiuYao conversation",
    description="Create a new conversation thread for a LiuYao record",
    request=CreateConversationSerializer,
    responses={
        201: ConversationSerializer,
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="LiuYao record not found"),
    }
)
@api_view(['POST'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def create_liuyao_conversation_view(request, liuyao_id):
    """Create a new conversation for a LiuYao record"""
    try:
        liuyao_obj = liuyao.objects.get(id=liuyao_id, user=request.user)
    except liuyao.DoesNotExist:
        return Response(
            {"error": "LiuYao record not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    conversation = create_liuyao_conversation(
        user=request.user,
        liuyao=liuyao_obj,
        title=None  # No title needed
    )
    
    serializer = ConversationSerializer(conversation)
    return Response(serializer.data, status=status.HTTP_201_CREATED)


@extend_schema(
    summary="Send LiuYao conversation message",
    description="Send a user message and get AI response in a LiuYao conversation",
    request=SendMessageSerializer,
    responses={
        200: inline_serializer(
            name='SendMessageResponse',
            fields={
                'conversation_id': serializers.IntegerField(),
                'user_message': MessageSerializer(),
                'assistant_message': MessageSerializer(),
            }
        ),
        400: OpenApiResponse(description="Validation error or message limit reached"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Conversation not found"),
        403: OpenApiResponse(description="Permission denied for provider/model override"),
    }
)
@api_view(['POST'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def send_liuyao_message(request, liuyao_id, conversation_id):
    """Send a message in a LiuYao conversation"""
    try:
        liuyao_obj = liuyao.objects.get(id=liuyao_id, user=request.user)
    except liuyao.DoesNotExist:
        return Response(
            {"error": "LiuYao record not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this liuyao
    subject = ConversationSubject.get_or_create_subject('liuyao', liuyao_id)
    try:
        conversation = Conversation.objects.get(
            id=conversation_id,
            subject=subject,
            user=request.user
        )
    except Conversation.DoesNotExist:
        return Response(
            {"error": "Conversation not found"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Validate request data
    serializer = SendMessageSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    # Check message limit (admin configurable)
    from ai.models import ConversationConfig
    max_messages = ConversationConfig.get_max_messages()
    current_message_count = conversation.get_message_count()
    if current_message_count >= max_messages:
        return Response(
            {"error": f"Conversation has reached the maximum message limit ({max_messages})"},
            status=status.HTTP_400_BAD_REQUEST
        )
    
    # Check provider/model override permission
    provider = serializer.validated_data.get('provider')
    model = serializer.validated_data.get('model')
    
    if provider or model:
        # Check if user has can_regenerate_ai privilege
        if not hasattr(request.user, 'profile') or not request.user.profile.can_regenerate_ai:
            return Response(
                {"error": "You do not have permission to override AI provider/model"},
                status=status.HTTP_403_FORBIDDEN
            )
    
    try:
        # Send message and get AI response
        assistant_msg = send_conversation_message(
            conversation=conversation,
            user_message=serializer.validated_data['message'],
            provider=provider,
            model=model,
            language=serializer.validated_data.get('language', 'zh-hans')
        )
        
        # Get the user message (last user message)
        user_msg = conversation.messages.filter(role='user').order_by('-created_at').first()
        
        return Response({
            "conversation_id": conversation.id,
            "user_message": MessageSerializer(user_msg).data,
            "assistant_message": MessageSerializer(assistant_msg).data,
        })
    except Exception as e:
        logger.error(f"Error sending conversation message: {str(e)}", exc_info=True)
        
        # Get the failed user message (should be the last one with failed status)
        failed_user_msg = conversation.messages.filter(role='user', status='failed').order_by('-created_at').first()
        
        if failed_user_msg:
            return Response({
                "conversation_id": conversation.id,
                "user_message": MessageSerializer(failed_user_msg).data,
                "assistant_message": None,
                "error": "Failed to send message",
                "detail": str(e),
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        else:
            return Response(
                {"error": "Failed to send message", "detail": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


@extend_schema(
    summary="Retry failed LiuYao message",
    description="Retry sending a failed message in a LiuYao conversation. Allows editing the message content. Resends the message along with all previous messages to maintain context.",
    request=inline_serializer(
        name='RetryMessageRequest',
        fields={
            'message_id': serializers.IntegerField(required=False, help_text='Message ID to retry (optional, defaults to last failed message)'),
            'message': serializers.CharField(required=False, help_text='New message content (optional, if not provided uses existing message content)'),
        }
    ),
    responses={
        200: inline_serializer(
            name='RetryMessageResponse',
            fields={
                'conversation_id': serializers.IntegerField(),
                'user_message': MessageSerializer(),
                'assistant_message': MessageSerializer(),
            }
        ),
        400: OpenApiResponse(description="Validation error"),
        401: OpenApiResponse(description="Authentication required"),
        404: OpenApiResponse(description="Conversation or message not found"),
    }
)
@api_view(['POST'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def retry_liuyao_message(request, liuyao_id, conversation_id):
    """Retry sending a failed message in a LiuYao conversation"""
    try:
        liuyao_obj = liuyao.objects.get(id=liuyao_id, user=request.user)
    except liuyao.DoesNotExist:
        return Response(
            {"error": "LiuYao record not found or you don't have permission to access it"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get or create subject for this liuyao
    subject = ConversationSubject.get_or_create_subject('liuyao', liuyao_id)
    try:
        conversation = Conversation.objects.get(
            id=conversation_id,
            subject=subject,
            user=request.user
        )
    except Conversation.DoesNotExist:
        return Response(
            {"error": "Conversation not found"},
            status=status.HTTP_404_NOT_FOUND
        )
    
    # Get the message to retry (by ID if provided, otherwise last failed message, otherwise last user message)
    message_id = request.data.get('message_id')
    if message_id:
        try:
            message_to_retry = conversation.messages.get(id=message_id, role='user')
        except Message.DoesNotExist:
            return Response(
                {"error": "Message not found"},
                status=status.HTTP_404_NOT_FOUND
            )
    else:
        # Try to get last failed message, otherwise get last user message
        message_to_retry = conversation.messages.filter(role='user', status='failed').order_by('-created_at').first()
        if not message_to_retry:
            message_to_retry = conversation.messages.filter(role='user').order_by('-created_at').first()
    
    if not message_to_retry:
        return Response(
            {"error": "No user message found to retry"},
            status=status.HTTP_400_BAD_REQUEST
        )
    
    # Get new message content if provided, otherwise use existing
    new_message_content = request.data.get('message', message_to_retry.content)
    
    # Get language from last assistant message or default
    last_assistant_msg = conversation.messages.filter(role='assistant').order_by('-created_at').first()
    language = last_assistant_msg.meta.get('language', 'zh-hans') if last_assistant_msg and last_assistant_msg.meta else 'zh-hans'
    
    # Get provider/model from request (user selection), fallback to last assistant message, then defaults
    provider = request.data.get('provider')
    model = request.data.get('model')
    if not provider and last_assistant_msg:
        provider = last_assistant_msg.provider
    if not model and last_assistant_msg:
        model = last_assistant_msg.model
    
    try:
        # Resend the message (this will resend everything: message + previous responses)
        # Pass existing_user_message to reuse the failed message instead of creating a new one
        assistant_msg = send_conversation_message(
            conversation=conversation,
            user_message=new_message_content,
            provider=provider,
            model=model,
            language=language,
            existing_user_message=message_to_retry
        )
        
        # Refresh message_to_retry to get updated status
        message_to_retry.refresh_from_db()
        
        return Response({
            "conversation_id": conversation.id,
            "user_message": MessageSerializer(message_to_retry).data,
            "assistant_message": MessageSerializer(assistant_msg).data,
        })
    except Exception as e:
        logger.error(f"Error retrying conversation message: {str(e)}", exc_info=True)
        
        # Refresh message_to_retry to get updated status (should be failed now)
        message_to_retry.refresh_from_db()
        
        # Return the failed message (should be message_to_retry)
        failed_user_msg = message_to_retry
        
        if failed_user_msg:
            return Response({
                "conversation_id": conversation.id,
                "user_message": MessageSerializer(failed_user_msg).data,
                "assistant_message": None,
                "error": "Failed to retry message",
                "detail": str(e),
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        else:
            return Response(
                {"error": "Failed to retry message", "detail": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


@extend_schema(
    summary="Get conversation configuration",
    description="Get the current conversation configuration settings (e.g., max_messages limit)",
    tags=["Conversations"],
    responses={
        200: ConversationConfigSerializer,
        401: OpenApiResponse(description="Authentication required"),
    }
)
@api_view(['GET'])
@authentication_classes([ActivityTrackingJWTAuthentication, SessionAuthentication])
@permission_classes([IsAuthenticated])
def get_conversation_config(request):
    """Get the current conversation configuration"""
    from ai.models import ConversationConfig
    
    max_messages = ConversationConfig.get_max_messages()
    
    serializer = ConversationConfigSerializer({
        'max_messages': max_messages
    })
    
    return Response(serializer.data, status=status.HTTP_200_OK)