from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import Optional, List
from datetime import datetime
from decimal import Decimal
from . import repository
from .schema import (
    CancellationrequestCreate,
    CancellationrequestUpdate,
    CancellationrequestResponse,
    CancellationrequestDetailResponse,
    CancellationRequestStatus,
)
from booking_management import repository as booking_repo
from user_management import repository as user_repo


def _get_or_raise(db: Session, entity_id: str, repo) -> any:
    entity = repo.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Resource with id {entity_id} not found"
        )
    return entity


def create_cancellation_request(db: Session, data: CancellationrequestCreate) -> CancellationrequestResponse:
    # Validate booking exists
    booking = booking_repo.get_by_id(db, data.booking_id)
    if not booking:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Booking with id {data.booking_id} not found"
        )
    
    # Validate customer exists
    from user_management.models import Customer
    customer = db.query(Customer).filter(Customer.id == data.customer_id).first()
    if not customer:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Customer with id {data.customer_id} not found"
        )
    
    # Validate customer owns the booking
    if booking.customer_id != data.customer_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Customer does not own this booking"
        )
    
    # Check if booking is already cancelled
    if booking.booking_status == "cancelled":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Booking is already cancelled"
        )
    
    # Check if cancellation request already exists for this booking
    existing = db.query(repository.Cancellationrequest).filter(
        repository.Cancellationrequest.booking_id == data.booking_id,
        repository.Cancellationrequest.status.in_(["pending", "approved"])
    ).first()
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="A pending or approved cancellation request already exists for this booking"
        )
    
    # Validate refund amount does not exceed booking total
    if data.refund_amount > Decimal(str(booking.total_amount)):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Refund amount cannot exceed booking total amount"
        )
    
    try:
        cancellation_request = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(cancellation_request)
        return CancellationrequestResponse.model_validate(cancellation_request)
    except Exception:
        db.rollback()
        raise


def get_cancellation_request(db: Session, entity_id: str) -> CancellationrequestResponse:
    cancellation_request = _get_or_raise(db, entity_id, repository)
    return CancellationrequestResponse.model_validate(cancellation_request)


def list_cancellation_requests(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    status_filter: Optional[CancellationRequestStatus] = None,
    customer_id: Optional[str] = None,
    booking_id: Optional[str] = None,
) -> List[CancellationrequestResponse]:
    filters = {}
    if status_filter:
        filters["status"] = status_filter
    if customer_id:
        filters["customer_id"] = customer_id
    if booking_id:
        filters["booking_id"] = booking_id
    
    cancellation_requests = repository.list_all(db, limit, offset, **filters)
    return [CancellationrequestResponse.model_validate(cr) for cr in cancellation_requests]


def update_cancellation_request(
    db: Session, entity_id: str, data: CancellationrequestUpdate
) -> CancellationrequestResponse:
    cancellation_request = _get_or_raise(db, entity_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    # Validate booking if being updated
    if "booking_id" in update_data:
        booking = booking_repo.get_by_id(db, update_data["booking_id"])
        if not booking:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Booking with id {update_data['booking_id']} not found"
            )
    
    # Validate customer if being updated
    if "customer_id" in update_data:
        from user_management.models import Customer
        customer = db.query(Customer).filter(Customer.id == update_data["customer_id"]).first()
        if not customer:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Customer with id {update_data['customer_id']} not found"
            )
    
    # Validate processor if being updated
    if "processed_by" in update_data and update_data["processed_by"]:
        processor = user_repo.get_by_id(db, update_data["processed_by"])
        if not processor:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"User with id {update_data['processed_by']} not found"
            )
    
    # Validate refund amount if being updated
    if "refund_amount" in update_data:
        booking = booking_repo.get_by_id(db, cancellation_request.booking_id)
        if update_data["refund_amount"] > Decimal(str(booking.total_amount)):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Refund amount cannot exceed booking total amount"
            )
    
    try:
        updated = repository.update(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return CancellationrequestResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_cancellation_request(db: Session, entity_id: str) -> dict:
    cancellation_request = _get_or_raise(db, entity_id, repository)
    
    # Only allow deletion of pending requests
    if cancellation_request.status != CancellationRequestStatus.PENDING:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only pending cancellation requests can be deleted"
        )
    
    try:
        repository.delete(db, entity_id)
        db.commit()
        return {"message": "Cancellation request deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_cancellation_request_details(db: Session, entity_id: str) -> CancellationrequestDetailResponse:
    cancellation_request = repository.get_with_details(db, entity_id)
    if not cancellation_request:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Cancellation request with id {entity_id} not found"
        )
    
    response_data = {
        "id": cancellation_request.id,
        "booking_id": cancellation_request.booking_id,
        "customer_id": cancellation_request.customer_id,
        "reason": cancellation_request.reason,
        "requested_date": cancellation_request.requested_date,
        "cancellation_fee": cancellation_request.cancellation_fee,
        "refund_amount": cancellation_request.refund_amount,
        "status": cancellation_request.status,
        "processed_by": cancellation_request.processed_by,
        "processed_date": cancellation_request.processed_date,
        "admin_notes": cancellation_request.admin_notes,
        "created_at": cancellation_request.created_at,
        "updated_at": cancellation_request.updated_at,
    }
    
    if cancellation_request.booking:
        booking = cancellation_request.booking
        response_data["booking"] = {
            "booking_number": booking.booking_number,
            "booking_status": booking.booking_status,
            "total_amount": float(booking.total_amount),
            "tour_schedule": {
                "departure_date": booking.tour_schedule.departure_date.isoformat() if booking.tour_schedule else None,
                "tour_package": {
                    "name": booking.tour_schedule.tour_package.name if booking.tour_schedule and booking.tour_schedule.tour_package else None,
                } if booking.tour_schedule else None,
            } if booking.tour_schedule else None,
        }
    
    if cancellation_request.customer and cancellation_request.customer.user:
        user = cancellation_request.customer.user
        response_data["customer"] = {
            "first_name": user.first_name,
            "last_name": user.last_name,
            "email": user.email,
        }
    
    if cancellation_request.processor:
        processor = cancellation_request.processor
        response_data["processor"] = {
            "first_name": processor.first_name,
            "last_name": processor.last_name,
            "email": processor.email,
        }
    
    return CancellationrequestDetailResponse.model_validate(response_data)


def approve_cancellation_request(db: Session, entity_id: str, processed_by: str, admin_notes: Optional[str] = None) -> CancellationrequestResponse:
    cancellation_request = _get_or_raise(db, entity_id, repository)
    
    if cancellation_request.status != CancellationRequestStatus.PENDING:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only pending cancellation requests can be approved"
        )
    
    # Validate processor exists
    processor = user_repo.get_by_id(db, processed_by)
    if not processor:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {processed_by} not found"
        )
    
    try:
        update_data = {
            "status": CancellationRequestStatus.APPROVED,
            "processed_by": processed_by,
            "processed_date": datetime.utcnow(),
            "admin_notes": admin_notes,
        }
        updated = repository.update(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return CancellationrequestResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def reject_cancellation_request(db: Session, entity_id: str, processed_by: str, admin_notes: Optional[str] = None) -> CancellationrequestResponse:
    cancellation_request = _get_or_raise(db, entity_id, repository)
    
    if cancellation_request.status != CancellationRequestStatus.PENDING:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only pending cancellation requests can be rejected"
        )
    
    # Validate processor exists
    processor = user_repo.get_by_id(db, processed_by)
    if not processor:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {processed_by} not found"
        )
    
    try:
        update_data = {
            "status": CancellationRequestStatus.REJECTED,
            "processed_by": processed_by,
            "processed_date": datetime.utcnow(),
            "admin_notes": admin_notes,
        }
        updated = repository.update(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return CancellationrequestResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise