from datetime import date, datetime, timezone
from decimal import Decimal
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.customer import Customer
from app.models.receivable import Receivable, ReceivableCollection
from app.models.sale import Sale, SalePayment
from app.services.audit import record_audit

METHODS = {"CASH", "POS", "TRANSFER", "OTHER"}


def aging_bucket(receivable: Receivable, today: date | None = None) -> str:
    if receivable.balance <= 0:
        return "CURRENT"
    today = today or datetime.now(timezone.utc).date()
    anchor = receivable.due_date or receivable.created_at.date()
    days = (today - anchor).days
    if days <= 0: return "CURRENT"
    if days <= 30: return "1_30"
    if days <= 60: return "31_60"
    if days <= 90: return "61_90"
    return "90_PLUS"


def get_receivable(db: Session, business_id, receivable_id, lock=False) -> Receivable:
    stmt = select(Receivable).where(Receivable.id == receivable_id, Receivable.business_id == business_id)
    if lock: stmt = stmt.with_for_update()
    item = db.scalar(stmt)
    if not item: raise LookupError("Receivable not found.")
    return item


def collect(db: Session, business_id, receivable_id, user_id, data):
    if data.payment_method not in METHODS: raise ValueError("Invalid payment method.")
    receivable = get_receivable(db, business_id, receivable_id, lock=True)
    customer = db.get(Customer, receivable.customer_id)
    if not customer or customer.business_id != business_id: raise ValueError("Invalid receivable customer.")
    if receivable.balance <= 0 or receivable.status == "PAID": raise ValueError("Receivable is already paid.")
    if data.amount > receivable.balance: raise ValueError("Collection exceeds receivable balance.")
    sale = None
    if receivable.sale_id:
        sale = db.scalar(select(Sale).where(Sale.id == receivable.sale_id, Sale.business_id == business_id).with_for_update())
        if not sale or sale.customer_id != receivable.customer_id: raise ValueError("Linked sale is invalid.")
        if data.amount > sale.balance: raise ValueError("Collection exceeds sale balance.")
    collection = ReceivableCollection(business_id=business_id, receivable_id=receivable.id, customer_id=receivable.customer_id, amount=data.amount, payment_method=data.payment_method, reference=data.reference, collected_by=user_id)
    db.add(collection)
    receivable.amount_paid += data.amount; receivable.balance -= data.amount
    receivable.status = "PAID" if receivable.balance == 0 else "PARTIALLY_PAID"
    if sale:
        sale.amount_paid += data.amount; sale.balance -= data.amount
        sale.payment_status = "PAID" if sale.balance == 0 else "PARTIALLY_PAID"
        db.add(SalePayment(business_id=business_id, sale_id=sale.id, amount=data.amount, payment_method=data.payment_method, reference=data.reference, recorded_by=user_id))
    db.flush()
    record_audit(db, business_id, "RECEIVABLE_COLLECTION_CREATED", "receivable", receivable.id, user_id, None, {
        "collection_id": str(collection.id),
        "customer_id": str(receivable.customer_id),
        "amount": str(data.amount),
        "payment_method": data.payment_method,
        "remaining_balance": str(receivable.balance),
    })
    db.commit(); db.refresh(collection); db.refresh(receivable)
    return collection, receivable


def statement(db: Session, business_id, customer_id):
    items = list(
        db.scalars(
            select(Receivable)
            .where(
                Receivable.business_id == business_id,
                Receivable.customer_id == customer_id,
            )
            .order_by(Receivable.created_at)
        )
    )

    events = []

    for receivable in items:
        events.append(
            {
                "event_type": "CREDIT_SALE",
                "occurred_at": receivable.created_at,
                "receivable_id": receivable.id,
                "sale_id": receivable.sale_id,
                "amount": receivable.original_amount,
                "reference": None,
            }
        )

        for collection in db.scalars(
            select(ReceivableCollection)
            .where(
                ReceivableCollection.receivable_id
                == receivable.id
            )
            .order_by(
                ReceivableCollection.collected_at
            )
        ):
            events.append(
                {
                    "event_type": "COLLECTION",
                    "occurred_at": collection.collected_at,
                    "receivable_id": receivable.id,
                    "sale_id": receivable.sale_id,
                    "amount": collection.amount,
                    "reference": collection.reference,
                }
            )

    events.sort(key=lambda item: item["occurred_at"])

    running = ZERO = Decimal("0")
    output = []

    for event in events:
        if event["event_type"] == "CREDIT_SALE":
            running += event["amount"]
        else:
            running -= event["amount"]

        output.append(
            {
                **event,
                "remaining_balance": running,
            }
        )

    return output
