"""
Residents Table Chart API

Gantt-style visualization showing hotel bookings with restaurant table indicators.
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, or_
from datetime import date, timedelta
from typing import Optional
from pydantic import BaseModel

from auth import get_current_user, require_cap
from database import get_db
from models.user import User
from models.newbook import NewbookDailyOccupancy
from models.resos import ResosBooking

router = APIRouter(prefix="/residents-table-chart", tags=["Residents Table Chart"])


class RestaurantBookingDetail(BaseModel):
    has_booking: bool
    time: Optional[str] = None
    people: Optional[int] = None
    table_name: Optional[str] = None
    opening_hour_name: Optional[str] = None
    is_group_match: Optional[bool] = None  # True if matched via group/exclude field (not the lead booking)


class BookingSegment(BaseModel):
    booking_id: str | None
    bookings_group_id: Optional[str] = None
    check_in: str
    check_out: str
    nights: list[str]
    is_dbb: Optional[bool] = None
    is_package: Optional[bool] = None
    restaurant_bookings: dict[str, RestaurantBookingDetail]


class RoomRow(BaseModel):
    room_number: str | None
    bookings: list[BookingSegment]  # Multiple bookings in the same room


class ResidentsTableChartResponse(BaseModel):
    date_range: dict
    rooms: list[RoomRow]  # Changed from 'bookings' to 'rooms'
    summary: dict
    metrics: Optional[dict] = None  # Aggregated metrics for different time periods


@router.get("")
async def get_residents_table_chart(
    start_date: Optional[date] = None,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
) -> ResidentsTableChartResponse:
    """
    Get Gantt-style chart data showing hotel bookings with restaurant table indicators.

    Args:
        start_date: First day of 7-day period (defaults to today)

    Returns:
        Chart data with hotel stays and restaurant booking indicators
    """
    import logging
    logger = logging.getLogger(__name__)

    if start_date is None:
        start_date = date.today()

    logger.info(f"ResidentsTableChart API called with start_date={start_date}")

    end_date = start_date + timedelta(days=6)  # 7-day period
    date_range = {
        "start_date": start_date.isoformat(),
        "end_date": end_date.isoformat(),
        "dates": [(start_date + timedelta(days=i)).isoformat() for i in range(7)]
    }

    # Fetch Newbook occupancy data for 7-day period
    result = await db.execute(
        select(NewbookDailyOccupancy).where(
            and_(
                NewbookDailyOccupancy.kitchen_id == current_user.kitchen_id,
                NewbookDailyOccupancy.date >= start_date,
                NewbookDailyOccupancy.date <= end_date,
                NewbookDailyOccupancy.rooms_breakdown.isnot(None)  # Only records with room breakdown
            )
        ).order_by(NewbookDailyOccupancy.date)
    )
    occupancy_records = result.scalars().all()
    logger.info(f"Found {len(occupancy_records)} occupancy records")

    # Group by room number only (one row per room in Gantt chart)
    # Key: room_number, Value: dict of bookings for that room
    rooms_dict = {}
    for record in occupancy_records:
        # Parse JSONB array - each element is a room object for this date
        rooms = record.rooms_breakdown or []

        for room in rooms:
            room_number = room.get("room_number")
            booking_id = room.get("booking_id")

            if room_number not in rooms_dict:
                rooms_dict[room_number] = {}

            # Track each booking within this room
            if booking_id not in rooms_dict[room_number]:
                rooms_dict[room_number][booking_id] = {
                    'booking_id': booking_id,
                    'bookings_group_id': room.get("bookings_group_id"),
                    'nights': [],
                    'is_dbb': room.get("is_dbb", False),
                    'is_package': room.get("is_package", False)
                }

            rooms_dict[room_number][booking_id]['nights'].append(record.date)

    # Log rooms with multiple bookings to diagnose stacking issue
    for room_number, bookings in rooms_dict.items():
        if len(bookings) > 1:
            logger.warning(f"Room {room_number} has {len(bookings)} different bookings:")
            for booking_id, booking_data in bookings.items():
                nights_str = ', '.join(sorted([n.isoformat() for n in booking_data['nights']]))
                logger.warning(f"  - Booking {booking_id}: nights={nights_str}")

    # Convert to list - one entry per room with all its bookings
    hotel_stays = []
    for room_number, bookings in rooms_dict.items():
        # Collect all bookings for this room
        room_bookings = []
        all_nights = []

        for booking_data in bookings.values():
            nights = sorted(booking_data['nights'])
            if nights:
                all_nights.extend(nights)
                check_in = nights[0]
                check_out = nights[-1] + timedelta(days=1)

                room_bookings.append({
                    'booking_id': booking_data['booking_id'],
                    'bookings_group_id': booking_data.get('bookings_group_id'),
                    'check_in': check_in.isoformat(),
                    'check_out': check_out.isoformat(),
                    'nights': [n.isoformat() for n in nights],
                    'is_dbb': booking_data['is_dbb'],
                    'is_package': booking_data['is_package']
                })

        # Create one entry per room with all bookings
        if room_bookings:
            all_nights_sorted = sorted(set(all_nights))
            hotel_stays.append({
                'room_number': room_number,
                'bookings': room_bookings,  # Array of all bookings in this room
                'all_nights': [n.isoformat() for n in all_nights_sorted]  # All occupied nights for this room
            })

    logger.info(f"Built {len(hotel_stays)} room entries")

    # Fetch Resos bookings for hotel guests in this period
    result = await db.execute(
        select(ResosBooking).where(
            and_(
                ResosBooking.kitchen_id == current_user.kitchen_id,
                ResosBooking.booking_date >= start_date,
                ResosBooking.booking_date <= end_date,
                ResosBooking.is_hotel_guest == True,
                ResosBooking.hotel_booking_number.isnot(None)
            )
        )
    )
    resos_bookings = result.scalars().all()

    # Build lookup: booking_id -> {date -> resos_booking}
    # Also handle group bookings via exclude_flag field (format: "#32990,#32991")
    resos_lookup = {}
    import re

    for resos_booking in resos_bookings:
        booking_id = resos_booking.hotel_booking_number
        booking_date = resos_booking.booking_date.isoformat()

        if booking_id not in resos_lookup:
            resos_lookup[booking_id] = {}

        # Direct match for the lead/primary booking
        resos_lookup[booking_id][booking_date] = {
            'has_booking': True,
            'time': resos_booking.booking_time.strftime('%H:%M') if resos_booking.booking_time else None,
            'people': resos_booking.people,
            'table_name': resos_booking.table_name,
            'opening_hour_name': resos_booking.opening_hour_name,
            'is_group_match': False  # Direct match, not a group member
        }

        # Parse exclude_flag for group bookings (format: "#32990,#32991")
        if resos_booking.exclude_flag:
            # Extract all booking numbers from the exclude_flag field
            group_booking_ids = re.findall(r'#(\d+)', resos_booking.exclude_flag)

            for group_id in group_booking_ids:
                # Skip the lead booking itself (already added above)
                if group_id == booking_id:
                    continue

                # Add group member with is_group_match=True
                if group_id not in resos_lookup:
                    resos_lookup[group_id] = {}

                # Only add if not already present (don't overwrite direct matches)
                if booking_date not in resos_lookup[group_id]:
                    resos_lookup[group_id][booking_date] = {
                        'has_booking': True,
                        'time': resos_booking.booking_time.strftime('%H:%M') if resos_booking.booking_time else None,
                        'people': resos_booking.people,
                        'table_name': resos_booking.table_name,
                        'opening_hour_name': resos_booking.opening_hour_name,
                        'is_group_match': True  # Matched via group, not direct
                    }

    logger.info(f"Built resos_lookup with {len(resos_lookup)} booking IDs (including group matches)")

    # Combine rooms with restaurant bookings
    room_rows = []
    total_room_nights = 0
    nights_with_restaurant = 0

    try:
        for room_data in hotel_stays:
            booking_segments = []

            # Process each booking within this room
            for booking_data in room_data['bookings']:
                # Build restaurant bookings dict for each night in the 7-day period
                restaurant_bookings = {}

                for date_str in date_range['dates']:
                    # Check if this date is within this specific booking's nights
                    if date_str in booking_data['nights']:
                        total_room_nights += 1

                        # Check if there's a restaurant booking for this date
                        resos_data = resos_lookup.get(booking_data['booking_id'], {}).get(date_str)

                        if resos_data:
                            restaurant_bookings[date_str] = resos_data
                            nights_with_restaurant += 1
                        else:
                            restaurant_bookings[date_str] = {'has_booking': False}
                    else:
                        # Not staying this night
                        restaurant_bookings[date_str] = {'has_booking': False}

                # Create booking segment with restaurant data
                booking_segments.append(BookingSegment(
                    booking_id=booking_data['booking_id'],
                    bookings_group_id=booking_data.get('bookings_group_id'),
                    check_in=booking_data['check_in'],
                    check_out=booking_data['check_out'],
                    nights=booking_data['nights'],
                    is_dbb=booking_data['is_dbb'],
                    is_package=booking_data['is_package'],
                    restaurant_bookings=restaurant_bookings
                ))

            # Create room row with all its bookings
            room_rows.append(RoomRow(
                room_number=room_data['room_number'],
                bookings=booking_segments
            ))
    except Exception as e:
        logger.error(f"Error building room_rows: {e}", exc_info=True)
        raise

    logger.info(f"Built {len(room_rows)} room rows")

    # Sort rooms by room number (natural sort for numeric rooms)
    def natural_sort_key(room: RoomRow):
        """Natural sort key for room numbers (handles both numeric and alphanumeric)"""
        if not room.room_number:
            return (float('inf'), '')  # Put None/empty at end

        # Extract numeric part for sorting (e.g., "102" -> 102, "A-12" -> 12)
        import re
        numbers = re.findall(r'\d+', room.room_number)
        if numbers:
            return (int(numbers[0]), room.room_number)
        return (float('inf'), room.room_number)

    room_rows.sort(key=natural_sort_key)

    # Calculate summary
    coverage_pct = (nights_with_restaurant / total_room_nights * 100) if total_room_nights > 0 else 0.0

    # Count total bookings across all rooms
    total_bookings = sum(len(room.bookings) for room in room_rows)

    summary = {
        'total_rooms': len(room_rows),
        'total_bookings': total_bookings,
        'total_room_nights': total_room_nights,
        'nights_with_restaurant': nights_with_restaurant,
        'coverage_percentage': round(coverage_pct, 1)
    }

    # Calculate aggregated metrics for different time periods
    def get_week_start(d: date) -> date:
        """Get Monday of the week containing date d"""
        return d - timedelta(days=d.weekday())

    async def calculate_period_metrics(period_start: date, period_end: date, is_forecast: Optional[bool] = None) -> dict:
        """Calculate metrics for a specific date range"""
        query = select(NewbookDailyOccupancy).where(
            and_(
                NewbookDailyOccupancy.kitchen_id == current_user.kitchen_id,
                NewbookDailyOccupancy.date >= period_start,
                NewbookDailyOccupancy.date <= period_end,
                NewbookDailyOccupancy.rooms_breakdown.isnot(None)
            )
        )

        # Filter by forecast status if specified
        if is_forecast is not None:
            query = query.where(NewbookDailyOccupancy.is_forecast == is_forecast)

        result = await db.execute(query.order_by(NewbookDailyOccupancy.date))
        records = result.scalars().all()

        # Count metrics
        total_room_nights_period = 0
        unique_bookings = set()
        nights_with_rest = 0

        for record in records:
            rooms = record.rooms_breakdown or []
            for room in rooms:
                booking_id = room.get("booking_id")
                if booking_id:
                    unique_bookings.add(booking_id)
                    total_room_nights_period += 1

                    # Check if has restaurant booking for this date
                    date_str = record.date.isoformat()
                    resos_data = resos_lookup.get(booking_id, {}).get(date_str)
                    if resos_data:
                        nights_with_rest += 1

        coverage_pct_period = (nights_with_rest / total_room_nights_period * 100) if total_room_nights_period > 0 else 0.0

        # Calculate average occupancy
        total_available = 0
        total_occupied = 0
        for record in records:
            if record.total_rooms and record.occupied_rooms:
                total_available += record.total_rooms
                total_occupied += record.occupied_rooms

        avg_occupancy = (total_occupied / total_available * 100) if total_available > 0 else 0.0

        return {
            'total_bookings': len(unique_bookings),
            'total_room_nights': total_room_nights_period,
            'nights_with_restaurant': nights_with_rest,
            'coverage_percentage': round(coverage_pct_period, 1),
            'avg_occupancy_percentage': round(avg_occupancy, 1)
        }

    today = date.today()

    # This week (Monday to Sunday)
    this_week_start = get_week_start(today)
    this_week_end = this_week_start + timedelta(days=6)

    # Last week (previous Monday to Sunday)
    last_week_start = this_week_start - timedelta(days=7)
    last_week_end = last_week_start + timedelta(days=6)

    # Last 30 days rolling (from yesterday)
    yesterday = today - timedelta(days=1)
    rolling_30_start = yesterday - timedelta(days=29)
    rolling_30_end = yesterday

    # Calculate metrics for each period - always return metrics with default values
    default_metrics = {
        'total_bookings': 0,
        'total_room_nights': 0,
        'nights_with_restaurant': 0,
        'coverage_percentage': 0.0,
        'avg_occupancy_percentage': 0.0
    }

    try:
        metrics = {
            'this_week_actual': await calculate_period_metrics(this_week_start, this_week_end, is_forecast=False),
            'this_week_forecast': await calculate_period_metrics(this_week_start, this_week_end, is_forecast=True),
            'last_week_actual': await calculate_period_metrics(last_week_start, last_week_end, is_forecast=False),
            'last_30_days_rolling': await calculate_period_metrics(rolling_30_start, rolling_30_end, is_forecast=False),
        }
        logger.info(f"Calculated metrics: {metrics}")
    except Exception as e:
        logger.error(f"Error calculating metrics: {e}", exc_info=True)
        # Return default metrics structure instead of None
        metrics = {
            'this_week_actual': default_metrics.copy(),
            'this_week_forecast': default_metrics.copy(),
            'last_week_actual': default_metrics.copy(),
            'last_30_days_rolling': default_metrics.copy(),
        }

    return ResidentsTableChartResponse(
        date_range=date_range,
        rooms=room_rows,
        summary=summary,
        metrics=metrics
    )
