from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from . import repository
from .schema import (
    PaymentCreate, PaymentUpdate, PaymentResponse,
    InvoiceCreate, InvoiceUpdate, InvoiceResponse, InvoiceDetailResponse,
    InvoicelineitemCreate, InvoicelineitemUpdate, InvoicelineitemResponse,
    DiscountCreate, DiscountUpdate, DiscountResponse
)
from booking_management import repository as booking_repo
from tour_catalog import repository as tour_repo

def _get_or_raise(db: Session, entity_id: str, repo_module):
    entity = repo_module.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(status_code=404, detail=f"Entity with id {entity_id} not found")
    return entity

# Payment handlers
def create_payment(db: Session, data: PaymentCreate) -> PaymentResponse:
    booking = booking_repo.get_by_id(db, data.booking_id)
    if not booking:
        raise HTTPException(status_code=404, detail=f"Booking with id {data.booking_id} not found")
    
    try:
        payment = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(payment)
        return PaymentResponse.model_validate(payment)
    except Exception:
        db.rollback()
        raise

def get_payment(db: Session, payment_id: str) -> PaymentResponse:
    payment = _get_or_raise(db, payment_id, repository)
    return PaymentResponse.model_validate(payment)

def list_payments(db: Session, limit: int, offset: int, booking_id: Optional[str] = None, payment_status: Optional[str] = None, payment_method: Optional[str] = None) -> List[PaymentResponse]:
    filters = {}
    if booking_id:
        filters["booking_id"] = booking_id
    if payment_status:
        filters["payment_status"] = payment_status
    if payment_method:
        filters["payment_method"] = payment_method
    
    payments = repository.list_all(db, limit, offset, **filters)
    return [PaymentResponse.model_validate(p) for p in payments]

def update_payment(db: Session, payment_id: str, data: PaymentUpdate) -> PaymentResponse:
    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=404, detail=f"Booking with id {update_data['booking_id']} not found")
    
    try:
        payment = repository.update(db, payment_id, update_data)
        if not payment:
            raise HTTPException(status_code=404, detail=f"Payment with id {payment_id} not found")
        db.commit()
        db.refresh(payment)
        return PaymentResponse.model_validate(payment)
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

def delete_payment(db: Session, payment_id: str) -> dict:
    try:
        success = repository.delete(db, payment_id)
        if not success:
            raise HTTPException(status_code=404, detail=f"Payment with id {payment_id} not found")
        db.commit()
        return {"message": "Payment deleted successfully"}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

# Invoice handlers
def create_invoice(db: Session, data: InvoiceCreate) -> InvoiceResponse:
    booking = booking_repo.get_by_id(db, data.booking_id)
    if not booking:
        raise HTTPException(status_code=404, detail=f"Booking with id {data.booking_id} not found")
    
    existing = repository.get_invoice_by_number(db, data.invoice_number)
    if existing:
        raise HTTPException(status_code=409, detail=f"Invoice with number {data.invoice_number} already exists")
    
    try:
        invoice = repository.create_invoice(db, data.model_dump())
        db.commit()
        db.refresh(invoice)
        return InvoiceResponse.model_validate(invoice)
    except Exception:
        db.rollback()
        raise

def get_invoice(db: Session, invoice_id: str) -> InvoiceResponse:
    invoice = repository.get_invoice_by_id(db, invoice_id)
    if not invoice:
        raise HTTPException(status_code=404, detail=f"Invoice with id {invoice_id} not found")
    return InvoiceResponse.model_validate(invoice)

def get_invoice_details(db: Session, invoice_id: str) -> InvoiceDetailResponse:
    invoice = repository.get_invoice_with_details(db, invoice_id)
    if not invoice:
        raise HTTPException(status_code=404, detail=f"Invoice with id {invoice_id} not found")
    return InvoiceDetailResponse.model_validate(invoice)

def list_invoices(db: Session, limit: int, offset: int, booking_id: Optional[str] = None, invoice_status: Optional[str] = None, invoice_number: Optional[str] = None) -> List[InvoiceResponse]:
    filters = {}
    if booking_id:
        filters["booking_id"] = booking_id
    if invoice_status:
        filters["invoice_status"] = invoice_status
    if invoice_number:
        filters["invoice_number"] = invoice_number
    
    invoices = repository.list_invoices(db, limit, offset, **filters)
    return [InvoiceResponse.model_validate(i) for i in invoices]

def update_invoice(db: Session, invoice_id: str, data: InvoiceUpdate) -> InvoiceResponse:
    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=404, detail=f"Booking with id {update_data['booking_id']} not found")
    
    if "invoice_number" in update_data:
        existing = repository.get_invoice_by_number(db, update_data["invoice_number"])
        if existing and existing.id != invoice_id:
            raise HTTPException(status_code=409, detail=f"Invoice with number {update_data['invoice_number']} already exists")
    
    try:
        invoice = repository.update_invoice(db, invoice_id, update_data)
        if not invoice:
            raise HTTPException(status_code=404, detail=f"Invoice with id {invoice_id} not found")
        db.commit()
        db.refresh(invoice)
        return InvoiceResponse.model_validate(invoice)
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

def delete_invoice(db: Session, invoice_id: str) -> dict:
    try:
        success = repository.delete_invoice(db, invoice_id)
        if not success:
            raise HTTPException(status_code=404, detail=f"Invoice with id {invoice_id} not found")
        db.commit()
        return {"message": "Invoice deleted successfully"}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

# Invoice Line Item handlers
def create_line_item(db: Session, data: InvoicelineitemCreate) -> InvoicelineitemResponse:
    invoice = repository.get_invoice_by_id(db, data.invoice_id)
    if not invoice:
        raise HTTPException(status_code=404, detail=f"Invoice with id {data.invoice_id} not found")
    
    try:
        line_item = repository.create_line_item(db, data.model_dump())
        db.commit()
        db.refresh(line_item)
        return InvoicelineitemResponse.model_validate(line_item)
    except Exception:
        db.rollback()
        raise

def get_line_item(db: Session, line_item_id: str) -> InvoicelineitemResponse:
    line_item = repository.get_line_item_by_id(db, line_item_id)
    if not line_item:
        raise HTTPException(status_code=404, detail=f"Invoice line item with id {line_item_id} not found")
    return InvoicelineitemResponse.model_validate(line_item)

def list_line_items(db: Session, limit: int, offset: int, invoice_id: Optional[str] = None) -> List[InvoicelineitemResponse]:
    filters = {}
    if invoice_id:
        filters["invoice_id"] = invoice_id
    
    line_items = repository.list_line_items(db, limit, offset, **filters)
    return [InvoicelineitemResponse.model_validate(li) for li in line_items]

def update_line_item(db: Session, line_item_id: str, data: InvoicelineitemUpdate) -> InvoicelineitemResponse:
    update_data = data.model_dump(exclude_unset=True)
    
    if "invoice_id" in update_data:
        invoice = repository.get_invoice_by_id(db, update_data["invoice_id"])
        if not invoice:
            raise HTTPException(status_code=404, detail=f"Invoice with id {update_data['invoice_id']} not found")
    
    try:
        line_item = repository.update_line_item(db, line_item_id, update_data)
        if not line_item:
            raise HTTPException(status_code=404, detail=f"Invoice line item with id {line_item_id} not found")
        db.commit()
        db.refresh(line_item)
        return InvoicelineitemResponse.model_validate(line_item)
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

def delete_line_item(db: Session, line_item_id: str) -> dict:
    try:
        success = repository.delete_line_item(db, line_item_id)
        if not success:
            raise HTTPException(status_code=404, detail=f"Invoice line item with id {line_item_id} not found")
        db.commit()
        return {"message": "Invoice line item deleted successfully"}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

# Discount handlers
def create_discount(db: Session, data: DiscountCreate) -> DiscountResponse:
    existing = repository.get_discount_by_code(db, data.code)
    if existing:
        raise HTTPException(status_code=409, detail=f"Discount with code {data.code} already exists")
    
    if data.applicable_tour_package_id:
        tour_package = tour_repo.get_by_id(db, data.applicable_tour_package_id)
        if not tour_package:
            raise HTTPException(status_code=404, detail=f"Tour package with id {data.applicable_tour_package_id} not found")
    
    try:
        discount = repository.create_discount(db, data.model_dump())
        db.commit()
        db.refresh(discount)
        return DiscountResponse.model_validate(discount)
    except Exception:
        db.rollback()
        raise

def get_discount(db: Session, discount_id: str) -> DiscountResponse:
    discount = repository.get_discount_by_id(db, discount_id)
    if not discount:
        raise HTTPException(status_code=404, detail=f"Discount with id {discount_id} not found")
    return DiscountResponse.model_validate(discount)

def list_discounts(db: Session, limit: int, offset: int, is_active: Optional[bool] = None, discount_type: Optional[str] = None, code: Optional[str] = None) -> List[DiscountResponse]:
    filters = {}
    if is_active is not None:
        filters["is_active"] = is_active
    if discount_type:
        filters["discount_type"] = discount_type
    if code:
        filters["code"] = code
    
    discounts = repository.list_discounts(db, limit, offset, **filters)
    return [DiscountResponse.model_validate(d) for d in discounts]

def update_discount(db: Session, discount_id: str, data: DiscountUpdate) -> DiscountResponse:
    update_data = data.model_dump(exclude_unset=True)
    
    if "code" in update_data:
        existing = repository.get_discount_by_code(db, update_data["code"])
        if existing and existing.id != discount_id:
            raise HTTPException(status_code=409, detail=f"Discount with code {update_data['code']} already exists")
    
    if "applicable_tour_package_id" in update_data and update_data["applicable_tour_package_id"]:
        tour_package = tour_repo.get_by_id(db, update_data["applicable_tour_package_id"])
        if not tour_package:
            raise HTTPException(status_code=404, detail=f"Tour package with id {update_data['applicable_tour_package_id']} not found")
    
    try:
        discount = repository.update_discount(db, discount_id, update_data)
        if not discount:
            raise HTTPException(status_code=404, detail=f"Discount with id {discount_id} not found")
        db.commit()
        db.refresh(discount)
        return DiscountResponse.model_validate(discount)
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise

def delete_discount(db: Session, discount_id: str) -> dict:
    try:
        success = repository.delete_discount(db, discount_id)
        if not success:
            raise HTTPException(status_code=404, detail=f"Discount with id {discount_id} not found")
        db.commit()
        return {"message": "Discount deleted successfully"}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise