from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from . import repository
from .schema import (
    CategoryCreate,
    CategoryUpdate,
    DestinationCreate,
    DestinationUpdate,
    SeasonCreate,
    SeasonUpdate,
    AmenityCreate,
    AmenityUpdate,
    TourpackageCreate,
    TourpackageUpdate,
    TourpackagedestinationCreate,
    TourpackagedestinationUpdate,
    TourpackageamenityCreate,
    TourpackageamenityUpdate,
    ItineraryCreate,
    ItineraryUpdate,
    TourpackageDetailResponse,
    DestinationInPackageResponse,
    AmenityInPackageResponse,
)
from .models import Category, Destination, Season, Amenity, Tourpackage


# Category Handlers
def create_category(db: Session, data: CategoryCreate):
    existing = repository.get_by_slug(db, data.slug)
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Category with slug '{data.slug}' already exists",
        )
    try:
        category = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(category)
        return category
    except Exception:
        db.rollback()
        raise


def list_categories(db: Session, limit: int, offset: int, is_active: Optional[bool], search: Optional[str]):
    filters = {}
    if is_active is not None:
        filters["is_active"] = is_active
    if search:
        filters["search"] = search
    return repository.list_all(db, limit, offset, **filters)


def get_category(db: Session, category_id: str):
    category = repository.get_by_id(db, category_id)
    if not category:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Category with id '{category_id}' not found",
        )
    return category


def update_category(db: Session, category_id: str, data: CategoryUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    if "slug" in update_data:
        existing = repository.get_by_slug(db, update_data["slug"])
        if existing and existing.id != category_id:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Category with slug '{update_data['slug']}' already exists",
            )
    try:
        category = repository.update(db, category_id, update_data)
        if not category:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Category with id '{category_id}' not found",
            )
        db.commit()
        db.refresh(category)
        return category
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_category(db: Session, category_id: str):
    try:
        success = repository.delete(db, category_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Category with id '{category_id}' not found",
            )
        db.commit()
        return {"message": "Category deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


# Destination Handlers
def create_destination(db: Session, data: DestinationCreate):
    existing = repository.get_destination_by_slug(db, data.slug)
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Destination with slug '{data.slug}' already exists",
        )
    try:
        destination = repository.create_destination(db, data.model_dump())
        db.commit()
        db.refresh(destination)
        return destination
    except Exception:
        db.rollback()
        raise


def list_destinations(db: Session, limit: int, offset: int, is_active: Optional[bool], country: Optional[str], search: Optional[str]):
    filters = {}
    if is_active is not None:
        filters["is_active"] = is_active
    if country:
        filters["country"] = country
    if search:
        filters["search"] = search
    return repository.list_destinations(db, limit, offset, **filters)


def get_destination(db: Session, destination_id: str):
    destination = repository.get_destination_by_id(db, destination_id)
    if not destination:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Destination with id '{destination_id}' not found",
        )
    return destination


def update_destination(db: Session, destination_id: str, data: DestinationUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    if "slug" in update_data:
        existing = repository.get_destination_by_slug(db, update_data["slug"])
        if existing and existing.id != destination_id:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Destination with slug '{update_data['slug']}' already exists",
            )
    try:
        destination = repository.update_destination(db, destination_id, update_data)
        if not destination:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Destination with id '{destination_id}' not found",
            )
        db.commit()
        db.refresh(destination)
        return destination
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_destination(db: Session, destination_id: str):
    try:
        success = repository.delete_destination(db, destination_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Destination with id '{destination_id}' not found",
            )
        db.commit()
        return {"message": "Destination deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


# Season Handlers
def create_season(db: Session, data: SeasonCreate):
    try:
        season = repository.create_season(db, data.model_dump())
        db.commit()
        db.refresh(season)
        return season
    except Exception:
        db.rollback()
        raise


def list_seasons(db: Session, limit: int, offset: int, season_type: Optional[str]):
    filters = {}
    if season_type:
        filters["season_type"] = season_type
    return repository.list_seasons(db, limit, offset, **filters)


def get_season(db: Session, season_id: str):
    season = repository.get_season_by_id(db, season_id)
    if not season:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Season with id '{season_id}' not found",
        )
    return season


def update_season(db: Session, season_id: str, data: SeasonUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    try:
        season = repository.update_season(db, season_id, update_data)
        if not season:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Season with id '{season_id}' not found",
            )
        db.commit()
        db.refresh(season)
        return season
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_season(db: Session, season_id: str):
    try:
        success = repository.delete_season(db, season_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Season with id '{season_id}' not found",
            )
        db.commit()
        return {"message": "Season deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


# Amenity Handlers
def create_amenity(db: Session, data: AmenityCreate):
    try:
        amenity = repository.create_amenity(db, data.model_dump())
        db.commit()
        db.refresh(amenity)
        return amenity
    except Exception:
        db.rollback()
        raise


def list_amenities(db: Session, limit: int, offset: int, amenity_type: Optional[str], search: Optional[str]):
    filters = {}
    if amenity_type:
        filters["amenity_type"] = amenity_type
    if search:
        filters["search"] = search
    return repository.list_amenities(db, limit, offset, **filters)


def get_amenity(db: Session, amenity_id: str):
    amenity = repository.get_amenity_by_id(db, amenity_id)
    if not amenity:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Amenity with id '{amenity_id}' not found",
        )
    return amenity


def update_amenity(db: Session, amenity_id: str, data: AmenityUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    try:
        amenity = repository.update_amenity(db, amenity_id, update_data)
        if not amenity:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Amenity with id '{amenity_id}' not found",
            )
        db.commit()
        db.refresh(amenity)
        return amenity
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_amenity(db: Session, amenity_id: str):
    try:
        success = repository.delete_amenity(db, amenity_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Amenity with id '{amenity_id}' not found",
            )
        db.commit()
        return {"message": "Amenity deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


# Tourpackage Handlers
def create_tourpackage(db: Session, data: TourpackageCreate):
    category = repository.get_by_id(db, data.category_id)
    if not category:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Category with id '{data.category_id}' not found",
        )
    existing = repository.get_tourpackage_by_slug(db, data.slug)
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Tour package with slug '{data.slug}' already exists",
        )
    try:
        tourpackage = repository.create_tourpackage(db, data.model_dump())
        db.commit()
        db.refresh(tourpackage)
        return tourpackage
    except Exception:
        db.rollback()
        raise


def list_tourpackages(
    db: Session,
    limit: int,
    offset: int,
    is_active: Optional[bool],
    is_featured: Optional[bool],
    category_id: Optional[str],
    difficulty_level: Optional[str],
    search: Optional[str],
):
    filters = {}
    if is_active is not None:
        filters["is_active"] = is_active
    if is_featured is not None:
        filters["is_featured"] = is_featured
    if category_id:
        filters["category_id"] = category_id
    if difficulty_level:
        filters["difficulty_level"] = difficulty_level
    if search:
        filters["search"] = search
    return repository.list_tourpackages(db, limit, offset, **filters)


def get_tourpackage(db: Session, tourpackage_id: str):
    tourpackage = repository.get_tourpackage_by_id(db, tourpackage_id)
    if not tourpackage:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package with id '{tourpackage_id}' not found",
        )
    return tourpackage


def update_tourpackage(db: Session, tourpackage_id: str, data: TourpackageUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    if "category_id" in update_data:
        category = repository.get_by_id(db, update_data["category_id"])
        if not category:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Category with id '{update_data['category_id']}' not found",
            )
    if "slug" in update_data:
        existing = repository.get_tourpackage_by_slug(db, update_data["slug"])
        if existing and existing.id != tourpackage_id:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Tour package with slug '{update_data['slug']}' already exists",
            )
    try:
        tourpackage = repository.update_tourpackage(db, tourpackage_id, update_data)
        if not tourpackage:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package with id '{tourpackage_id}' not found",
            )
        db.commit()
        db.refresh(tourpackage)
        return tourpackage
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_tourpackage(db: Session, tourpackage_id: str):
    try:
        success = repository.delete_tourpackage(db, tourpackage_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package with id '{tourpackage_id}' not found",
            )
        db.commit()
        return {"message": "Tour package deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def get_tourpackage_details(db: Session, tourpackage_id: str):
    tourpackage = repository.get_tourpackage_with_details(db, tourpackage_id)
    if not tourpackage:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package with id '{tourpackage_id}' not found",
        )
    
    destinations = []
    for tpd in sorted(tourpackage.tour_package_destinations, key=lambda x: x.visit_order):
        destinations.append(
            DestinationInPackageResponse(
                id=tpd.destination.id,
                name=tpd.destination.name,
                country=tpd.destination.country,
                state=tpd.destination.state,
                city=tpd.destination.city,
                visit_order=tpd.visit_order,
                duration_hours=tpd.duration_hours,
            )
        )
    
    amenities = []
    for tpa in tourpackage.tour_package_amenities:
        amenities.append(
            AmenityInPackageResponse(
                id=tpa.amenity.id,
                name=tpa.amenity.name,
                amenity_type=tpa.amenity.amenity_type,
            )
        )
    
    return TourpackageDetailResponse(
        id=tourpackage.id,
        category_id=tourpackage.category_id,
        category_name=tourpackage.category.name,
        name=tourpackage.name,
        slug=tourpackage.slug,
        description=tourpackage.description,
        duration_days=tourpackage.duration_days,
        duration_nights=tourpackage.duration_nights,
        difficulty_level=tourpackage.difficulty_level,
        min_age=tourpackage.min_age,
        max_age=tourpackage.max_age,
        min_group_size=tourpackage.min_group_size,
        max_group_size=tourpackage.max_group_size,
        base_price=tourpackage.base_price,
        child_price=tourpackage.child_price,
        single_supplement=tourpackage.single_supplement,
        inclusions=tourpackage.inclusions,
        exclusions=tourpackage.exclusions,
        terms_and_conditions=tourpackage.terms_and_conditions,
        cancellation_policy=tourpackage.cancellation_policy,
        is_active=tourpackage.is_active,
        is_featured=tourpackage.is_featured,
        rating=tourpackage.rating,
        total_reviews=tourpackage.total_reviews,
        image_url=tourpackage.image_url,
        created_at=tourpackage.created_at,
        updated_at=tourpackage.updated_at,
        itineraries=[
            {
                "id": it.id,
                "tour_package_id": it.tour_package_id,
                "day_number": it.day_number,
                "title": it.title,
                "description": it.description,
                "activities": it.activities,
                "meals_included": it.meals_included,
                "accommodation_type": it.accommodation_type,
                "start_time": it.start_time,
                "end_time": it.end_time,
                "created_at": it.created_at,
                "updated_at": it.updated_at,
            }
            for it in sorted(tourpackage.itineraries, key=lambda x: x.day_number)
        ],
        destinations=destinations,
        amenities=amenities,
    )


# Tourpackagedestination Handlers
def create_tourpackagedestination(db: Session, data: TourpackagedestinationCreate):
    tourpackage = repository.get_tourpackage_by_id(db, data.tour_package_id)
    if not tourpackage:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package with id '{data.tour_package_id}' not found",
        )
    destination = repository.get_destination_by_id(db, data.destination_id)
    if not destination:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Destination with id '{data.destination_id}' not found",
        )
    try:
        tpd = repository.create_tourpackagedestination(db, data.model_dump())
        db.commit()
        db.refresh(tpd)
        return tpd
    except Exception:
        db.rollback()
        raise


def list_tourpackagedestinations(db: Session, limit: int, offset: int, tour_package_id: Optional[str], destination_id: Optional[str]):
    filters = {}
    if tour_package_id:
        filters["tour_package_id"] = tour_package_id
    if destination_id:
        filters["destination_id"] = destination_id
    return repository.list_tourpackagedestinations(db, limit, offset, **filters)


def get_tourpackagedestination(db: Session, tpd_id: str):
    tpd = repository.get_tourpackagedestination_by_id(db, tpd_id)
    if not tpd:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package destination with id '{tpd_id}' not found",
        )
    return tpd


def update_tourpackagedestination(db: Session, tpd_id: str, data: TourpackagedestinationUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    if "tour_package_id" in update_data:
        tourpackage = repository.get_tourpackage_by_id(db, update_data["tour_package_id"])
        if not tourpackage:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package with id '{update_data['tour_package_id']}' not found",
            )
    if "destination_id" in update_data:
        destination = repository.get_destination_by_id(db, update_data["destination_id"])
        if not destination:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Destination with id '{update_data['destination_id']}' not found",
            )
    try:
        tpd = repository.update_tourpackagedestination(db, tpd_id, update_data)
        if not tpd:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package destination with id '{tpd_id}' not found",
            )
        db.commit()
        db.refresh(tpd)
        return tpd
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_tourpackagedestination(db: Session, tpd_id: str):
    try:
        success = repository.delete_tourpackagedestination(db, tpd_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package destination with id '{tpd_id}' not found",
            )
        db.commit()
        return {"message": "Tour package destination deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


# Tourpackageamenity Handlers
def create_tourpackageamenity(db: Session, data: TourpackageamenityCreate):
    tourpackage = repository.get_tourpackage_by_id(db, data.tour_package_id)
    if not tourpackage:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package with id '{data.tour_package_id}' not found",
        )
    amenity = repository.get_amenity_by_id(db, data.amenity_id)
    if not amenity:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Amenity with id '{data.amenity_id}' not found",
        )
    try:
        tpa = repository.create_tourpackageamenity(db, data.model_dump())
        db.commit()
        db.refresh(tpa)
        return tpa
    except Exception:
        db.rollback()
        raise


def list_tourpackageamenities(db: Session, limit: int, offset: int, tour_package_id: Optional[str], amenity_id: Optional[str]):
    filters = {}
    if tour_package_id:
        filters["tour_package_id"] = tour_package_id
    if amenity_id:
        filters["amenity_id"] = amenity_id
    return repository.list_tourpackageamenities(db, limit, offset, **filters)


def get_tourpackageamenity(db: Session, tpa_id: str):
    tpa = repository.get_tourpackageamenity_by_id(db, tpa_id)
    if not tpa:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package amenity with id '{tpa_id}' not found",
        )
    return tpa


def update_tourpackageamenity(db: Session, tpa_id: str, data: TourpackageamenityUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    if "tour_package_id" in update_data:
        tourpackage = repository.get_tourpackage_by_id(db, update_data["tour_package_id"])
        if not tourpackage:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package with id '{update_data['tour_package_id']}' not found",
            )
    if "amenity_id" in update_data:
        amenity = repository.get_amenity_by_id(db, update_data["amenity_id"])
        if not amenity:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Amenity with id '{update_data['amenity_id']}' not found",
            )
    try:
        tpa = repository.update_tourpackageamenity(db, tpa_id, update_data)
        if not tpa:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package amenity with id '{tpa_id}' not found",
            )
        db.commit()
        db.refresh(tpa)
        return tpa
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_tourpackageamenity(db: Session, tpa_id: str):
    try:
        success = repository.delete_tourpackageamenity(db, tpa_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package amenity with id '{tpa_id}' not found",
            )
        db.commit()
        return {"message": "Tour package amenity deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


# Itinerary Handlers
def create_itinerary(db: Session, data: ItineraryCreate):
    tourpackage = repository.get_tourpackage_by_id(db, data.tour_package_id)
    if not tourpackage:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tour package with id '{data.tour_package_id}' not found",
        )
    try:
        itinerary = repository.create_itinerary(db, data.model_dump())
        db.commit()
        db.refresh(itinerary)
        return itinerary
    except Exception:
        db.rollback()
        raise


def list_itineraries(db: Session, limit: int, offset: int, tour_package_id: Optional[str]):
    filters = {}
    if tour_package_id:
        filters["tour_package_id"] = tour_package_id
    return repository.list_itineraries(db, limit, offset, **filters)


def get_itinerary(db: Session, itinerary_id: str):
    itinerary = repository.get_itinerary_by_id(db, itinerary_id)
    if not itinerary:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Itinerary with id '{itinerary_id}' not found",
        )
    return itinerary


def update_itinerary(db: Session, itinerary_id: str, data: ItineraryUpdate):
    update_data = data.model_dump(exclude_unset=True)
    if not update_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No fields to update",
        )
    if "tour_package_id" in update_data:
        tourpackage = repository.get_tourpackage_by_id(db, update_data["tour_package_id"])
        if not tourpackage:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour package with id '{update_data['tour_package_id']}' not found",
            )
    try:
        itinerary = repository.update_itinerary(db, itinerary_id, update_data)
        if not itinerary:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Itinerary with id '{itinerary_id}' not found",
            )
        db.commit()
        db.refresh(itinerary)
        return itinerary
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise


def delete_itinerary(db: Session, itinerary_id: str):
    try:
        success = repository.delete_itinerary(db, itinerary_id)
        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Itinerary with id '{itinerary_id}' not found",
            )
        db.commit()
        return {"message": "Itinerary deleted successfully"}
    except HTTPException:
        raise
    except Exception:
        db.rollback()
        raise