from sqlalchemy.orm import Session, load_only
from sqlalchemy import func, and_, or_, desc
from typing import List, Dict, Optional
from datetime import datetime, timedelta
from decimal import Decimal
from ...auth.models.user import User
from ...bookings.models.booking import Booking, BookingStatus
from ...payments.models.payment import Payment
from ...reviews.models.review import Review
from ..models.guest_preference import GuestPreference
from ..models.guest_note import GuestNote
from ..models.guest_tag import GuestTag
from ..models.guest_communication import GuestCommunication
from ..models.guest_segment import GuestSegment, guest_segment_association

class GuestProfileService:
    
    @staticmethod
    def calculate_lifetime_value(user_id: int, db: Session) -> Decimal:
        """Calculate guest lifetime value from all bookings and payments"""
        from ...payments.models.payment import PaymentStatus
        
        # Get payments through bookings
        total_revenue = db.query(func.coalesce(func.sum(Payment.amount), 0)).join(
            Booking, Payment.booking_id == Booking.id
        ).filter(
            Booking.user_id == user_id,
            Payment.payment_status == PaymentStatus.completed
        ).scalar()
        
        # Also include service bookings
        from ...hotel_services.models.service_booking import ServiceBooking, ServiceBookingStatus
        service_revenue = db.query(func.coalesce(func.sum(ServiceBooking.total_amount), 0)).filter(
            ServiceBooking.user_id == user_id,
            ServiceBooking.status == ServiceBookingStatus.completed
        ).scalar()
        
        return Decimal(str(total_revenue or 0)) + Decimal(str(service_revenue or 0))
    
    @staticmethod
    def calculate_satisfaction_score(user_id: int, db: Session) -> Optional[float]:
        """Calculate average satisfaction score from reviews"""
        avg_rating = db.query(func.avg(Review.rating)).filter(
            Review.user_id == user_id,
            Review.status == 'approved'
        ).scalar()
        
        return float(avg_rating) if avg_rating else None
    
    @staticmethod
    def get_booking_history(user_id: int, db: Session, limit: Optional[int] = None) -> List[Booking]:
        """Get complete booking history for a guest"""
        # Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
        query = db.query(Booking).options(
            load_only(
                Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
                Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
                Booking.total_price, Booking.original_price, Booking.discount_amount,
                Booking.promotion_code, Booking.status, Booking.deposit_paid,
                Booking.requires_deposit, Booking.special_requests,
                Booking.created_at, Booking.updated_at
            )
        ).filter(Booking.user_id == user_id).order_by(desc(Booking.created_at))
        if limit:
            query = query.limit(limit)
        return query.all()
    
    @staticmethod
    def get_booking_statistics(user_id: int, db: Session) -> Dict:
        """Get booking statistics for a guest"""
        # Use func.count with load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
        # This avoids SQLAlchemy generating subqueries with all columns
        total_bookings = db.query(func.count(Booking.id)).filter(Booking.user_id == user_id).scalar() or 0
        completed_bookings = db.query(func.count(Booking.id)).filter(
            Booking.user_id == user_id,
            Booking.status == BookingStatus.checked_out
        ).scalar() or 0
        cancelled_bookings = db.query(func.count(Booking.id)).filter(
            Booking.user_id == user_id,
            Booking.status == BookingStatus.cancelled
        ).scalar() or 0
        
        # Get last visit date
        # Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
        last_booking = db.query(Booking).options(
            load_only(
                Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
                Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
                Booking.total_price, Booking.original_price, Booking.discount_amount,
                Booking.promotion_code, Booking.status, Booking.deposit_paid,
                Booking.requires_deposit, Booking.special_requests,
                Booking.created_at, Booking.updated_at
            )
        ).filter(
            Booking.user_id == user_id,
            Booking.status == BookingStatus.checked_out
        ).order_by(desc(Booking.check_in_date)).first()
        
        last_visit_date = last_booking.check_in_date if last_booking else None
        
        # Get total nights stayed
        # Aggregate queries don't need load_only as they don't load full objects
        total_nights = db.query(
            func.sum(func.extract('day', Booking.check_out_date - Booking.check_in_date))
        ).filter(
            Booking.user_id == user_id,
            Booking.status == BookingStatus.checked_out
        ).scalar() or 0
        
        return {
            'total_bookings': total_bookings,
            'completed_bookings': completed_bookings,
            'cancelled_bookings': cancelled_bookings,
            'last_visit_date': last_visit_date.isoformat() if last_visit_date else None,
            'total_nights_stayed': int(total_nights)
        }
    
    @staticmethod
    def update_guest_metrics(user_id: int, db: Session) -> Dict:
        """Update guest metrics (lifetime value, satisfaction score, etc.)"""
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            return None
        
        # Calculate and update lifetime value
        lifetime_value = GuestProfileService.calculate_lifetime_value(user_id, db)
        user.lifetime_value = lifetime_value
        
        # Calculate and update satisfaction score
        satisfaction_score = GuestProfileService.calculate_satisfaction_score(user_id, db)
        if satisfaction_score:
            user.satisfaction_score = Decimal(str(satisfaction_score))
        
        # Update total visits
        stats = GuestProfileService.get_booking_statistics(user_id, db)
        user.total_visits = stats['completed_bookings']
        if stats['last_visit_date']:
            user.last_visit_date = datetime.fromisoformat(stats['last_visit_date'].replace('Z', '+00:00'))
        
        db.commit()
        db.refresh(user)
        
        return {
            'lifetime_value': float(lifetime_value),
            'satisfaction_score': satisfaction_score,
            'total_visits': user.total_visits,
            'last_visit_date': user.last_visit_date.isoformat() if user.last_visit_date else None
        }
    
    @staticmethod
    def get_guest_segments(user_id: int, db: Session) -> List[GuestSegment]:
        """Get all segments a guest belongs to"""
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            return []
        return user.guest_segments
    
    @staticmethod
    def assign_segment(user_id: int, segment_id: int, db: Session) -> bool:
        """Assign a guest to a segment"""
        user = db.query(User).filter(User.id == user_id).first()
        segment = db.query(GuestSegment).filter(GuestSegment.id == segment_id).first()
        
        if not user or not segment:
            return False
        
        if segment not in user.guest_segments:
            user.guest_segments.append(segment)
            db.commit()
        return True
    
    @staticmethod
    def remove_segment(user_id: int, segment_id: int, db: Session) -> bool:
        """Remove a guest from a segment"""
        user = db.query(User).filter(User.id == user_id).first()
        segment = db.query(GuestSegment).filter(GuestSegment.id == segment_id).first()
        
        if not user or not segment:
            return False
        
        if segment in user.guest_segments:
            user.guest_segments.remove(segment)
            db.commit()
        return True
    
    @staticmethod
    def get_guest_analytics(user_id: int, db: Session) -> Dict:
        """Get comprehensive analytics for a guest"""
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            return None
        
        # Get booking statistics
        booking_stats = GuestProfileService.get_booking_statistics(user_id, db)
        
        # Get lifetime value
        lifetime_value = GuestProfileService.calculate_lifetime_value(user_id, db)
        
        # Get satisfaction score
        satisfaction_score = GuestProfileService.calculate_satisfaction_score(user_id, db)
        
        # Get preferred room types
        # Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
        bookings = db.query(Booking).options(
            load_only(
                Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
                Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
                Booking.total_price, Booking.original_price, Booking.discount_amount,
                Booking.promotion_code, Booking.status, Booking.deposit_paid,
                Booking.requires_deposit, Booking.special_requests,
                Booking.created_at, Booking.updated_at
            )
        ).filter(Booking.user_id == user_id).all()
        room_type_counts = {}
        for booking in bookings:
            if booking.room and booking.room.room_type:
                room_type_name = booking.room.room_type.name
                room_type_counts[room_type_name] = room_type_counts.get(room_type_name, 0) + 1
        
        preferred_room_type = max(room_type_counts.items(), key=lambda x: x[1])[0] if room_type_counts else None
        
        # Get average booking value
        avg_booking_value = db.query(func.avg(Booking.total_price)).filter(
            Booking.user_id == user_id,
            Booking.status.in_([BookingStatus.confirmed, BookingStatus.checked_out])
        ).scalar() or 0
        
        # Get communication count
        communication_count = db.query(GuestCommunication).filter(
            GuestCommunication.user_id == user_id
        ).count()
        
        return {
            'lifetime_value': float(lifetime_value),
            'satisfaction_score': satisfaction_score,
            'booking_statistics': booking_stats,
            'preferred_room_type': preferred_room_type,
            'average_booking_value': float(avg_booking_value),
            'communication_count': communication_count,
            'is_vip': user.is_vip,
            'total_visits': user.total_visits,
            'last_visit_date': user.last_visit_date.isoformat() if user.last_visit_date else None
        }
    
    @staticmethod
    def search_guests(
        db: Session,
        search: Optional[str] = None,
        is_vip: Optional[bool] = None,
        segment_id: Optional[int] = None,
        min_lifetime_value: Optional[float] = None,
        min_satisfaction_score: Optional[float] = None,
        tag_id: Optional[int] = None,
        page: int = 1,
        limit: int = 10
    ) -> Dict:
        """Search and filter guests with various criteria"""
        query = db.query(User).filter(User.role_id == 3)  # Only customers
        
        if search:
            query = query.filter(
                or_(
                    User.full_name.ilike(f'%{search}%'),
                    User.email.ilike(f'%{search}%'),
                    User.phone.ilike(f'%{search}%')
                )
            )
        
        if is_vip is not None:
            query = query.filter(User.is_vip == is_vip)
        
        if segment_id:
            query = query.join(User.guest_segments).filter(GuestSegment.id == segment_id)
        
        if min_lifetime_value is not None:
            query = query.filter(User.lifetime_value >= Decimal(str(min_lifetime_value)))
        
        if min_satisfaction_score is not None:
            query = query.filter(User.satisfaction_score >= Decimal(str(min_satisfaction_score)))
        
        if tag_id:
            query = query.join(User.guest_tags).filter(GuestTag.id == tag_id)
        
        total = query.count()
        offset = (page - 1) * limit
        guests = query.order_by(desc(User.lifetime_value)).offset(offset).limit(limit).all()
        
        return {
            'guests': guests,
            'total': total,
            'page': page,
            'limit': limit,
            'total_pages': (total + limit - 1) // limit
        }

