from __future__ import annotations
import uuid
from datetime import datetime, date
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.database import get_db
from app.config import get_settings
from app.models.sale import Sale, SaleItem, Quotation, QuotationItem
from app.models.customer import Customer
from app.models.plant import Plant
from app.models.inventory import InventoryItem, InventoryTransaction
from app.models.finance import JournalEntry
from app.services.auth import get_current_user
from app.services.accounting import AccountingError
from app.services.transaction_postings import (
    post_sale, return_sale, sale_posting_entry,
)

router = APIRouter(prefix="/sales", tags=["sales"])
settings = get_settings()


class SaleItemCreate(BaseModel):
    plant_id: Optional[str] = None
    inventory_id: Optional[str] = None
    inventory_item_id: Optional[str] = None
    description: Optional[str] = ""
    quantity: int = 1
    unit_price: float = 0.0
    discount: float = 0.0

class SaleItemOut(BaseModel):
    id: str
    sale_id: str
    plant_id: Optional[str] = None
    inventory_id: Optional[str] = None
    description: str
    quantity: int
    unit_price: float
    discount: float
    total: float
    model_config = {"from_attributes": True}

class SaleCreate(BaseModel):
    customer_id: Optional[str] = None
    sale_type: str = "retail"
    date: Optional[date] = None
    discount: float = 0.0
    tax: float = 0.0
    paid: float = 0.0
    payment_method: Optional[str] = None
    branch_id: Optional[str] = None
    notes: Optional[str] = None
    items: List[SaleItemCreate] = []

class SaleUpdate(BaseModel):
    customer_id: Optional[str] = None
    sale_type: Optional[str] = None
    date: Optional[date] = None
    discount: Optional[float] = None
    tax: Optional[float] = None
    paid: Optional[float] = None
    payment_method: Optional[str] = None
    status: Optional[str] = None
    notes: Optional[str] = None

class SaleOut(BaseModel):
    id: str
    invoice_number: str
    customer_id: Optional[str] = None
    sale_type: str
    date: date
    subtotal: float
    discount: float
    tax: float
    total: float
    paid: float
    due: float
    status: str
    payment_method: Optional[str] = None
    notes: Optional[str] = None
    created_by: str
    created_at: datetime
    model_config = {"from_attributes": True}

class QuotationItemCreate(BaseModel):
    plant_id: Optional[str] = None
    description: str
    quantity: int = 1
    unit_price: float = 0.0

class QuotationItemOut(BaseModel):
    id: str
    quotation_id: str
    plant_id: Optional[str] = None
    description: str
    quantity: int
    unit_price: float
    total: float
    model_config = {"from_attributes": True}

class QuotationCreate(BaseModel):
    customer_id: Optional[str] = None
    date: date
    valid_until: Optional[date] = None
    discount: float = 0.0
    notes: Optional[str] = None
    items: List[QuotationItemCreate] = []

class QuotationUpdate(BaseModel):
    customer_id: Optional[str] = None
    date: Optional[date] = None
    valid_until: Optional[date] = None
    discount: Optional[float] = None
    status: Optional[str] = None
    notes: Optional[str] = None

class QuotationOut(BaseModel):
    id: str
    quote_number: str
    customer_id: Optional[str] = None
    date: date
    valid_until: Optional[date] = None
    subtotal: float
    discount: float
    total: float
    status: str
    notes: Optional[str] = None
    created_by: str
    created_at: datetime
    model_config = {"from_attributes": True}


def _generate_invoice_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(Sale).filter(
        Sale.invoice_number.like(f"INV-{year}-%")
    ).order_by(Sale.invoice_number.desc()).first()
    if last:
        seq = int(last.invoice_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"INV-{year}-{seq:06d}"


def _generate_quote_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(Quotation).filter(
        Quotation.quote_number.like(f"QT-{year}-%")
    ).order_by(Quotation.quote_number.desc()).first()
    if last:
        seq = int(last.quote_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"QT-{year}-{seq:06d}"


@router.get("", response_model=list[SaleOut])
def list_sales(
    customer_id: Optional[str] = None,
    status: Optional[str] = None,
    from_date: Optional[date] = None,
    to_date: Optional[date] = None,
    db: Session = Depends(get_db),
    _=Depends(get_current_user),
):
    q = db.query(Sale)
    if customer_id:
        q = q.filter(Sale.customer_id == customer_id)
    if status:
        q = q.filter(Sale.status == status)
    if from_date:
        q = q.filter(Sale.date >= from_date)
    if to_date:
        q = q.filter(Sale.date <= to_date)
    return q.order_by(Sale.created_at.desc()).all()


@router.post("", response_model=SaleOut, status_code=201)
def create_sale(data: SaleCreate, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    sale_date = data.date or date.today()
    sale = Sale(
        invoice_number=_generate_invoice_number(db),
        customer_id=data.customer_id,
        sale_type=data.sale_type,
        date=sale_date,
        discount=data.discount,
        tax=data.tax,
        paid=data.paid,
        payment_method=data.payment_method,
        branch_id=data.branch_id or current_user.branch_id,
        notes=data.notes,
        created_by=current_user.id,
    )
    db.add(sale)
    db.flush()

    subtotal = 0.0
    for item_data in data.items:
        item_total = (item_data.unit_price * item_data.quantity) - item_data.discount
        subtotal += item_total
        inv_id = item_data.inventory_id or item_data.inventory_item_id
        desc = item_data.description or ""
        item = SaleItem(
            sale_id=sale.id,
            plant_id=item_data.plant_id,
            inventory_id=inv_id,
            description=desc,
            quantity=item_data.quantity,
            unit_price=item_data.unit_price,
            discount=item_data.discount,
            total=item_total,
        )
        db.add(item)

        if inv_id:
            inv_item = db.query(InventoryItem).filter(InventoryItem.id == inv_id).first()
            if inv_item:
                if inv_item.quantity < item_data.quantity:
                    db.rollback()
                    raise HTTPException(status_code=400, detail=f"Insufficient stock for {inv_item.name}")
                inv_item.quantity -= item_data.quantity
                if inv_item.quantity <= inv_item.min_quantity:
                    inv_item.status = "low_stock"

    total = subtotal - data.discount + data.tax
    sale.subtotal = subtotal
    sale.total = total
    sale.due = total - data.paid

    if data.customer_id:
        customer = db.query(Customer).filter(Customer.id == data.customer_id).first()
        if customer:
            customer.total_purchases += total
            customer.outstanding += sale.due

    db.flush()
    try:
        if settings.auto_post_sales and sale.total > 0 and sale.due <= 0:
            post_sale(db, sale.id, current_user.id)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))

    db.commit()
    db.refresh(sale)
    return sale


@router.get("/posting-batch")
def get_sales_posting_batch(ids: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    id_list = [i for i in ids.split(",") if i][:200]
    entries = db.query(JournalEntry).filter(
        JournalEntry.source == "sale",
        JournalEntry.reference_type == "sale",
        JournalEntry.reference_id.in_(id_list),
    ).all()
    by_id = {e.reference_id: e for e in entries}
    return {
        sale_id: {
            "posted": bool(by_id.get(sale_id)),
            "status": by_id[sale_id].status if by_id.get(sale_id) else None,
            "journal_entry_id": by_id[sale_id].id if by_id.get(sale_id) else None,
            "entry_number": by_id[sale_id].entry_number if by_id.get(sale_id) else None,
        }
        for sale_id in id_list
    }


@router.get("/{sale_id}", response_model=SaleOut)
def get_sale(sale_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Sale).filter(Sale.id == sale_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Sale not found")
    return obj


@router.get("/{sale_id}/items", response_model=list[SaleItemOut])
def get_sale_items(sale_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    return db.query(SaleItem).filter(SaleItem.sale_id == sale_id).all()


@router.post("/{sale_id}/post")
def post_sale_entry(sale_id: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    sale = db.query(Sale).filter(Sale.id == sale_id).first()
    if not sale:
        raise HTTPException(status_code=404, detail="Sale not found")
    try:
        entry = post_sale(db, sale.id, current_user.id)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"sale_id": sale.id, "invoice_number": sale.invoice_number,
            "posted": True, "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.post("/{sale_id}/return")
def reverse_sale_entry(sale_id: str, reason: Optional[str] = None,
                       db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    sale = db.query(Sale).filter(Sale.id == sale_id).first()
    if not sale:
        raise HTTPException(status_code=404, detail="Sale not found")
    try:
        entry = return_sale(db, sale.id, current_user.id, reason)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"sale_id": sale.id, "invoice_number": sale.invoice_number,
            "returned": True, "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.get("/{sale_id}/posting")
def get_sale_posting_status(sale_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    sale = db.query(Sale).filter(Sale.id == sale_id).first()
    if not sale:
        raise HTTPException(status_code=404, detail="Sale not found")
    entry = sale_posting_entry(db, sale)
    return {
        "sale_id": sale.id,
        "posted": bool(entry),
        "status": entry.status if entry else None,
        "journal_entry_id": entry.id if entry else None,
        "entry_number": entry.entry_number if entry else None,
        "sale_status": sale.status,
    }


@router.put("/{sale_id}", response_model=SaleOut)
def update_sale(sale_id: str, data: SaleUpdate, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Sale).filter(Sale.id == sale_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Sale not found")
    if sale_posting_entry(db, obj):
        raise HTTPException(status_code=400, detail="Posted sales cannot be edited; return or post first")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.patch("/{sale_id}/status")
def update_sale_status(sale_id: str, status: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    obj = db.query(Sale).filter(Sale.id == sale_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Sale not found")
    obj.status = status
    posted = False
    if settings.auto_post_sales and obj.status in ("completed", "paid", "confirmed", "closed"):
        try:
            post_sale(db, obj.id, current_user.id)
            posted = True
        except AccountingError as exc:
            db.rollback()
            raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"detail": f"Sale status updated to {status}", "posted": posted}


@router.delete("/{sale_id}")
def delete_sale(sale_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Sale).filter(Sale.id == sale_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Sale not found")
    if sale_posting_entry(db, obj):
        raise HTTPException(status_code=400, detail="Posted sales cannot be deleted; return the sale first")
    db.delete(obj)
    db.commit()
    return {"detail": "Sale deleted"}


@router.get("/quotations", response_model=list[QuotationOut])
def list_quotations(
    customer_id: Optional[str] = None,
    status: Optional[str] = None,
    db: Session = Depends(get_db),
    _=Depends(get_current_user),
):
    q = db.query(Quotation)
    if customer_id:
        q = q.filter(Quotation.customer_id == customer_id)
    if status:
        q = q.filter(Quotation.status == status)
    return q.order_by(Quotation.created_at.desc()).all()


@router.post("/quotations", response_model=QuotationOut, status_code=201)
def create_quotation(data: QuotationCreate, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    quotation = Quotation(
        quote_number=_generate_quote_number(db),
        customer_id=data.customer_id,
        date=data.date,
        valid_until=data.valid_until,
        discount=data.discount,
        notes=data.notes,
        created_by=current_user.id,
    )
    db.add(quotation)
    db.flush()

    subtotal = 0.0
    for item_data in data.items:
        item_total = item_data.unit_price * item_data.quantity
        subtotal += item_total
        item = QuotationItem(
            quotation_id=quotation.id,
            plant_id=item_data.plant_id,
            description=item_data.description,
            quantity=item_data.quantity,
            unit_price=item_data.unit_price,
            total=item_total,
        )
        db.add(item)

    quotation.subtotal = subtotal
    quotation.total = subtotal - data.discount
    db.commit()
    db.refresh(quotation)
    return quotation


@router.get("/quotations/{quotation_id}", response_model=QuotationOut)
def get_quotation(quotation_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Quotation).filter(Quotation.id == quotation_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Quotation not found")
    return obj


@router.get("/quotations/{quotation_id}/items", response_model=list[QuotationItemOut])
def get_quotation_items(quotation_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    return db.query(QuotationItem).filter(QuotationItem.quotation_id == quotation_id).all()


@router.put("/quotations/{quotation_id}", response_model=QuotationOut)
def update_quotation(quotation_id: str, data: QuotationUpdate, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Quotation).filter(Quotation.id == quotation_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Quotation not found")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.patch("/quotations/{quotation_id}/status")
def update_quotation_status(quotation_id: str, status: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Quotation).filter(Quotation.id == quotation_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Quotation not found")
    obj.status = status
    db.commit()
    return {"detail": f"Quotation status updated to {status}"}


@router.delete("/quotations/{quotation_id}")
def delete_quotation(quotation_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(Quotation).filter(Quotation.id == quotation_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Quotation not found")
    db.delete(obj)
    db.commit()
    return {"detail": "Quotation deleted"}
