from sqlalchemy.orm import Session, joinedload, subqueryload
from typing import Optional, List
from .models import Service, Invoice, InvoiceLineItem, Payment


# Service repository functions
def get_by_id(db: Session, entity_id: str) -> Optional[Service]:
    return db.query(Service).filter(Service.id == entity_id).first()


def list_all(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Service]:
    q = db.query(Service)
    if filters.get("is_active") is not None:
        q = q.filter(Service.is_active == filters["is_active"])
    if filters.get("service_type") is not None:
        q = q.filter(Service.service_type == filters["service_type"])
    if filters.get("department_id") is not None:
        q = q.filter(Service.department_id == filters["department_id"])
    return q.order_by(Service.created_at.desc()).limit(limit).offset(offset).all()


def count_all(db: Session, **filters) -> int:
    q = db.query(Service.id)
    if filters.get("is_active") is not None:
        q = q.filter(Service.is_active == filters["is_active"])
    if filters.get("service_type") is not None:
        q = q.filter(Service.service_type == filters["service_type"])
    if filters.get("department_id") is not None:
        q = q.filter(Service.department_id == filters["department_id"])
    return q.count()


def create(db: Session, data: dict) -> Service:
    obj = Service(**data)
    db.add(obj)
    db.flush()
    return obj


def update(db: Session, entity_id: str, data: dict) -> Optional[Service]:
    obj = get_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def delete(db: Session, entity_id: str) -> bool:
    obj = get_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True


# Invoice repository functions - using Invoice prefix for namespacing
def invoice_get_by_id(db: Session, entity_id: str) -> Optional[Invoice]:
    return db.query(Invoice).filter(Invoice.id == entity_id).first()


def invoice_get_with_details(db: Session, entity_id: str) -> Optional[Invoice]:
    return (
        db.query(Invoice)
        .options(
            joinedload(Invoice.patient),
            joinedload(Invoice.appointment),
            joinedload(Invoice.line_items).joinedload(InvoiceLineItem.service),
            subqueryload(Invoice.payments),
        )
        .filter(Invoice.id == entity_id)
        .first()
    )


def invoice_list_all(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Invoice]:
    q = db.query(Invoice)
    if filters.get("status") is not None:
        q = q.filter(Invoice.status == filters["status"])
    if filters.get("patient_id") is not None:
        q = q.filter(Invoice.patient_id == filters["patient_id"])
    if filters.get("appointment_id") is not None:
        q = q.filter(Invoice.appointment_id == filters["appointment_id"])
    return q.order_by(Invoice.created_at.desc()).limit(limit).offset(offset).all()


def invoice_count_all(db: Session, **filters) -> int:
    q = db.query(Invoice.id)
    if filters.get("status") is not None:
        q = q.filter(Invoice.status == filters["status"])
    if filters.get("patient_id") is not None:
        q = q.filter(Invoice.patient_id == filters["patient_id"])
    if filters.get("appointment_id") is not None:
        q = q.filter(Invoice.appointment_id == filters["appointment_id"])
    return q.count()


def invoice_create(db: Session, data: dict) -> Invoice:
    obj = Invoice(**data)
    db.add(obj)
    db.flush()
    return obj


def invoice_update(db: Session, entity_id: str, data: dict) -> Optional[Invoice]:
    obj = invoice_get_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def invoice_delete(db: Session, entity_id: str) -> bool:
    obj = invoice_get_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True


# InvoiceLineItem repository functions - using lineitem prefix for namespacing
def lineitem_get_by_id(db: Session, entity_id: str) -> Optional[InvoiceLineItem]:
    return db.query(InvoiceLineItem).filter(InvoiceLineItem.id == entity_id).first()


def lineitem_list_all(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[InvoiceLineItem]:
    q = db.query(InvoiceLineItem)
    if filters.get("invoice_id") is not None:
        q = q.filter(InvoiceLineItem.invoice_id == filters["invoice_id"])
    if filters.get("service_id") is not None:
        q = q.filter(InvoiceLineItem.service_id == filters["service_id"])
    return q.order_by(InvoiceLineItem.created_at.desc()).limit(limit).offset(offset).all()


def lineitem_count_all(db: Session, **filters) -> int:
    q = db.query(InvoiceLineItem.id)
    if filters.get("invoice_id") is not None:
        q = q.filter(InvoiceLineItem.invoice_id == filters["invoice_id"])
    if filters.get("service_id") is not None:
        q = q.filter(InvoiceLineItem.service_id == filters["service_id"])
    return q.count()


def lineitem_create(db: Session, data: dict) -> InvoiceLineItem:
    obj = InvoiceLineItem(**data)
    db.add(obj)
    db.flush()
    return obj


def lineitem_update(db: Session, entity_id: str, data: dict) -> Optional[InvoiceLineItem]:
    obj = lineitem_get_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def lineitem_delete(db: Session, entity_id: str) -> bool:
    obj = lineitem_get_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True


# Payment repository functions - using payment prefix for namespacing
def payment_get_by_id(db: Session, entity_id: str) -> Optional[Payment]:
    return db.query(Payment).filter(Payment.id == entity_id).first()


def payment_list_all(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Payment]:
    q = db.query(Payment)
    if filters.get("invoice_id") is not None:
        q = q.filter(Payment.invoice_id == filters["invoice_id"])
    if filters.get("patient_id") is not None:
        q = q.filter(Payment.patient_id == filters["patient_id"])
    if filters.get("payment_method") is not None:
        q = q.filter(Payment.payment_method == filters["payment_method"])
    return q.order_by(Payment.created_at.desc()).limit(limit).offset(offset).all()


def payment_count_all(db: Session, **filters) -> int:
    q = db.query(Payment.id)
    if filters.get("invoice_id") is not None:
        q = q.filter(Payment.invoice_id == filters["invoice_id"])
    if filters.get("patient_id") is not None:
        q = q.filter(Payment.patient_id == filters["patient_id"])
    if filters.get("payment_method") is not None:
        q = q.filter(Payment.payment_method == filters["payment_method"])
    return q.count()


def payment_create(db: Session, data: dict) -> Payment:
    obj = Payment(**data)
    db.add(obj)
    db.flush()
    return obj


def payment_update(db: Session, entity_id: str, data: dict) -> Optional[Payment]:
    obj = payment_get_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def payment_delete(db: Session, entity_id: str) -> bool:
    obj = payment_get_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True