from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from typing import Optional
from datetime import datetime, timedelta
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...security.middleware.auth import get_current_user, authorize_roles
from ...auth.models.user import User
from ..models.promotion import Promotion, DiscountType
from ..schemas.promotion import (
    ValidatePromotionRequest,
    CreatePromotionRequest,
    UpdatePromotionRequest
)

logger = get_logger(__name__)
router = APIRouter(prefix='/promotions', tags=['promotions'])

def serialize_promotion(promo: Promotion) -> dict:
    """Helper function to serialize promotion object with all fields"""
    return {
        'id': promo.id,
        'code': promo.code,
        'name': promo.name,
        'description': promo.description,
        'discount_type': promo.discount_type.value if isinstance(promo.discount_type, DiscountType) else promo.discount_type,
        'discount_value': float(promo.discount_value) if promo.discount_value else 0.0,
        'min_booking_amount': float(promo.min_booking_amount) if promo.min_booking_amount else None,
        'max_discount_amount': float(promo.max_discount_amount) if promo.max_discount_amount else None,
        'min_stay_days': promo.min_stay_days,
        'max_stay_days': promo.max_stay_days,
        'advance_booking_days': promo.advance_booking_days,
        'max_advance_booking_days': promo.max_advance_booking_days,
        'allowed_check_in_days': promo.allowed_check_in_days,
        'allowed_check_out_days': promo.allowed_check_out_days,
        'allowed_room_type_ids': promo.allowed_room_type_ids,
        'excluded_room_type_ids': promo.excluded_room_type_ids,
        'min_guests': promo.min_guests,
        'max_guests': promo.max_guests,
        'first_time_customer_only': promo.first_time_customer_only,
        'repeat_customer_only': promo.repeat_customer_only,
        'blackout_dates': promo.blackout_dates,
        'start_date': promo.start_date.isoformat() if promo.start_date else None,
        'end_date': promo.end_date.isoformat() if promo.end_date else None,
        'usage_limit': promo.usage_limit,
        'used_count': promo.used_count,
        'is_active': promo.is_active,
        'status': 'active' if promo.is_active else 'inactive',
        'created_at': promo.created_at.isoformat() if promo.created_at else None,
        'updated_at': promo.updated_at.isoformat() if promo.updated_at else None,
    }

@router.get('/')
async def get_promotions(search: Optional[str]=Query(None), status_filter: Optional[str]=Query(None, alias='status'), type: Optional[str]=Query(None), page: int=Query(1, ge=1), limit: int=Query(10, ge=1, le=100), db: Session=Depends(get_db)):
    try:
        query = db.query(Promotion)
        if search:
            query = query.filter(or_(Promotion.code.like(f'%{search}%'), Promotion.name.like(f'%{search}%')))
        if status_filter:
            is_active = status_filter == 'active'
            query = query.filter(Promotion.is_active == is_active)
        if type:
            try:
                query = query.filter(Promotion.discount_type == DiscountType(type))
            except ValueError:
                pass
        total = query.count()
        offset = (page - 1) * limit
        promotions = query.order_by(Promotion.created_at.desc()).offset(offset).limit(limit).all()
        result = []
        for promo in promotions:
            promo_dict = serialize_promotion(promo)
            result.append(promo_dict)
        return {'status': 'success', 'data': {'promotions': result, 'pagination': {'total': total, 'page': page, 'limit': limit, 'totalPages': (total + limit - 1) // limit}}}
    except Exception as e:
        db.rollback()
        logger.error(f'Error fetching promotions: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/{code}')
async def get_promotion_by_code(code: str, db: Session=Depends(get_db)):
    try:
        promotion = db.query(Promotion).filter(Promotion.code == code).first()
        if not promotion:
            raise HTTPException(status_code=404, detail='Promotion not found')
        promo_dict = serialize_promotion(promotion)
        return {'status': 'success', 'data': {'promotion': promo_dict}}
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f'Error fetching promotion by code: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/validate')
async def validate_promotion(validation_data: ValidatePromotionRequest, db: Session=Depends(get_db)):
    try:
        code = validation_data.code
        booking_amount = float(validation_data.booking_value or validation_data.booking_amount or 0)
        promotion = db.query(Promotion).filter(Promotion.code == code).first()
        if not promotion:
            raise HTTPException(status_code=404, detail='Promotion code not found')
        if not promotion.is_active:
            raise HTTPException(status_code=400, detail='Promotion is not active')
        now = datetime.utcnow()
        if promotion.start_date:
            # Compare datetimes directly (both should be timezone-naive UTC)
            if now < promotion.start_date:
                logger.warning(f'Promotion {code} not started yet. Now: {now}, Start: {promotion.start_date}')
                raise HTTPException(status_code=400, detail=f'Promotion is not valid yet. Valid from {promotion.start_date.strftime("%Y-%m-%d %H:%M:%S")} UTC')
        
        if promotion.end_date:
            # Compare datetimes directly (both should be timezone-naive UTC)
            if now > promotion.end_date:
                logger.warning(f'Promotion {code} expired. Now: {now}, End: {promotion.end_date}')
                raise HTTPException(status_code=400, detail=f'Promotion has expired. Valid until {promotion.end_date.strftime("%Y-%m-%d %H:%M:%S")} UTC')
        if promotion.usage_limit and promotion.used_count >= promotion.usage_limit:
            raise HTTPException(status_code=400, detail='Promotion usage limit reached')
        if promotion.min_booking_amount and booking_amount < float(promotion.min_booking_amount):
            raise HTTPException(status_code=400, detail=f'Minimum booking amount is {promotion.min_booking_amount}')
        
        # Check booking conditions if dates are provided
        if validation_data.check_in_date and validation_data.check_out_date:
            try:
                check_in = datetime.strptime(validation_data.check_in_date, '%Y-%m-%d').date()
                check_out = datetime.strptime(validation_data.check_out_date, '%Y-%m-%d').date()
                today = datetime.utcnow().date()
                
                # Check minimum stay days
                if promotion.min_stay_days:
                    stay_days = (check_out - check_in).days
                    if stay_days < promotion.min_stay_days:
                        raise HTTPException(
                            status_code=400, 
                            detail=f'This promotion requires a minimum stay of {promotion.min_stay_days} night{"s" if promotion.min_stay_days > 1 else ""}. Your booking is for {stay_days} night{"s" if stay_days != 1 else ""}.'
                        )
                
                # Check maximum stay days
                if promotion.max_stay_days:
                    stay_days = (check_out - check_in).days
                    if stay_days > promotion.max_stay_days:
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion allows a maximum stay of {promotion.max_stay_days} night{"s" if promotion.max_stay_days > 1 else ""}. Your booking is for {stay_days} night{"s" if stay_days != 1 else ""}.'
                        )
                
                # Check advance booking requirement
                if promotion.advance_booking_days:
                    days_until_checkin = (check_in - today).days
                    if days_until_checkin < promotion.advance_booking_days:
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion requires booking at least {promotion.advance_booking_days} day{"s" if promotion.advance_booking_days > 1 else ""} in advance. Check-in is in {days_until_checkin} day{"s" if days_until_checkin != 1 else ""}.'
                        )
                
                # Check maximum advance booking
                if promotion.max_advance_booking_days:
                    days_until_checkin = (check_in - today).days
                    if days_until_checkin > promotion.max_advance_booking_days:
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion allows booking up to {promotion.max_advance_booking_days} day{"s" if promotion.max_advance_booking_days > 1 else ""} in advance. Check-in is in {days_until_checkin} day{"s" if days_until_checkin != 1 else ""}.'
                        )
                
                # Check day of week restrictions for check-in
                if promotion.allowed_check_in_days:
                    check_in_weekday = check_in.weekday()  # 0=Monday, 6=Sunday
                    if check_in_weekday not in promotion.allowed_check_in_days:
                        days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
                        allowed_days = ', '.join([days[d] for d in promotion.allowed_check_in_days])
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion is only valid for check-in on: {allowed_days}'
                        )
                
                # Check day of week restrictions for check-out
                if promotion.allowed_check_out_days:
                    check_out_weekday = check_out.weekday()
                    if check_out_weekday not in promotion.allowed_check_out_days:
                        days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
                        allowed_days = ', '.join([days[d] for d in promotion.allowed_check_out_days])
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion is only valid for check-out on: {allowed_days}'
                        )
                
                # Check blackout dates
                if promotion.blackout_dates:
                    check_in_str = check_in.strftime('%Y-%m-%d')
                    check_out_str = check_out.strftime('%Y-%m-%d')
                    # Check if check-in or check-out falls on a blackout date
                    if check_in_str in promotion.blackout_dates:
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion is not valid for check-in on {check_in_str} (blackout date)'
                        )
                    if check_out_str in promotion.blackout_dates:
                        raise HTTPException(
                            status_code=400,
                            detail=f'This promotion is not valid for check-out on {check_out_str} (blackout date)'
                        )
                    # Check if any date in the stay range is a blackout date
                    current_date = check_in
                    while current_date < check_out:
                        if current_date.strftime('%Y-%m-%d') in promotion.blackout_dates:
                            raise HTTPException(
                                status_code=400,
                                detail=f'This promotion is not valid during your stay period (includes blackout date: {current_date.strftime("%Y-%m-%d")})'
                            )
                        current_date += timedelta(days=1)
                
            except ValueError as e:
                logger.warning(f'Invalid date format in promotion validation: {e}')
                # Continue validation even if date parsing fails (backward compatibility)
        
        # Check room type restrictions
        if validation_data.room_type_id is not None:
            if promotion.allowed_room_type_ids and validation_data.room_type_id not in promotion.allowed_room_type_ids:
                raise HTTPException(
                    status_code=400,
                    detail='This promotion is not valid for the selected room type'
                )
            if promotion.excluded_room_type_ids and validation_data.room_type_id in promotion.excluded_room_type_ids:
                raise HTTPException(
                    status_code=400,
                    detail='This promotion is not valid for the selected room type'
                )
        
        # Check guest count restrictions
        if validation_data.guest_count is not None:
            if promotion.min_guests and validation_data.guest_count < promotion.min_guests:
                raise HTTPException(
                    status_code=400,
                    detail=f'This promotion requires a minimum of {promotion.min_guests} guest{"s" if promotion.min_guests > 1 else ""}'
                )
            if promotion.max_guests and validation_data.guest_count > promotion.max_guests:
                raise HTTPException(
                    status_code=400,
                    detail=f'This promotion allows a maximum of {promotion.max_guests} guest{"s" if promotion.max_guests > 1 else ""}'
                )
        
        # Check customer type restrictions
        if promotion.first_time_customer_only:
            if validation_data.is_first_time_customer is None:
                raise HTTPException(
                    status_code=400,
                    detail='This promotion is only available to first-time customers. Please log in to verify your customer status.'
                )
            if not validation_data.is_first_time_customer:
                raise HTTPException(
                    status_code=400,
                    detail='This promotion is only available to first-time customers'
                )
        
        if promotion.repeat_customer_only:
            if validation_data.is_first_time_customer is None:
                raise HTTPException(
                    status_code=400,
                    detail='This promotion is only available to returning customers. Please log in to verify your customer status.'
                )
            if validation_data.is_first_time_customer:
                raise HTTPException(
                    status_code=400,
                    detail='This promotion is only available to returning customers'
                )
        
        discount_amount = promotion.calculate_discount(booking_amount)
        final_amount = booking_amount - discount_amount
        promo_dict = serialize_promotion(promotion)
        return {'success': True, 'status': 'success', 'data': {'promotion': promo_dict, 'discount': discount_amount, 'original_amount': booking_amount, 'discount_amount': discount_amount, 'final_amount': final_amount}, 'message': 'Promotion validated successfully'}
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f'Error validating promotion: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/', dependencies=[Depends(authorize_roles('admin'))])
async def create_promotion(promotion_data: CreatePromotionRequest, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
    try:
        code = promotion_data.code
        existing = db.query(Promotion).filter(Promotion.code == code).first()
        if existing:
            raise HTTPException(status_code=400, detail='Promotion code already exists')
        discount_type = promotion_data.discount_type
        discount_value = promotion_data.discount_value
        promotion = Promotion(
            code=code,
            name=promotion_data.name,
            description=promotion_data.description,
            discount_type=DiscountType(discount_type),
            discount_value=discount_value,
            min_booking_amount=promotion_data.min_booking_amount,
            max_discount_amount=promotion_data.max_discount_amount,
            min_stay_days=promotion_data.min_stay_days,
            max_stay_days=promotion_data.max_stay_days,
            advance_booking_days=promotion_data.advance_booking_days,
            max_advance_booking_days=promotion_data.max_advance_booking_days,
            allowed_check_in_days=promotion_data.allowed_check_in_days,
            allowed_check_out_days=promotion_data.allowed_check_out_days,
            allowed_room_type_ids=promotion_data.allowed_room_type_ids,
            excluded_room_type_ids=promotion_data.excluded_room_type_ids,
            min_guests=promotion_data.min_guests,
            max_guests=promotion_data.max_guests,
            first_time_customer_only=promotion_data.first_time_customer_only or False,
            repeat_customer_only=promotion_data.repeat_customer_only or False,
            blackout_dates=promotion_data.blackout_dates,
            start_date=datetime.fromisoformat(promotion_data.start_date.replace('Z', '+00:00')) if promotion_data.start_date else None,
            end_date=datetime.fromisoformat(promotion_data.end_date.replace('Z', '+00:00')) if promotion_data.end_date else None,
            usage_limit=promotion_data.usage_limit,
            used_count=0,
            is_active=promotion_data.status == 'active' if promotion_data.status else True
        )
        db.add(promotion)
        db.commit()
        db.refresh(promotion)
        return {'status': 'success', 'message': 'Promotion created successfully', 'data': {'promotion': promotion}}
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f'Error creating promotion: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/{id}', dependencies=[Depends(authorize_roles('admin'))])
async def update_promotion(id: int, promotion_data: UpdatePromotionRequest, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
    try:
        promotion = db.query(Promotion).filter(Promotion.id == id).first()
        if not promotion:
            raise HTTPException(status_code=404, detail='Promotion not found')
        code = promotion_data.code
        if code and code != promotion.code:
            existing = db.query(Promotion).filter(Promotion.code == code, Promotion.id != id).first()
            if existing:
                raise HTTPException(status_code=400, detail='Promotion code already exists')
        discount_type = promotion_data.discount_type or (promotion.discount_type.value if isinstance(promotion.discount_type, DiscountType) else promotion.discount_type)
        discount_value = promotion_data.discount_value
        if discount_value is not None and discount_type == 'percentage' and discount_value > 100:
            raise HTTPException(status_code=400, detail='Percentage discount cannot exceed 100%')
        if promotion_data.code is not None:
            promotion.code = promotion_data.code
        if promotion_data.name is not None:
            promotion.name = promotion_data.name
        if promotion_data.description is not None:
            promotion.description = promotion_data.description
        if promotion_data.discount_type is not None:
            promotion.discount_type = DiscountType(promotion_data.discount_type)
        if promotion_data.discount_value is not None:
            promotion.discount_value = promotion_data.discount_value
        if promotion_data.min_booking_amount is not None:
            promotion.min_booking_amount = promotion_data.min_booking_amount
        if promotion_data.max_discount_amount is not None:
            promotion.max_discount_amount = promotion_data.max_discount_amount
        if promotion_data.min_stay_days is not None:
            promotion.min_stay_days = promotion_data.min_stay_days
        if promotion_data.max_stay_days is not None:
            promotion.max_stay_days = promotion_data.max_stay_days
        if promotion_data.advance_booking_days is not None:
            promotion.advance_booking_days = promotion_data.advance_booking_days
        if promotion_data.max_advance_booking_days is not None:
            promotion.max_advance_booking_days = promotion_data.max_advance_booking_days
        if promotion_data.allowed_check_in_days is not None:
            promotion.allowed_check_in_days = promotion_data.allowed_check_in_days
        if promotion_data.allowed_check_out_days is not None:
            promotion.allowed_check_out_days = promotion_data.allowed_check_out_days
        if promotion_data.allowed_room_type_ids is not None:
            promotion.allowed_room_type_ids = promotion_data.allowed_room_type_ids
        if promotion_data.excluded_room_type_ids is not None:
            promotion.excluded_room_type_ids = promotion_data.excluded_room_type_ids
        if promotion_data.min_guests is not None:
            promotion.min_guests = promotion_data.min_guests
        if promotion_data.max_guests is not None:
            promotion.max_guests = promotion_data.max_guests
        if promotion_data.first_time_customer_only is not None:
            promotion.first_time_customer_only = promotion_data.first_time_customer_only
        if promotion_data.repeat_customer_only is not None:
            promotion.repeat_customer_only = promotion_data.repeat_customer_only
        if promotion_data.blackout_dates is not None:
            promotion.blackout_dates = promotion_data.blackout_dates
        if promotion_data.start_date is not None:
            promotion.start_date = datetime.fromisoformat(promotion_data.start_date.replace('Z', '+00:00')) if promotion_data.start_date else None
        if promotion_data.end_date is not None:
            promotion.end_date = datetime.fromisoformat(promotion_data.end_date.replace('Z', '+00:00')) if promotion_data.end_date else None
        if promotion_data.usage_limit is not None:
            promotion.usage_limit = promotion_data.usage_limit
        if promotion_data.status is not None:
            promotion.is_active = promotion_data.status == 'active'
        db.commit()
        db.refresh(promotion)
        return {'status': 'success', 'message': 'Promotion updated successfully', 'data': {'promotion': promotion}}
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f'Error updating promotion: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.delete('/{id}', dependencies=[Depends(authorize_roles('admin'))])
async def delete_promotion(id: int, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
    try:
        promotion = db.query(Promotion).filter(Promotion.id == id).first()
        if not promotion:
            raise HTTPException(status_code=404, detail='Promotion not found')
        db.delete(promotion)
        db.commit()
        return {'status': 'success', 'message': 'Promotion deleted successfully'}
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f'Error deleting promotion: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))