from datetime import date, timedelta

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import Response
from sqlalchemy.orm import Session, selectinload

from app.database import get_db
from app.deps import require_permission
from app.invoice_pdf import build_invoice_pdf
from app.models import Client, CompanySetting, Event, Expense, Invoice, InvoiceItem, InvoiceSetting, Payment, Quotation, User
from app.schemas import ExpenseIn, ExpenseOut, InvoiceDraftOut, InvoiceDraftItemOut, InvoiceIn, InvoiceOut, PaymentIn, PaymentOut
from app.utils import event_profitability, invoice_totals, line_amount, money, next_public_id

router = APIRouter(tags=["finance"])


def _next_invoice_number(db: Session) -> str:
    settings = db.query(InvoiceSetting).first()
    prefix = settings.prefix if settings else "INV"
    number = settings.next_number if settings else 1
    value = f"{prefix}-{number:04d}"
    if settings:
        settings.next_number = number + 1
    return value


@router.get("/invoices", response_model=list[InvoiceOut])
def list_invoices(db: Session = Depends(get_db), _: User = Depends(require_permission("invoice.view"))):
    return db.query(Invoice).options(selectinload(Invoice.items)).order_by(Invoice.id.desc()).all()


@router.get("/invoices/{invoice_id}", response_model=InvoiceOut)
def get_invoice(invoice_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("invoice.view"))):
    row = db.query(Invoice).options(selectinload(Invoice.items)).filter(Invoice.id == invoice_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Invoice not found")
    return row


@router.get("/invoices/{invoice_id}/pdf")
def download_invoice_pdf(
    invoice_id: int,
    db: Session = Depends(get_db),
    _: User = Depends(require_permission("invoice.view")),
):
    row = (
        db.query(Invoice)
        .options(selectinload(Invoice.items), selectinload(Invoice.client), selectinload(Invoice.event))
        .filter(Invoice.id == invoice_id)
        .first()
    )
    if row is None:
        raise HTTPException(status_code=404, detail="Invoice not found")

    client = row.client or db.query(Client).filter(Client.id == row.client_id).first()
    event = row.event
    if event is None and row.event_id:
        event = db.query(Event).filter(Event.id == row.event_id).first()
    company = db.query(CompanySetting).first()
    pdf_bytes = build_invoice_pdf(invoice=row, client=client, event=event, company=company)
    filename = f"{row.invoice_number.replace('/', '-').replace(' ', '')}.pdf"
    return Response(
        content=pdf_bytes,
        media_type="application/pdf",
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )


@router.get("/events/{event_id}/invoice-draft", response_model=InvoiceDraftOut)
def invoice_draft_from_event(
    event_id: int,
    db: Session = Depends(get_db),
    _: User = Depends(require_permission("invoice.create")),
):
    event = (
        db.query(Event)
        .options(selectinload(Event.products), selectinload(Event.client))
        .filter(Event.id == event_id)
        .first()
    )
    if event is None:
        raise HTTPException(status_code=404, detail="Event not found")

    existing = (
        db.query(Invoice)
        .filter(Invoice.event_id == event_id, Invoice.status != "Cancelled")
        .order_by(Invoice.id.desc())
        .first()
    )
    if existing is None and event.invoice_id:
        existing = db.query(Invoice).filter(Invoice.id == event.invoice_id, Invoice.status != "Cancelled").first()

    settings = db.query(InvoiceSetting).first()
    due_days = settings.default_due_days if settings and settings.default_due_days else 7
    today = date.today()

    discount = money(0)
    if event.quotation_id:
        quote = db.query(Quotation).filter(Quotation.id == event.quotation_id).first()
        if quote:
            discount = money(quote.discount)

    items: list[InvoiceDraftItemOut] = []
    subtotal = money(0)
    for product in event.products or []:
        amount = money(product.amount)
        if amount <= 0:
            amount = line_amount(product.quantity, product.unit_price, product.discount)
        subtotal += amount
        items.append(
            InvoiceDraftItemOut(
                product_id=product.product_id,
                name=product.name,
                quantity=product.quantity,
                unit_price=money(product.unit_price),
                discount=money(product.discount),
                amount=amount,
            )
        )

    client = event.client or db.query(Client).filter(Client.id == event.client_id).first()
    return InvoiceDraftOut(
        event_id=event.id,
        client_id=event.client_id,
        client_name=client.name if client else None,
        event_name=event.name,
        event_public_id=event.public_id,
        invoice_date=today,
        due_date=today + timedelta(days=due_days),
        discount=discount,
        status="Issued",
        items=items,
        subtotal=subtotal,
        final_amount=money(subtotal - discount),
        existing_invoice_id=existing.id if existing else None,
        existing_invoice_number=existing.invoice_number if existing else None,
    )


@router.post("/invoices", response_model=InvoiceOut)
def create_invoice(payload: InvoiceIn, db: Session = Depends(get_db), _: User = Depends(require_permission("invoice.create"))):
    items = [
        InvoiceItem(
            name=item.name,
            quantity=item.quantity,
            unit_price=money(item.unit_price),
            discount=money(item.discount),
            amount=line_amount(item.quantity, item.unit_price, item.discount),
        )
        for item in payload.items
    ]
    subtotal = sum((item.amount for item in items), money(0))
    row = Invoice(
        public_id=next_public_id(db, Invoice, "INV"),
        invoice_number=_next_invoice_number(db),
        event_id=payload.event_id,
        client_id=payload.client_id,
        invoice_date=payload.invoice_date,
        due_date=payload.due_date,
        discount=money(payload.discount),
        subtotal=subtotal,
        final_amount=money(subtotal - money(payload.discount)),
        amount_paid=money(0),
        status=payload.status,
        items=items,
    )
    invoice_totals(row)
    db.add(row)
    db.flush()
    if payload.event_id:
        event = db.query(Event).filter(Event.id == payload.event_id).first()
        if event:
            event.invoice_id = row.id
    db.commit()
    db.refresh(row)
    return row


@router.patch("/invoices/{invoice_id}", response_model=InvoiceOut)
def update_invoice(invoice_id: int, payload: InvoiceIn, db: Session = Depends(get_db), _: User = Depends(require_permission("invoice.edit"))):
    row = db.query(Invoice).options(selectinload(Invoice.items)).filter(Invoice.id == invoice_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Invoice not found")
    row.event_id = payload.event_id
    row.client_id = payload.client_id
    row.invoice_date = payload.invoice_date
    row.due_date = payload.due_date
    row.discount = money(payload.discount)
    row.status = payload.status
    row.items.clear()
    db.flush()
    row.items = [
        InvoiceItem(
            name=item.name,
            quantity=item.quantity,
            unit_price=money(item.unit_price),
            discount=money(item.discount),
            amount=line_amount(item.quantity, item.unit_price, item.discount),
        )
        for item in payload.items
    ]
    row.subtotal = sum((item.amount for item in row.items), money(0))
    row.final_amount = money(row.subtotal - row.discount)
    invoice_totals(row)
    db.commit()
    db.refresh(row)
    return row


@router.post("/invoices/{invoice_id}/cancel", response_model=InvoiceOut)
def cancel_invoice(invoice_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("invoice.cancel"))):
    row = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Invoice not found")
    row.status = "Cancelled"
    db.commit()
    db.refresh(row)
    return row


@router.delete("/invoices/{invoice_id}")
def delete_invoice(invoice_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("invoice.cancel"))):
    row = db.query(Invoice).options(selectinload(Invoice.items)).filter(Invoice.id == invoice_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Invoice not found")
    if db.query(Payment).filter(Payment.invoice_id == invoice_id).first() is not None:
        raise HTTPException(status_code=400, detail="Cannot delete invoice with payments recorded")
    event = db.query(Event).filter(Event.id == row.event_id).first() if row.event_id else None
    if event and event.invoice_id == row.id:
        event.invoice_id = None
    row.items.clear()
    db.delete(row)
    db.commit()
    return {"ok": True}


@router.get("/payments", response_model=list[PaymentOut])
def list_payments(db: Session = Depends(get_db), _: User = Depends(require_permission("payment.view"))):
    return db.query(Payment).order_by(Payment.id.desc()).all()


@router.get("/payments/{payment_id}", response_model=PaymentOut)
def get_payment(payment_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("payment.view"))):
    row = db.query(Payment).filter(Payment.id == payment_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Payment not found")
    return row


@router.post("/payments", response_model=PaymentOut)
def create_payment(payload: PaymentIn, db: Session = Depends(get_db), actor: User = Depends(require_permission("payment.create"))):
    invoice = None
    if payload.invoice_id:
        invoice = db.query(Invoice).filter(Invoice.id == payload.invoice_id).first()
    elif payload.event_id:
        invoice = (
            db.query(Invoice)
            .filter(Invoice.event_id == payload.event_id, Invoice.status != "Cancelled")
            .order_by(Invoice.id.desc())
            .first()
        )
        if invoice is None:
            event = db.query(Event).filter(Event.id == payload.event_id).first()
            if event and event.invoice_id:
                invoice = db.query(Invoice).filter(Invoice.id == event.invoice_id, Invoice.status != "Cancelled").first()
    if invoice is None:
        raise HTTPException(status_code=404, detail="No invoice found for this event. Create an invoice first.")
    row = Payment(
        public_id=next_public_id(db, Payment, "PAY"),
        invoice_id=invoice.id,
        event_id=payload.event_id or invoice.event_id,
        client_id=payload.client_id or invoice.client_id,
        payment_date=payload.payment_date,
        amount=money(payload.amount),
        payment_mode=payload.payment_mode,
        transaction_reference=payload.transaction_reference,
        notes=payload.notes,
        recorded_by_id=actor.id,
    )
    invoice.amount_paid = money(invoice.amount_paid) + row.amount
    invoice_totals(invoice)
    db.add(row)
    db.commit()
    db.refresh(row)
    return row


@router.delete("/payments/{payment_id}")
def delete_payment(payment_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("payment.delete"))):
    row = db.query(Payment).filter(Payment.id == payment_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Payment not found")
    invoice = db.query(Invoice).filter(Invoice.id == row.invoice_id).first() if row.invoice_id else None
    if invoice is not None:
        next_paid = money(invoice.amount_paid) - money(row.amount)
        invoice.amount_paid = money(0) if next_paid < 0 else money(next_paid)
        invoice_totals(invoice)
    db.delete(row)
    db.commit()
    return {"ok": True}


@router.get("/expenses", response_model=list[ExpenseOut])
def list_expenses(db: Session = Depends(get_db), _: User = Depends(require_permission("expense.view"))):
    return db.query(Expense).order_by(Expense.id.desc()).all()


@router.get("/expenses/{expense_id}", response_model=ExpenseOut)
def get_expense(expense_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("expense.view"))):
    row = db.query(Expense).filter(Expense.id == expense_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Expense not found")
    return row


@router.post("/expenses", response_model=ExpenseOut)
def create_expense(payload: ExpenseIn, db: Session = Depends(get_db), actor: User = Depends(require_permission("expense.create"))):
    row = Expense(public_id=next_public_id(db, Expense, "EXP"), added_by_id=actor.id, **payload.model_dump())
    db.add(row)
    db.commit()
    db.refresh(row)
    return row


@router.patch("/expenses/{expense_id}", response_model=ExpenseOut)
def update_expense(expense_id: int, payload: ExpenseIn, db: Session = Depends(get_db), _: User = Depends(require_permission("expense.edit"))):
    row = db.query(Expense).filter(Expense.id == expense_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Expense not found")
    for key, value in payload.model_dump().items():
        setattr(row, key, value)
    db.commit()
    db.refresh(row)
    return row


@router.post("/expenses/{expense_id}/approve", response_model=ExpenseOut)
def approve_expense(expense_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("expense.approve"))):
    row = db.query(Expense).filter(Expense.id == expense_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Expense not found")
    row.approval_status = "Approved"
    db.commit()
    db.refresh(row)
    return row


@router.get("/events/{event_id}/profitability")
def profitability(event_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("report.finance"))):
    if db.query(Event).filter(Event.id == event_id).first() is None:
        raise HTTPException(status_code=404, detail="Event not found")
    return event_profitability(db, event_id)
