from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import Optional, List
from datetime import datetime
from . import repository
from .schema import (
    ScheduleCreate,
    ScheduleUpdate,
    ScheduleResponse,
    SeatCreate,
    SeatUpdate,
    SeatResponse,
    ScheduleDetailResponse,
    GenerateSeatsResponse,
    SeatStatus,
)


def _get_schedule_or_raise(db: Session, schedule_id: str):
    schedule = repository.get_schedule_by_id(db, schedule_id)
    if not schedule:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Schedule with id {schedule_id} not found",
        )
    return schedule


def _get_seat_or_raise(db: Session, seat_id: str):
    seat = repository.get_seat_by_id(db, seat_id)
    if not seat:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Seat with id {seat_id} not found",
        )
    return seat


def create_schedule(db: Session, data: ScheduleCreate) -> ScheduleResponse:
    bus = repository.get_bus_by_id(db, data.bus_id)
    if not bus:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Bus with id {data.bus_id} not found",
        )
    if not bus.is_active:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot create schedule for inactive bus",
        )

    route = repository.get_route_by_id(db, data.route_id)
    if not route:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Route with id {data.route_id} not found",
        )
    if not route.is_active:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot create schedule for inactive route",
        )

    if data.departure_datetime <= datetime.utcnow():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Schedule departure time must be in the future",
        )

    if data.arrival_datetime <= data.departure_datetime:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Arrival time must be after departure time",
        )

    schedule_data = data.model_dump()
    schedule = repository.create_schedule(db, schedule_data)
    db.commit()
    db.refresh(schedule)
    return ScheduleResponse.model_validate(schedule)


def get_schedule(db: Session, schedule_id: str) -> ScheduleResponse:
    schedule = _get_schedule_or_raise(db, schedule_id)
    return ScheduleResponse.model_validate(schedule)


def list_schedules(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    route_id: Optional[str] = None,
    status: Optional[str] = None,
) -> List[ScheduleResponse]:
    schedules = repository.list_schedules(db, limit, offset, route_id, status)
    return [ScheduleResponse.model_validate(s) for s in schedules]


def update_schedule(db: Session, schedule_id: str, data: ScheduleUpdate) -> ScheduleResponse:
    schedule = _get_schedule_or_raise(db, schedule_id)

    update_data = data.model_dump(exclude_unset=True)

    if "bus_id" in update_data:
        bus = repository.get_bus_by_id(db, update_data["bus_id"])
        if not bus:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Bus with id {update_data['bus_id']} not found",
            )
        if not bus.is_active:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot assign inactive bus to schedule",
            )

    if "route_id" in update_data:
        route = repository.get_route_by_id(db, update_data["route_id"])
        if not route:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Route with id {update_data['route_id']} not found",
            )
        if not route.is_active:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot assign inactive route to schedule",
            )

    if "departure_datetime" in update_data or "arrival_datetime" in update_data:
        departure = update_data.get("departure_datetime", schedule.departure_datetime)
        arrival = update_data.get("arrival_datetime", schedule.arrival_datetime)
        if arrival <= departure:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Arrival time must be after departure time",
            )

    updated_schedule = repository.update_schedule(db, schedule_id, update_data)
    db.commit()
    db.refresh(updated_schedule)
    return ScheduleResponse.model_validate(updated_schedule)


def delete_schedule(db: Session, schedule_id: str) -> dict:
    schedule = _get_schedule_or_raise(db, schedule_id)
    if schedule.status in ["in_transit", "completed"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete schedule that is in transit or completed",
        )
    repository.delete_schedule(db, schedule_id)
    db.commit()
    return {"message": "Schedule deleted successfully"}


def get_schedule_details(db: Session, schedule_id: str) -> ScheduleDetailResponse:
    schedule = repository.get_schedule_with_details(db, schedule_id)
    if not schedule:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Schedule with id {schedule_id} not found",
        )
    return ScheduleDetailResponse.model_validate(schedule)


def generate_seats_for_schedule(db: Session, schedule_id: str) -> GenerateSeatsResponse:
    schedule = repository.get_schedule_with_details(db, schedule_id)
    if not schedule:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Schedule with id {schedule_id} not found",
        )

    existing_seats = repository.get_seats_by_schedule(db, schedule_id)
    if existing_seats:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Seats have already been generated for this schedule",
        )

    bus = schedule.bus
    if not bus:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Bus information not found for schedule",
        )

    seat_layout_config = bus.seat_layout_config
    if not seat_layout_config or not isinstance(seat_layout_config, dict):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid seat layout configuration for bus",
        )

    seats_to_create = []
    seat_count = 0

    rows = seat_layout_config.get("rows", [])
    for row in rows:
        row_number = row.get("row_number")
        seats_in_row = row.get("seats", [])
        for seat_info in seats_in_row:
            seat_number = seat_info.get("seat_number")
            seat_type = seat_info.get("seat_type", "standard")
            price_modifier = seat_info.get("price_modifier", 0.0)

            if not seat_number:
                continue

            seat_data = {
                "schedule_id": schedule_id,
                "seat_number": seat_number,
                "seat_type": seat_type,
                "price_modifier": price_modifier,
                "status": SeatStatus.AVAILABLE.value,
            }
            seats_to_create.append(seat_data)
            seat_count += 1

    if seat_count == 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="No valid seats found in bus layout configuration",
        )

    for seat_data in seats_to_create:
        repository.create_seat(db, seat_data)

    schedule.available_seats = seat_count
    db.commit()

    return GenerateSeatsResponse(
        schedule_id=schedule_id,
        seats_generated=seat_count,
        message=f"Successfully generated {seat_count} seats for schedule",
    )


def search_schedules(
    db: Session,
    origin_city: Optional[str] = None,
    destination_city: Optional[str] = None,
    departure_date: Optional[str] = None,
    limit: int = 20,
    offset: int = 0,
) -> List[ScheduleResponse]:
    schedules = repository.search_schedules(
        db, origin_city, destination_city, departure_date, limit, offset
    )
    return [ScheduleResponse.model_validate(s) for s in schedules]


def get_schedule_seats(db: Session, schedule_id: str) -> List[SeatResponse]:
    schedule = _get_schedule_or_raise(db, schedule_id)
    seats = repository.get_seats_by_schedule(db, schedule_id)
    return [SeatResponse.model_validate(s) for s in seats]


def create_seat(db: Session, data: SeatCreate) -> SeatResponse:
    schedule = repository.get_schedule_by_id(db, data.schedule_id)
    if not schedule:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Schedule with id {data.schedule_id} not found",
        )

    existing_seats = repository.get_seats_by_schedule(db, data.schedule_id)
    seat_numbers = [s.seat_number for s in existing_seats]
    if data.seat_number in seat_numbers:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Seat number {data.seat_number} already exists for this schedule",
        )

    seat_data = data.model_dump()
    seat = repository.create_seat(db, seat_data)
    db.commit()
    db.refresh(seat)
    return SeatResponse.model_validate(seat)


def get_seat(db: Session, seat_id: str) -> SeatResponse:
    seat = _get_seat_or_raise(db, seat_id)
    return SeatResponse.model_validate(seat)


def list_seats(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    schedule_id: Optional[str] = None,
    status: Optional[str] = None,
) -> List[SeatResponse]:
    seats = repository.list_seats(db, limit, offset, schedule_id, status)
    return [SeatResponse.model_validate(s) for s in seats]


def update_seat(db: Session, seat_id: str, data: SeatUpdate) -> SeatResponse:
    seat = _get_seat_or_raise(db, seat_id)

    update_data = data.model_dump(exclude_unset=True)

    if "schedule_id" in update_data:
        schedule = repository.get_schedule_by_id(db, update_data["schedule_id"])
        if not schedule:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Schedule with id {update_data['schedule_id']} not found",
            )

    if "seat_number" in update_data and update_data["seat_number"] != seat.seat_number:
        schedule_id = update_data.get("schedule_id", seat.schedule_id)
        existing_seats = repository.get_seats_by_schedule(db, schedule_id)
        seat_numbers = [s.seat_number for s in existing_seats if s.id != seat_id]
        if update_data["seat_number"] in seat_numbers:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Seat number {update_data['seat_number']} already exists for this schedule",
            )

    updated_seat = repository.update_seat(db, seat_id, update_data)
    db.commit()
    db.refresh(updated_seat)
    return SeatResponse.model_validate(updated_seat)


def delete_seat(db: Session, seat_id: str) -> dict:
    seat = _get_seat_or_raise(db, seat_id)
    if seat.status != SeatStatus.AVAILABLE.value:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete seat that is reserved or booked",
        )
    repository.delete_seat(db, seat_id)
    db.commit()
    return {"message": "Seat deleted successfully"}