from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_, and_
from typing import Optional, List
from datetime import datetime, date
from decimal import Decimal
from ..config.database import get_db
from ..middleware.auth import get_current_user, authorize_roles
from ..models.user import User
from ..models.package import Package, PackageItem, PackageStatus, PackageItemType
from ..models.rate_plan import RatePlan
from ..models.room_type import RoomType
from pydantic import BaseModel

router = APIRouter(prefix='/packages', tags=['packages'])

# Pydantic models
class PackageItemCreate(BaseModel):
    item_type: str
    item_id: Optional[int] = None
    item_name: str
    item_description: Optional[str] = None
    quantity: int = 1
    unit: Optional[str] = None
    price: Optional[float] = None
    included: bool = True
    price_modifier: Optional[float] = None
    display_order: int = 0
    extra_data: Optional[dict] = None

class PackageCreate(BaseModel):
    name: str
    code: str
    description: Optional[str] = None
    status: str = 'active'
    base_price: Optional[float] = None
    price_modifier: float = 1.0
    discount_percentage: Optional[float] = None
    room_type_id: Optional[int] = None
    min_nights: Optional[int] = None
    max_nights: Optional[int] = None
    valid_from: Optional[str] = None
    valid_to: Optional[str] = None
    image_url: Optional[str] = None
    highlights: Optional[List[str]] = None
    terms_conditions: Optional[str] = None
    extra_data: Optional[dict] = None
    items: Optional[List[PackageItemCreate]] = []

class PackageUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    status: Optional[str] = None
    base_price: Optional[float] = None
    price_modifier: Optional[float] = None
    discount_percentage: Optional[float] = None
    room_type_id: Optional[int] = None
    min_nights: Optional[int] = None
    max_nights: Optional[int] = None
    valid_from: Optional[str] = None
    valid_to: Optional[str] = None
    image_url: Optional[str] = None
    highlights: Optional[List[str]] = None
    terms_conditions: Optional[str] = None
    extra_data: Optional[dict] = None

@router.get('/')
async def get_packages(
    search: Optional[str] = Query(None),
    status_filter: Optional[str] = Query(None, alias='status'),
    room_type_id: Optional[int] = 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(Package)
        
        if search:
            query = query.filter(
                or_(
                    Package.name.like(f'%{search}%'),
                    Package.code.like(f'%{search}%'),
                    Package.description.like(f'%{search}%')
                )
            )
        
        if status_filter:
            try:
                query = query.filter(Package.status == PackageStatus(status_filter))
            except ValueError:
                pass
        
        if room_type_id:
            query = query.filter(
                or_(
                    Package.room_type_id == room_type_id,
                    Package.room_type_id.is_(None)
                )
            )
        
        total = query.count()
        offset = (page - 1) * limit
        packages = query.order_by(Package.created_at.desc()).offset(offset).limit(limit).all()
        
        result = []
        for pkg in packages:
            pkg_dict = {
                'id': pkg.id,
                'name': pkg.name,
                'code': pkg.code,
                'description': pkg.description,
                'status': pkg.status.value if isinstance(pkg.status, PackageStatus) else pkg.status,
                'base_price': float(pkg.base_price) if pkg.base_price else None,
                'price_modifier': float(pkg.price_modifier) if pkg.price_modifier else 1.0,
                'discount_percentage': float(pkg.discount_percentage) if pkg.discount_percentage else None,
                'room_type_id': pkg.room_type_id,
                'room_type_name': pkg.room_type.name if pkg.room_type else None,
                'min_nights': pkg.min_nights,
                'max_nights': pkg.max_nights,
                'valid_from': pkg.valid_from.isoformat() if pkg.valid_from else None,
                'valid_to': pkg.valid_to.isoformat() if pkg.valid_to else None,
                'image_url': pkg.image_url,
                'highlights': pkg.highlights,
                'terms_conditions': pkg.terms_conditions,
                'extra_data': pkg.extra_data,
                'created_at': pkg.created_at.isoformat() if pkg.created_at else None,
                'updated_at': pkg.updated_at.isoformat() if pkg.updated_at else None,
            }
            result.append(pkg_dict)
        
        return {
            'status': 'success',
            'data': {
                'packages': result,
                'pagination': {
                    'total': total,
                    'page': page,
                    'limit': limit,
                    'totalPages': (total + limit - 1) // limit
                }
            }
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/{id}')
async def get_package(id: int, db: Session = Depends(get_db)):
    try:
        pkg = db.query(Package).filter(Package.id == id).first()
        if not pkg:
            raise HTTPException(status_code=404, detail='Package not found')
        
        items = db.query(PackageItem).filter(PackageItem.package_id == id).order_by(PackageItem.display_order.asc()).all()
        
        pkg_dict = {
            'id': pkg.id,
            'name': pkg.name,
            'code': pkg.code,
            'description': pkg.description,
            'status': pkg.status.value if isinstance(pkg.status, PackageStatus) else pkg.status,
            'base_price': float(pkg.base_price) if pkg.base_price else None,
            'price_modifier': float(pkg.price_modifier) if pkg.price_modifier else 1.0,
            'discount_percentage': float(pkg.discount_percentage) if pkg.discount_percentage else None,
            'room_type_id': pkg.room_type_id,
            'room_type_name': pkg.room_type.name if pkg.room_type else None,
            'min_nights': pkg.min_nights,
            'max_nights': pkg.max_nights,
            'valid_from': pkg.valid_from.isoformat() if pkg.valid_from else None,
            'valid_to': pkg.valid_to.isoformat() if pkg.valid_to else None,
            'image_url': pkg.image_url,
            'highlights': pkg.highlights,
            'terms_conditions': pkg.terms_conditions,
            'extra_data': pkg.extra_data,
            'items': [
                {
                    'id': item.id,
                    'item_type': item.item_type.value if isinstance(item.item_type, PackageItemType) else item.item_type,
                    'item_id': item.item_id,
                    'item_name': item.item_name,
                    'item_description': item.item_description,
                    'quantity': item.quantity,
                    'unit': item.unit,
                    'price': float(item.price) if item.price else None,
                    'included': item.included,
                    'price_modifier': float(item.price_modifier) if item.price_modifier else None,
                    'display_order': item.display_order,
                    'extra_data': item.extra_data,
                }
                for item in items
            ],
            'created_at': pkg.created_at.isoformat() if pkg.created_at else None,
            'updated_at': pkg.updated_at.isoformat() if pkg.updated_at else None,
        }
        
        return {'status': 'success', 'data': {'package': pkg_dict}}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/', dependencies=[Depends(authorize_roles('admin'))])
async def create_package(package_data: PackageCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    try:
        # Check if code already exists
        existing = db.query(Package).filter(Package.code == package_data.code).first()
        if existing:
            raise HTTPException(status_code=400, detail='Package code already exists')
        
        # Validate room_type_id if provided
        if package_data.room_type_id:
            room_type = db.query(RoomType).filter(RoomType.id == package_data.room_type_id).first()
            if not room_type:
                raise HTTPException(status_code=404, detail='Room type not found')
        
        # Create package
        pkg = Package(
            name=package_data.name,
            code=package_data.code,
            description=package_data.description,
            status=PackageStatus(package_data.status),
            base_price=Decimal(str(package_data.base_price)) if package_data.base_price else None,
            price_modifier=Decimal(str(package_data.price_modifier)),
            discount_percentage=Decimal(str(package_data.discount_percentage)) if package_data.discount_percentage else None,
            room_type_id=package_data.room_type_id,
            min_nights=package_data.min_nights,
            max_nights=package_data.max_nights,
            valid_from=datetime.strptime(package_data.valid_from, '%Y-%m-%d').date() if package_data.valid_from else None,
            valid_to=datetime.strptime(package_data.valid_to, '%Y-%m-%d').date() if package_data.valid_to else None,
            image_url=package_data.image_url,
            highlights=package_data.highlights,
            terms_conditions=package_data.terms_conditions,
            extra_data=package_data.extra_data,
        )
        
        db.add(pkg)
        db.flush()
        
        # Create items
        if package_data.items:
            for item_data in package_data.items:
                item = PackageItem(
                    package_id=pkg.id,
                    item_type=PackageItemType(item_data.item_type),
                    item_id=item_data.item_id,
                    item_name=item_data.item_name,
                    item_description=item_data.item_description,
                    quantity=item_data.quantity,
                    unit=item_data.unit,
                    price=Decimal(str(item_data.price)) if item_data.price else None,
                    included=item_data.included,
                    price_modifier=Decimal(str(item_data.price_modifier)) if item_data.price_modifier else None,
                    display_order=item_data.display_order,
                    extra_data=item_data.extra_data,
                )
                db.add(item)
        
        db.commit()
        db.refresh(pkg)
        
        return {'status': 'success', 'message': 'Package created successfully', 'data': {'package_id': pkg.id}}
    except HTTPException:
        raise
    except ValueError as e:
        raise HTTPException(status_code=400, detail=f'Invalid enum value: {str(e)}')
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/{id}', dependencies=[Depends(authorize_roles('admin'))])
async def update_package(id: int, package_data: PackageUpdate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    try:
        pkg = db.query(Package).filter(Package.id == id).first()
        if not pkg:
            raise HTTPException(status_code=404, detail='Package not found')
        
        # Update fields
        if package_data.name is not None:
            pkg.name = package_data.name
        if package_data.description is not None:
            pkg.description = package_data.description
        if package_data.status is not None:
            pkg.status = PackageStatus(package_data.status)
        if package_data.base_price is not None:
            pkg.base_price = Decimal(str(package_data.base_price)) if package_data.base_price else None
        if package_data.price_modifier is not None:
            pkg.price_modifier = Decimal(str(package_data.price_modifier))
        if package_data.discount_percentage is not None:
            pkg.discount_percentage = Decimal(str(package_data.discount_percentage)) if package_data.discount_percentage else None
        if package_data.room_type_id is not None:
            if package_data.room_type_id:
                room_type = db.query(RoomType).filter(RoomType.id == package_data.room_type_id).first()
                if not room_type:
                    raise HTTPException(status_code=404, detail='Room type not found')
            pkg.room_type_id = package_data.room_type_id
        if package_data.min_nights is not None:
            pkg.min_nights = package_data.min_nights
        if package_data.max_nights is not None:
            pkg.max_nights = package_data.max_nights
        if package_data.valid_from is not None:
            pkg.valid_from = datetime.strptime(package_data.valid_from, '%Y-%m-%d').date() if package_data.valid_from else None
        if package_data.valid_to is not None:
            pkg.valid_to = datetime.strptime(package_data.valid_to, '%Y-%m-%d').date() if package_data.valid_to else None
        if package_data.image_url is not None:
            pkg.image_url = package_data.image_url
        if package_data.highlights is not None:
            pkg.highlights = package_data.highlights
        if package_data.terms_conditions is not None:
            pkg.terms_conditions = package_data.terms_conditions
        if package_data.extra_data is not None:
            pkg.extra_data = package_data.extra_data
        
        db.commit()
        
        return {'status': 'success', 'message': 'Package updated successfully'}
    except HTTPException:
        raise
    except ValueError as e:
        raise HTTPException(status_code=400, detail=f'Invalid enum value: {str(e)}')
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.delete('/{id}', dependencies=[Depends(authorize_roles('admin'))])
async def delete_package(id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    try:
        pkg = db.query(Package).filter(Package.id == id).first()
        if not pkg:
            raise HTTPException(status_code=404, detail='Package not found')
        
        # Check if package is used in rate plans
        rate_plan_count = db.query(RatePlan).filter(RatePlan.package_id == id).count()
        if rate_plan_count > 0:
            raise HTTPException(status_code=400, detail=f'Cannot delete package. It is used in {rate_plan_count} rate plan(s)')
        
        # Delete items first
        db.query(PackageItem).filter(PackageItem.package_id == id).delete()
        
        db.delete(pkg)
        db.commit()
        
        return {'status': 'success', 'message': 'Package deleted successfully'}
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/available/{room_type_id}')
async def get_available_packages(
    room_type_id: int,
    check_in: str = Query(...),
    check_out: str = Query(...),
    num_nights: Optional[int] = Query(None),
    db: Session = Depends(get_db)
):
    """Get available packages for a room type and date range"""
    try:
        check_in_date = datetime.strptime(check_in, '%Y-%m-%d').date()
        check_out_date = datetime.strptime(check_out, '%Y-%m-%d').date()
        
        if num_nights is None:
            num_nights = (check_out_date - check_in_date).days
        
        # Query packages
        query = db.query(Package).filter(
            Package.status == PackageStatus.active,
            or_(
                Package.room_type_id == room_type_id,
                Package.room_type_id.is_(None)
            )
        )
        
        # Filter by date range
        query = query.filter(
            or_(
                Package.valid_from.is_(None),
                Package.valid_from <= check_in_date
            ),
            or_(
                Package.valid_to.is_(None),
                Package.valid_to >= check_out_date
            )
        )
        
        # Filter by nights
        query = query.filter(
            or_(
                Package.min_nights.is_(None),
                Package.min_nights <= num_nights
            ),
            or_(
                Package.max_nights.is_(None),
                Package.max_nights >= num_nights
            )
        )
        
        packages = query.order_by(Package.created_at.desc()).all()
        
        result = []
        for pkg in packages:
            items = db.query(PackageItem).filter(PackageItem.package_id == pkg.id).order_by(PackageItem.display_order.asc()).all()
            
            pkg_dict = {
                'id': pkg.id,
                'name': pkg.name,
                'code': pkg.code,
                'description': pkg.description,
                'base_price': float(pkg.base_price) if pkg.base_price else None,
                'price_modifier': float(pkg.price_modifier) if pkg.price_modifier else 1.0,
                'discount_percentage': float(pkg.discount_percentage) if pkg.discount_percentage else None,
                'image_url': pkg.image_url,
                'highlights': pkg.highlights,
                'items': [
                    {
                        'id': item.id,
                        'item_type': item.item_type.value if isinstance(item.item_type, PackageItemType) else item.item_type,
                        'item_name': item.item_name,
                        'item_description': item.item_description,
                        'quantity': item.quantity,
                        'unit': item.unit,
                        'price': float(item.price) if item.price else None,
                        'included': item.included,
                    }
                    for item in items
                ],
            }
            result.append(pkg_dict)
        
        return {'status': 'success', 'data': {'packages': result}}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=f'Invalid date format: {str(e)}')
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

