import uuid
from datetime import date
from decimal import Decimal
from sqlalchemy import select
from app.models.customer import Customer
from app.models.invoice import Invoice
from app.models.product import Product, ProductVariant
from app.models.quote import Quote, QuoteItem
from app.schemas.invoice import InvoiceCreate, InvoiceItemCreate
from app.services.invoices import create_invoice
from app.services.products import require_branch
from app.services.audit import record_audit

EDITABLE = {"DRAFT"}

def get_quote(db, business_id, quote_id, lock=False):
    q = select(Quote).where(Quote.id == quote_id, Quote.business_id == business_id)
    if lock: q = q.with_for_update()
    quote = db.scalar(q)
    if not quote: raise LookupError("Quote not found.")
    return quote

def effective_status(quote):
    if quote.status != "CONVERTED" and quote.expiry_date and quote.expiry_date < date.today(): return "EXPIRED"
    return quote.status

def _customer(db, business_id, customer_id):
    customer = db.get(Customer, customer_id)
    if not customer or customer.business_id != business_id or not customer.is_active: raise ValueError("Invalid customer.")
    return customer

def _rows(db, business_id, items):
    rows = []
    for item in items:
        product = None
        if item.product_id:
            product = db.get(Product, item.product_id)
            if not product or product.business_id != business_id or not product.is_active: raise ValueError("Invalid product.")
        if item.variant_id:
            variant = db.get(ProductVariant, item.variant_id)
            if not variant or variant.business_id != business_id or not product or variant.product_id != product.id: raise ValueError("Invalid variant for product.")
        line = item.quantity * item.unit_price - item.discount
        if line < 0: raise ValueError("Item discount cannot exceed line price.")
        rows.append((item, line))
    return rows

def _set_totals(quote, rows, discount, tax):
    subtotal = sum((line for _, line in rows), Decimal("0")); total = subtotal - discount + tax
    if total < 0: raise ValueError("Discount cannot exceed subtotal plus tax.")
    quote.subtotal, quote.discount, quote.tax, quote.total = subtotal, discount, tax, total

def _number(data):
    return data.quote_number.strip() if data.quote_number else f"Q-{uuid.uuid4().hex.upper()}"

def create_quote(db, business_id, user_id, data):
    if data.branch_id: require_branch(db, business_id, data.branch_id)
    _customer(db, business_id, data.customer_id)
    rows = _rows(db, business_id, data.items)
    quote = Quote(business_id=business_id, branch_id=data.branch_id, customer_id=data.customer_id, quote_number=_number(data), status="DRAFT", issue_date=data.issue_date, expiry_date=data.expiry_date, subtotal=Decimal("0"), discount=Decimal("0"), tax=Decimal("0"), total=Decimal("0"), notes=data.notes, created_by=user_id)
    _set_totals(quote, rows, data.discount, data.tax); db.add(quote); db.flush()
    for item, line in rows: db.add(QuoteItem(quote_id=quote.id, product_id=item.product_id, variant_id=item.variant_id, description=item.description, quantity=item.quantity, unit_price=item.unit_price, discount=item.discount, line_total=line))
    record_audit(db,business_id,'QUOTE_CREATED','quote',quote.id,user_id,quote.branch_id,{'quote_number':quote.quote_number,'customer_id':str(quote.customer_id),'total':str(quote.total)}); db.commit(); db.refresh(quote); return quote

def update_quote(db, business_id, quote_id, data):
    quote = get_quote(db, business_id, quote_id, True)
    if effective_status(quote) not in EDITABLE: raise ValueError("Only draft quotes can be edited.")
    values = data.model_dump(exclude_unset=True); items = values.pop("items", None)
    if "branch_id" in values and values["branch_id"]: require_branch(db, business_id, values["branch_id"])
    if "customer_id" in values and values["customer_id"]: _customer(db, business_id, values["customer_id"])
    if "quote_number" in values and values["quote_number"]: values["quote_number"] = values["quote_number"].strip()
    for key, value in values.items(): setattr(quote, key, value)
    if items is not None:
        rows = _rows(db, business_id, items)
        for old in db.scalars(select(QuoteItem).where(QuoteItem.quote_id == quote.id)): db.delete(old)
        _set_totals(quote, rows, quote.discount, quote.tax)
        for item, line in rows: db.add(QuoteItem(quote_id=quote.id, product_id=item.product_id, variant_id=item.variant_id, description=item.description, quantity=item.quantity, unit_price=item.unit_price, discount=item.discount, line_total=line))
    elif {"discount", "tax"} & values.keys():
        _set_totals(quote, [(item, item.line_total) for item in db.scalars(select(QuoteItem).where(QuoteItem.quote_id == quote.id))], quote.discount, quote.tax)
    db.commit(); db.refresh(quote); return quote

def transition_quote(db, business_id, quote_id, target):
    quote = get_quote(db, business_id, quote_id, True); current = effective_status(quote)
    allowed = {"SENT": {"DRAFT"}, "ACCEPTED": {"SENT"}, "REJECTED": {"SENT"}}
    if current not in allowed[target]: raise ValueError(f"Quote cannot transition from {current} to {target}.")
    quote.status = target; db.commit(); db.refresh(quote); return quote

def convert_quote(db, business_id, quote_id, user_id):
    quote = get_quote(db, business_id, quote_id, True)
    if quote.converted_invoice_id:
        invoice = db.get(Invoice, quote.converted_invoice_id)
        if invoice and invoice.business_id == business_id: return quote, invoice
        raise ValueError("Quote conversion is invalid.")
    if effective_status(quote) != "ACCEPTED": raise ValueError("Only accepted, unexpired quotes can be converted.")
    _customer(db, business_id, quote.customer_id)
    items = list(db.scalars(select(QuoteItem).where(QuoteItem.quote_id == quote.id)))
    payload = InvoiceCreate(customer_id=quote.customer_id, branch_id=quote.branch_id, invoice_number=f"INV-Q-{uuid.uuid4().hex.upper()}", issue_date=quote.issue_date, due_date=quote.expiry_date, discount=quote.discount, tax=quote.tax, notes=quote.notes, items=[InvoiceItemCreate(product_id=i.product_id, variant_id=i.variant_id, description=i.description, quantity=i.quantity, unit_price=i.unit_price, discount=i.discount) for i in items])
    # Reuse the normal invoice validation and receivable behavior, while keeping
    # the invoice and conversion marker in this single transaction.
    invoice = create_invoice(db, business_id, user_id, payload, commit=False)
    quote.status = "CONVERTED"; quote.converted_invoice_id = invoice.id
    record_audit(db,business_id,'QUOTE_CONVERTED','quote',quote.id,user_id,quote.branch_id,{'invoice_id':str(invoice.id),'customer_id':str(quote.customer_id),'total':str(quote.total)}); db.commit(); db.refresh(quote); return quote, invoice
