from datetime import date
from decimal import Decimal
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.api.workspace import get_verified_current_user
from app.db.session import get_db
from app.models.purchase import Purchase, PurchaseItem, PurchasePayment
from app.models.supplier import Supplier
from app.models.user import User
from app.schemas.purchase import PurchaseCreate, PurchasePaymentCreate, PurchaseUpdate
from app.services.purchases import cancel_purchase, create_purchase, get_purchase, record_payment, receive_purchase, update_purchase
from app.services.workspace import get_membership_for_business
router=APIRouter(tags=["Purchases"])
def access(db,user,business_id):
 if not get_membership_for_business(db,user,business_id): raise HTTPException(404,"Business workspace not found.")
def run(db,fn):
 try:return fn()
 except LookupError as e: raise HTTPException(404,str(e))
 except ValueError as e: db.rollback();raise HTTPException(422,str(e))
 except IntegrityError: db.rollback();raise HTTPException(409,"Duplicate purchase reference for this business.")
def detail(db,p): return {"purchase":p,"items":list(db.scalars(select(PurchaseItem).where(PurchaseItem.purchase_id==p.id)))}
@router.post('/api/businesses/{business_id}/purchases',status_code=201)
def create(business_id:UUID,data:PurchaseCreate,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);return run(db,lambda:detail(db,create_purchase(db,business_id,user.id,data)))
@router.get('/api/businesses/{business_id}/purchases')
def listing(business_id:UUID,supplier_id:UUID|None=None,branch_id:UUID|None=None,lifecycle_status:str|None=None,payment_status:str|None=None,date_from:date|None=None,date_to:date|None=None,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);q=select(Purchase).where(Purchase.business_id==business_id)
 for col,val in ((Purchase.supplier_id,supplier_id),(Purchase.branch_id,branch_id),(Purchase.lifecycle_status,lifecycle_status),(Purchase.payment_status,payment_status)):
  if val is not None:q=q.where(col==val)
 if date_from:q=q.where(Purchase.purchase_date>=date_from)
 if date_to:q=q.where(Purchase.purchase_date<=date_to)
 return list(db.scalars(q.order_by(Purchase.created_at.desc())))
@router.get('/api/businesses/{business_id}/purchases/{purchase_id}')
def get(business_id:UUID,purchase_id:UUID,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);return run(db,lambda:detail(db,get_purchase(db,business_id,purchase_id)))
@router.patch('/api/businesses/{business_id}/purchases/{purchase_id}')
def patch(business_id:UUID,purchase_id:UUID,data:PurchaseUpdate,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);return run(db,lambda:detail(db,update_purchase(db,business_id,purchase_id,data)))
@router.post('/api/businesses/{business_id}/purchases/{purchase_id}/receive')
def receive(business_id:UUID,purchase_id:UUID,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);return run(db,lambda:detail(db,receive_purchase(db,business_id,user.id,purchase_id)))
@router.post('/api/businesses/{business_id}/purchases/{purchase_id}/payments',status_code=201)
def payment(business_id:UUID,purchase_id:UUID,data:PurchasePaymentCreate,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);return run(db,lambda:record_payment(db,business_id,user.id,purchase_id,data))
@router.get('/api/businesses/{business_id}/purchases/{purchase_id}/payments')
def payments(business_id:UUID,purchase_id:UUID,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);run(db,lambda:get_purchase(db,business_id,purchase_id));return list(db.scalars(select(PurchasePayment).where(PurchasePayment.purchase_id==purchase_id).order_by(PurchasePayment.paid_at)))
@router.post('/api/businesses/{business_id}/purchases/{purchase_id}/cancel')
def cancel(business_id:UUID,purchase_id:UUID,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id);return run(db,lambda:cancel_purchase(db,business_id,purchase_id))
@router.get('/api/businesses/{business_id}/suppliers/{supplier_id}/payable')
def supplier_payable(business_id:UUID,supplier_id:UUID,user:User=Depends(get_verified_current_user),db:Session=Depends(get_db)):
 access(db,user,business_id)
 supplier=db.get(Supplier,supplier_id)
 if not supplier or supplier.business_id!=business_id: raise HTTPException(404,"Supplier not found.")
 # The ledger, not a mutable supplier aggregate, is the source of truth.
 purchases=select(Purchase.id,Purchase.total).where(Purchase.business_id==business_id,Purchase.supplier_id==supplier_id,Purchase.lifecycle_status=="RECEIVED").subquery()
 total=db.scalar(select(func.coalesce(func.sum(purchases.c.total),0)))
 paid=db.scalar(select(func.coalesce(func.sum(PurchasePayment.amount),0)).where(PurchasePayment.business_id==business_id,PurchasePayment.supplier_id==supplier_id,PurchasePayment.purchase_id.in_(select(purchases.c.id))))
 return {"supplier_id":supplier_id,"received_total":total,"amount_paid":paid,"payable":total-paid}



@router.get("/api/businesses/{business_id}/payables")
def list_payables(
    business_id: UUID,
    supplier_id: UUID | None = None,
    branch_id: UUID | None = None,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    query = (
        select(Purchase, Supplier)
        .join(Supplier, Supplier.id == Purchase.supplier_id)
        .where(
            Purchase.business_id == business_id,
            Purchase.lifecycle_status == "RECEIVED",
            Purchase.balance > 0,
        )
    )

    if supplier_id:
        query = query.where(Purchase.supplier_id == supplier_id)

    if branch_id:
        query = query.where(Purchase.branch_id == branch_id)

    query = query.order_by(
        Purchase.purchase_date.desc(),
        Purchase.created_at.desc(),
    )

    return [
        {
            "id": purchase.id,
            "supplier_id": purchase.supplier_id,
            "supplier_name": supplier.name,
            "branch_id": purchase.branch_id,
            "reference_number": purchase.reference_number,
            "purchase_date": purchase.purchase_date,
            "total": purchase.total,
            "amount_paid": purchase.amount_paid,
            "balance": purchase.balance,
            "payment_status": purchase.payment_status,
            "received_at": purchase.received_at,
            "created_at": purchase.created_at,
        }
        for purchase, supplier in db.execute(query)
    ]


@router.get(
    "/api/businesses/{business_id}/suppliers/{supplier_id}/statement"
)
def supplier_statement(
    business_id: UUID,
    supplier_id: UUID,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    supplier = db.get(Supplier, supplier_id)

    if not supplier or supplier.business_id != business_id:
        raise HTTPException(404, "Supplier not found.")

    purchases = list(
        db.scalars(
            select(Purchase)
            .where(
                Purchase.business_id == business_id,
                Purchase.supplier_id == supplier_id,
                Purchase.lifecycle_status == "RECEIVED",
            )
            .order_by(
                Purchase.purchase_date,
                Purchase.created_at,
            )
        )
    )

    purchase_ids = [purchase.id for purchase in purchases]

    payments = (
        []
        if not purchase_ids
        else list(
            db.scalars(
                select(PurchasePayment)
                .where(
                    PurchasePayment.business_id == business_id,
                    PurchasePayment.supplier_id == supplier_id,
                    PurchasePayment.purchase_id.in_(purchase_ids),
                )
                .order_by(PurchasePayment.paid_at)
            )
        )
    )

    purchase_by_id = {
        purchase.id: purchase
        for purchase in purchases
    }

    events = []

    for purchase in purchases:
        events.append(
            {
                "event_type": "PURCHASE_RECEIVED",
                "occurred_at": (
                    purchase.received_at
                    or purchase.created_at
                ),
                "purchase_id": purchase.id,
                "reference_number": purchase.reference_number,
                "amount": purchase.total,
                "payment_method": None,
                "reference": None,
                "_delta": purchase.total,
            }
        )

    for payment in payments:
        purchase = purchase_by_id.get(payment.purchase_id)

        events.append(
            {
                "event_type": "PAYMENT",
                "occurred_at": payment.paid_at,
                "purchase_id": payment.purchase_id,
                "reference_number": (
                    purchase.reference_number
                    if purchase
                    else None
                ),
                "amount": payment.amount,
                "payment_method": payment.payment_method,
                "reference": payment.reference,
                "_delta": -payment.amount,
            }
        )

    events.sort(
        key=lambda event: (
            event["occurred_at"],
            0
            if event["event_type"] == "PURCHASE_RECEIVED"
            else 1,
        )
    )

    running = Decimal("0")

    for event in events:
        running += event.pop("_delta")
        event["running_balance"] = running

    current_payable = sum(
        (purchase.balance for purchase in purchases),
        Decimal("0"),
    )

    return {
        "supplier": {
            "id": supplier.id,
            "name": supplier.name,
            "phone": supplier.phone,
            "email": supplier.email,
        },
        "current_payable": current_payable,
        "received_purchase_count": len(purchases),
        "entries": events,
    }
