from datetime import date

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, selectinload

from app.database import get_db
from app.deps import require_permission
from app.models import Enquiry, Event, EventProduct, Expense, Invoice, Payment, Quotation, StaffAllocation, User
from app.schemas import ConvertEnquiryIn, EventIn, EventOut, LineItemOut
from app.utils import event_profitability, line_amount, money, next_public_id

router = APIRouter(tags=["events"])


def serialize_event(db: Session, event: Event) -> EventOut:
    data = EventOut.model_validate(event)
    data.products = [
        LineItemOut(
            id=item.id,
            product_id=item.product_id,
            name=item.name,
            quantity=item.quantity,
            unit_price=item.unit_price,
            discount=item.discount,
            amount=item.amount,
            cost=item.cost,
        )
        for item in event.products
    ]
    data.profitability = event_profitability(db, event.id)
    return data


def _apply_products(event: Event, payload: EventIn) -> None:
    event.products = [
        EventProduct(
            product_id=item.product_id,
            name=item.name,
            quantity=item.quantity,
            unit_price=money(item.unit_price),
            cost=money(item.cost),
            discount=money(item.discount),
            amount=line_amount(item.quantity, item.unit_price, item.discount),
        )
        for item in payload.products
    ]


@router.get("/events", response_model=list[EventOut])
def list_events(scope: str | None = None, db: Session = Depends(get_db), _: User = Depends(require_permission("event.view"))):
    query = db.query(Event).options(selectinload(Event.products))
    today = date.today()
    if scope == "upcoming":
        query = query.filter(Event.event_date >= today, Event.status.in_(["Confirmed", "Upcoming"]))
    elif scope == "today":
        query = query.filter(Event.event_date == today)
    rows = query.order_by(Event.event_date.asc()).all()
    return [serialize_event(db, row) for row in rows]


@router.get("/events/{event_id}", response_model=EventOut)
def get_event(event_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("event.view"))):
    row = db.query(Event).options(selectinload(Event.products)).filter(Event.id == event_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Event not found")
    return serialize_event(db, row)


@router.post("/events", response_model=EventOut)
def create_event(payload: EventIn, db: Session = Depends(get_db), actor: User = Depends(require_permission("event.create"))):
    data = payload.model_dump(exclude={"products"})
    row = Event(public_id=next_public_id(db, Event, "EVT"), created_by_id=actor.id, **data)
    _apply_products(row, payload)
    db.add(row)
    db.commit()
    db.refresh(row)
    row = db.query(Event).options(selectinload(Event.products)).filter(Event.id == row.id).first()
    return serialize_event(db, row)


@router.patch("/events/{event_id}", response_model=EventOut)
def update_event(event_id: int, payload: EventIn, db: Session = Depends(get_db), _: User = Depends(require_permission("event.edit"))):
    row = db.query(Event).options(selectinload(Event.products)).filter(Event.id == event_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Event not found")
    data = payload.model_dump(exclude={"products"})
    for key, value in data.items():
        setattr(row, key, value)
    row.products.clear()
    db.flush()
    _apply_products(row, payload)
    db.commit()
    row = db.query(Event).options(selectinload(Event.products)).filter(Event.id == row.id).first()
    return serialize_event(db, row)


@router.delete("/events/{event_id}")
def delete_event(event_id: int, db: Session = Depends(get_db), _: User = Depends(require_permission("event.delete"))):
    row = db.query(Event).options(selectinload(Event.products)).filter(Event.id == event_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="Event not found")
    if db.query(Payment).filter(Payment.event_id == event_id).first() is not None:
        raise HTTPException(status_code=400, detail="Cannot delete event with recorded payments")
    invoices = db.query(Invoice).filter(Invoice.event_id == event_id).all()
    for invoice in invoices:
        if db.query(Payment).filter(Payment.invoice_id == invoice.id).first() is not None:
            raise HTTPException(status_code=400, detail="Cannot delete event with invoice payments")
        invoice.items.clear()
        db.delete(invoice)
    db.query(Expense).filter(Expense.event_id == event_id).delete()
    db.query(StaffAllocation).filter(StaffAllocation.event_id == event_id).delete()
    enquiry = db.query(Enquiry).filter(Enquiry.id == row.enquiry_id).first() if row.enquiry_id else None
    if enquiry and enquiry.status == "Confirmed":
        enquiry.status = "Negotiation"
    db.delete(row)
    db.commit()
    return {"ok": True}


def _event_products_from_enquiry(enquiry: Enquiry, quote: Quotation | None) -> list[EventProduct]:
    if quote and quote.items:
        return [
            EventProduct(
                product_id=item.product_id,
                name=item.name,
                quantity=item.quantity,
                unit_price=item.unit_price,
                discount=item.discount,
                amount=item.amount,
            )
            for item in quote.items
        ]
    if enquiry.products:
        return [
            EventProduct(
                product_id=item.product_id,
                name=item.name,
                quantity=item.quantity,
                unit_price=item.unit_price,
                discount=item.discount,
                amount=item.amount,
            )
            for item in enquiry.products
        ]
    return []


def _apply_enquiry_to_event(
    event: Event,
    enquiry: Enquiry,
    payload: ConvertEnquiryIn,
    quote: Quotation | None,
) -> None:
    event.name = payload.name or f"{enquiry.client_name} {enquiry.event_type or 'Event'}"
    event.event_type = enquiry.event_type
    event.client_id = enquiry.client_id
    event.enquiry_id = enquiry.id
    if quote:
        event.quotation_id = quote.id
    event.event_date = payload.event_date or enquiry.event_date or date.today()
    event.start_time = enquiry.start_time
    event.end_time = enquiry.end_time
    event.district = enquiry.district
    event.city = enquiry.city
    event.venue_name = enquiry.venue
    if event.status not in {"In Progress", "Completed", "Cancelled"}:
        event.status = "Confirmed"
    event.lead_source = enquiry.lead_source
    event.booking_channel = enquiry.booking_channel

    products = _event_products_from_enquiry(enquiry, quote)
    if products:
        event.products.clear()
        event.products.extend(products)

    if quote:
        quote.status = "Accepted"

    enquiry.status = "Confirmed"


@router.post("/enquiries/{enquiry_id}/convert", response_model=EventOut)
def convert_enquiry(enquiry_id: int, payload: ConvertEnquiryIn, db: Session = Depends(get_db), actor: User = Depends(require_permission("event.create"))):
    enquiry = db.query(Enquiry).options(selectinload(Enquiry.products)).filter(Enquiry.id == enquiry_id).first()
    if enquiry is None:
        raise HTTPException(status_code=404, detail="Enquiry not found")
    quote = (
        db.query(Quotation)
        .options(selectinload(Quotation.items))
        .filter(Quotation.enquiry_id == enquiry.id)
        .order_by(Quotation.id.desc())
        .first()
    )
    existing = db.query(Event).options(selectinload(Event.products)).filter(Event.enquiry_id == enquiry.id).order_by(Event.id.desc()).first()
    if existing:
        _apply_enquiry_to_event(existing, enquiry, payload, quote)
        if quote:
            quote.event_id = existing.id
        db.commit()
        event = db.query(Event).options(selectinload(Event.products)).filter(Event.id == existing.id).first()
        return serialize_event(db, event)

    event = Event(
        public_id=next_public_id(db, Event, "EVT"),
        created_by_id=actor.id,
    )
    _apply_enquiry_to_event(event, enquiry, payload, quote)
    db.add(event)
    db.flush()
    if quote:
        quote.event_id = event.id
    db.commit()
    event = db.query(Event).options(selectinload(Event.products)).filter(Event.id == event.id).first()
    return serialize_event(db, event)
