from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from . import repository
from .schema import (
    ExpenseCreate,
    ExpenseUpdate,
    ExpenseResponse,
    CommissionCreate,
    CommissionUpdate,
    CommissionResponse,
    ExpenseStatus,
    CommissionStatus,
)
from datetime import datetime
from user_management import repository as user_repo
from booking_management import repository as booking_repo


def _get_or_raise(db: Session, entity_id: str, repo_func, entity_name: str):
    entity = repo_func(db, entity_id)
    if not entity:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"{entity_name} with id {entity_id} not found",
        )
    return entity


def create_expense(db: Session, data: ExpenseCreate) -> ExpenseResponse:
    submitter = user_repo.get_by_id(db, data.submitted_by)
    if not submitter:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {data.submitted_by} not found",
        )
    
    if data.tour_schedule_id:
        from booking_management.models import Tourschedule
        tour_schedule = db.query(Tourschedule).filter(Tourschedule.id == data.tour_schedule_id).first()
        if not tour_schedule:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour schedule with id {data.tour_schedule_id} not found",
            )
    
    try:
        expense_data = data.model_dump()
        expense_data["status"] = ExpenseStatus.PENDING.value
        expense = repository.create_expense(db, expense_data)
        db.commit()
        db.refresh(expense)
        return ExpenseResponse.model_validate(expense)
    except Exception:
        db.rollback()
        raise


def get_expense(db: Session, expense_id: str) -> ExpenseResponse:
    expense = _get_or_raise(db, expense_id, repository.get_expense_by_id, "Expense")
    return ExpenseResponse.model_validate(expense)


def list_expenses(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    status_filter: Optional[ExpenseStatus] = None,
    tour_schedule_id: Optional[str] = None,
    submitted_by: Optional[str] = None,
) -> List[ExpenseResponse]:
    filters = {}
    if status_filter:
        filters["status"] = status_filter.value
    if tour_schedule_id:
        filters["tour_schedule_id"] = tour_schedule_id
    if submitted_by:
        filters["submitted_by"] = submitted_by
    
    expenses = repository.list_expenses(db, limit, offset, **filters)
    return [ExpenseResponse.model_validate(e) for e in expenses]


def update_expense(db: Session, expense_id: str, data: ExpenseUpdate) -> ExpenseResponse:
    expense = _get_or_raise(db, expense_id, repository.get_expense_by_id, "Expense")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "submitted_by" in update_data:
        submitter = user_repo.get_by_id(db, update_data["submitted_by"])
        if not submitter:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"User with id {update_data['submitted_by']} not found",
            )
    
    if "approved_by" in update_data and update_data["approved_by"]:
        approver = user_repo.get_by_id(db, update_data["approved_by"])
        if not approver:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"User with id {update_data['approved_by']} not found",
            )
    
    if "tour_schedule_id" in update_data and update_data["tour_schedule_id"]:
        from booking_management.models import Tourschedule
        tour_schedule = db.query(Tourschedule).filter(Tourschedule.id == update_data["tour_schedule_id"]).first()
        if not tour_schedule:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Tour schedule with id {update_data['tour_schedule_id']} not found",
            )
    
    try:
        updated_expense = repository.update_expense(db, expense_id, update_data)
        db.commit()
        db.refresh(updated_expense)
        return ExpenseResponse.model_validate(updated_expense)
    except Exception:
        db.rollback()
        raise


def delete_expense(db: Session, expense_id: str) -> dict:
    expense = _get_or_raise(db, expense_id, repository.get_expense_by_id, "Expense")
    
    try:
        repository.delete_expense(db, expense_id)
        db.commit()
        return {"message": "Expense deleted successfully"}
    except Exception:
        db.rollback()
        raise


def approve_expense(db: Session, expense_id: str, approved_by: str) -> ExpenseResponse:
    expense = _get_or_raise(db, expense_id, repository.get_expense_by_id, "Expense")
    
    if expense.status != ExpenseStatus.PENDING.value:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot approve expense with status {expense.status}",
        )
    
    approver = user_repo.get_by_id(db, approved_by)
    if not approver:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {approved_by} not found",
        )
    
    try:
        update_data = {
            "status": ExpenseStatus.APPROVED.value,
            "approved_by": approved_by,
            "approval_date": datetime.utcnow(),
        }
        updated_expense = repository.update_expense(db, expense_id, update_data)
        db.commit()
        db.refresh(updated_expense)
        return ExpenseResponse.model_validate(updated_expense)
    except Exception:
        db.rollback()
        raise


def reject_expense(db: Session, expense_id: str, rejected_by: str, notes: Optional[str] = None) -> ExpenseResponse:
    expense = _get_or_raise(db, expense_id, repository.get_expense_by_id, "Expense")
    
    if expense.status != ExpenseStatus.PENDING.value:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot reject expense with status {expense.status}",
        )
    
    rejector = user_repo.get_by_id(db, rejected_by)
    if not rejector:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {rejected_by} not found",
        )
    
    try:
        update_data = {
            "status": ExpenseStatus.REJECTED.value,
            "approved_by": rejected_by,
            "approval_date": datetime.utcnow(),
        }
        if notes:
            update_data["notes"] = notes
        
        updated_expense = repository.update_expense(db, expense_id, update_data)
        db.commit()
        db.refresh(updated_expense)
        return ExpenseResponse.model_validate(updated_expense)
    except Exception:
        db.rollback()
        raise


def create_commission(db: Session, data: CommissionCreate) -> CommissionResponse:
    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",
        )
    
    agent = user_repo.get_by_id(db, data.booking_agent_id)
    if not agent:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {data.booking_agent_id} not found",
        )
    
    try:
        commission_data = data.model_dump()
        commission_data["status"] = CommissionStatus.PENDING.value
        commission = repository.create_commission(db, commission_data)
        db.commit()
        db.refresh(commission)
        return CommissionResponse.model_validate(commission)
    except Exception:
        db.rollback()
        raise


def get_commission(db: Session, commission_id: str) -> CommissionResponse:
    commission = _get_or_raise(db, commission_id, repository.get_commission_by_id, "Commission")
    return CommissionResponse.model_validate(commission)


def list_commissions(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    status_filter: Optional[CommissionStatus] = None,
    booking_agent_id: Optional[str] = None,
) -> List[CommissionResponse]:
    filters = {}
    if status_filter:
        filters["status"] = status_filter.value
    if booking_agent_id:
        filters["booking_agent_id"] = booking_agent_id
    
    commissions = repository.list_commissions(db, limit, offset, **filters)
    return [CommissionResponse.model_validate(c) for c in commissions]


def update_commission(db: Session, commission_id: str, data: CommissionUpdate) -> CommissionResponse:
    commission = _get_or_raise(db, commission_id, repository.get_commission_by_id, "Commission")
    
    update_data = data.model_dump(exclude_unset=True)
    
    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",
            )
    
    if "booking_agent_id" in update_data:
        agent = user_repo.get_by_id(db, update_data["booking_agent_id"])
        if not agent:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"User with id {update_data['booking_agent_id']} not found",
            )
    
    try:
        updated_commission = repository.update_commission(db, commission_id, update_data)
        db.commit()
        db.refresh(updated_commission)
        return CommissionResponse.model_validate(updated_commission)
    except Exception:
        db.rollback()
        raise


def delete_commission(db: Session, commission_id: str) -> dict:
    commission = _get_or_raise(db, commission_id, repository.get_commission_by_id, "Commission")
    
    try:
        repository.delete_commission(db, commission_id)
        db.commit()
        return {"message": "Commission deleted successfully"}
    except Exception:
        db.rollback()
        raise


def approve_commission(db: Session, commission_id: str) -> CommissionResponse:
    commission = _get_or_raise(db, commission_id, repository.get_commission_by_id, "Commission")
    
    if commission.status != CommissionStatus.PENDING.value:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot approve commission with status {commission.status}",
        )
    
    try:
        update_data = {"status": CommissionStatus.APPROVED.value}
        updated_commission = repository.update_commission(db, commission_id, update_data)
        db.commit()
        db.refresh(updated_commission)
        return CommissionResponse.model_validate(updated_commission)
    except Exception:
        db.rollback()
        raise


def pay_commission(db: Session, commission_id: str, payment_date: datetime) -> CommissionResponse:
    commission = _get_or_raise(db, commission_id, repository.get_commission_by_id, "Commission")
    
    if commission.status != CommissionStatus.APPROVED.value:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot pay commission with status {commission.status}. Commission must be approved first.",
        )
    
    try:
        update_data = {
            "status": CommissionStatus.PAID.value,
            "payment_date": payment_date.date() if isinstance(payment_date, datetime) else payment_date,
        }
        updated_commission = repository.update_commission(db, commission_id, update_data)
        db.commit()
        db.refresh(updated_commission)
        return CommissionResponse.model_validate(updated_commission)
    except Exception:
        db.rollback()
        raise